diff options
author | Sverker Eriksson <[email protected]> | 2017-08-30 20:55:08 +0200 |
---|---|---|
committer | Sverker Eriksson <[email protected]> | 2017-08-30 20:55:08 +0200 |
commit | 7c67bbddb53c364086f66260701bc54a61c9659c (patch) | |
tree | 92ab0d4b91d5e2f6e7a3f9d61ea25089e8a71fe0 /lib/ssl | |
parent | 97dc5e7f396129222419811c173edc7fa767b0f8 (diff) | |
parent | 3b7a6ffddc819bf305353a593904cea9e932e7dc (diff) | |
download | otp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.gz otp-7c67bbddb53c364086f66260701bc54a61c9659c.tar.bz2 otp-7c67bbddb53c364086f66260701bc54a61c9659c.zip |
Merge tag 'OTP-19.0' into sverker/19/binary_to_atom-utf8-crash/ERL-474/OTP-14590
Diffstat (limited to 'lib/ssl')
104 files changed, 22769 insertions, 9784 deletions
diff --git a/lib/ssl/Makefile b/lib/ssl/Makefile index a7a95004a6..bd43794a36 100644 --- a/lib/ssl/Makefile +++ b/lib/ssl/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2011. All Rights Reserved. -# -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. -# -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# Copyright Ericsson AB 1999-2016. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # diff --git a/lib/ssl/doc/src/Makefile b/lib/ssl/doc/src/Makefile index fb12499ef7..669062779e 100644 --- a/lib/ssl/doc/src/Makefile +++ b/lib/ssl/doc/src/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2012. All Rights Reserved. +# Copyright Ericsson AB 1999-2015. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # @@ -37,7 +38,7 @@ RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN) # Target Specs # ---------------------------------------------------- XML_APPLICATION_FILES = refman.xml -XML_REF3_FILES = ssl.xml ssl_session_cache_api.xml +XML_REF3_FILES = ssl.xml ssl_crl_cache.xml ssl_crl_cache_api.xml ssl_session_cache_api.xml XML_REF6_FILES = ssl_app.xml XML_PART_FILES = release_notes.xml usersguide.xml diff --git a/lib/ssl/doc/src/book.xml b/lib/ssl/doc/src/book.xml index ecfb915b44..056c958f0f 100644 --- a/lib/ssl/doc/src/book.xml +++ b/lib/ssl/doc/src/book.xml @@ -1,23 +1,24 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE book SYSTEM "book.dtd"> <book xmlns:xi="http://www.w3.org/2001/XInclude"> <header titlestyle="normal"> <copyright> - <year>1999</year><year>2011</year> + <year>1999</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> diff --git a/lib/ssl/doc/src/fascicules.xml b/lib/ssl/doc/src/fascicules.xml index 7ee764fda3..7a60e8dd1f 100644 --- a/lib/ssl/doc/src/fascicules.xml +++ b/lib/ssl/doc/src/fascicules.xml @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE fascicules SYSTEM "fascicules.dtd"> <fascicules> diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 8875d07535..3b6f988a2d 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -1,23 +1,24 @@ -<?xml version="1.0" encoding="iso-8859-1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> - <year>1999</year><year>2013</year> + <year>1999</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> @@ -25,8 +26,1206 @@ <file>notes.xml</file> </header> <p>This document describes the changes made to the SSL application.</p> - - <section><title>SSL 5.3</title> + + +<section><title>SSL 8.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Server now rejects, a not requested client cert, as an + incorrect handshake message and ends the connection.</p> + <p> + Own Id: OTP-13651</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Remove default support for DES cipher suites</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-13195</p> + </item> + <item> + <p> + Deprecate the function <c>crypto:rand_bytes</c> and make + sure that <c>crypto:strong_rand_bytes</c> is used in all + places that are cryptographically significant.</p> + <p> + Own Id: OTP-13214</p> + </item> + <item> + <p> + Better error handling of user error during TLS upgrade. + ERL-69 is solved by gen_statem rewrite of ssl + application.</p> + <p> + Own Id: OTP-13255</p> + </item> + <item> + <p> + Provide user friendly error message when crypto rejects a + key</p> + <p> + Own Id: OTP-13256</p> + </item> + <item> + <p> + Add ssl:getstat/1 and ssl:getstat/2</p> + <p> + Own Id: OTP-13415</p> + </item> + <item> + <p> + TLS distribution connections now allow specifying the + options <c>verify_fun</c>, <c>crl_check</c> and + <c>crl_cache</c>. See the documentation. GitHub pull req + #956 contributed by Magnus Henoch.</p> + <p> + Own Id: OTP-13429 Aux Id: Pull#956 </p> + </item> + <item> + <p> + Remove confusing error message when closing a distributed + erlang node running over TLS</p> + <p> + Own Id: OTP-13431</p> + </item> + <item> + <p> + Remove default support for use of md5 in TLS 1.2 + signature algorithms</p> + <p> + Own Id: OTP-13463</p> + </item> + <item> + <p> + ssl now uses gen_statem instead of gen_fsm to implement + the ssl connection process, this solves some timing + issues in addition to making the code more intuitive as + the behaviour can be used cleanly instead of having a lot + of workaround for shortcomings of the behaviour.</p> + <p> + Own Id: OTP-13464</p> + </item> + <item> + <p> + Phase out interoperability with clients that offer SSLv2. + By default they are no longer supported, but an option to + provide interoperability is offered.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-13465</p> + </item> + <item> + <p> + OpenSSL has functions to generate short (eight hex + digits) hashes of issuers of certificates and CRLs. These + hashes are used by the "c_rehash" script to populate + directories of CA certificates and CRLs, e.g. in the + Apache web server. Add functionality to let an Erlang + program find the right CRL for a given certificate in + such a directory.</p> + <p> + Own Id: OTP-13530</p> + </item> + <item> + <p> + Some legacy TLS 1.0 software does not tolerate the 1/n-1 + content split BEAST mitigation technique. Add a + beast_mitigation SSL option (defaulting to + one_n_minus_one) to select or disable the BEAST + mitigation technique.</p> + <p> + Own Id: OTP-13629</p> + </item> + <item> + <p> + Enhance error log messages to facilitate for users to + understand the error</p> + <p> + Own Id: OTP-13632</p> + </item> + <item> + <p> + Increased default DH params to 2048-bit</p> + <p> + Own Id: OTP-13636</p> + </item> + <item> + <p> + Propagate CRL unknown CA error so that public_key + validation process continues correctly and determines + what should happen.</p> + <p> + Own Id: OTP-13656</p> + </item> + <item> + <p> + Introduce a flight concept for handshake packages. This + is a preparation for enabling DTLS, however it can also + have a positive effects for TLS on slow and unreliable + networks.</p> + <p> + Own Id: OTP-13678</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 7.3.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct ssl:prf/5 to use the negotiated cipher suite's + prf function in ssl:prf/5 instead of the default prf.</p> + <p> + Own Id: OTP-13546</p> + </item> + <item> + <p> + Timeouts may have the value 0, guards have been corrected + to allow this</p> + <p> + Own Id: OTP-13635</p> + </item> + <item> + <p> + Change of internal handling of hash sign pairs as the + used one enforced to much restrictions making some valid + combinations unavailable.</p> + <p> + Own Id: OTP-13670</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Create a little randomness in sending of session + invalidation messages, to mitigate load when whole table + is invalidated.</p> + <p> + Own Id: OTP-13490</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 7.3.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Correct cipher suites conversion and gaurd expression. + Caused problems with GCM cipher suites and client side + option to set signature_algorithms extention values.</p> + <p> + Own Id: OTP-13525</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 7.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Corrections to cipher suite handling using the 3 and 4 + tuple format in addition to commit + 89d7e21cf4ae988c57c8ef047bfe85127875c70c</p> + <p> + Own Id: OTP-13511</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Make values for the TLS-1.2 signature_algorithms + extension configurable</p> + <p> + Own Id: OTP-13261</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 7.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Make sure there is only one poller validator at a time + for validating the session cache.</p> + <p> + Own Id: OTP-13185</p> + </item> + <item> + <p> + A timing related issue could cause ssl to hang, + especially happened with newer versions of OpenSSL in + combination with ECC ciphers.</p> + <p> + Own Id: OTP-13253</p> + </item> + <item> + <p> + Work around a race condition in the TLS distribution + start.</p> + <p> + Own Id: OTP-13268</p> + </item> + <item> + <p> + Big handshake messages are now correctly fragmented in + the TLS record layer.</p> + <p> + Own Id: OTP-13306</p> + </item> + <item> + <p> + Improve portability of ECC tests in Crypto and SSL for + "exotic" OpenSSL versions.</p> + <p> + Own Id: OTP-13311</p> + </item> + <item> + <p> + Certificate extensions marked as critical are ignored + when using verify_none</p> + <p> + Own Id: OTP-13377</p> + </item> + <item> + <p> + If a certificate doesn't contain a CRL Distribution + Points extension, and the relevant CRL is not in the + cache, and the <c>crl_check</c> option is not set to + <c>best_effort</c> , the revocation check should fail.</p> + <p> + Own Id: OTP-13378</p> + </item> + <item> + <p> + Enable TLS distribution over IPv6</p> + <p> + Own Id: OTP-13391</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Improve error reporting for TLS distribution</p> + <p> + Own Id: OTP-13219</p> + </item> + <item> + <p> + Include options from connect, listen and accept in + <c>connection_information/1,2</c></p> + <p> + Own Id: OTP-13232</p> + </item> + <item> + <p> + Allow adding extra options for outgoing TLS distribution + connections, as supported for plain TCP connections.</p> + <p> + Own Id: OTP-13285</p> + </item> + <item> + <p> + Use loopback as server option in TLS-distribution module</p> + <p> + Own Id: OTP-13300</p> + </item> + <item> + <p> + Verify certificate signature against original certificate + binary.</p> + <p> + This avoids bugs due to encoding errors when re-encoding + a decode certificate. As there exists several decode step + and using of different ASN.1 specification this is a risk + worth avoiding.</p> + <p> + Own Id: OTP-13334</p> + </item> + <item> + <p> + Use <c>application:ensure_all_started/2</c> instead of + hard-coding dependencies</p> + <p> + Own Id: OTP-13363</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 7.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Honor distribution port range options</p> + <p> + Own Id: OTP-12838</p> + </item> + <item> + <p> + Correct supervisor specification in TLS distribution.</p> + <p> + Own Id: OTP-13134</p> + </item> + <item> + <p> + Correct cache timeout</p> + <p> + Own Id: OTP-13141</p> + </item> + <item> + <p> + Avoid crash and restart of ssl process when key file does + not exist.</p> + <p> + Own Id: OTP-13144</p> + </item> + <item> + <p> + Enable passing of raw socket options on the format + {raw,_,_,_} to the underlying socket.</p> + <p> + Own Id: OTP-13166</p> + </item> + <item> + <p> + Hibernation with small or a zero timeout will now work as + expected</p> + <p> + Own Id: OTP-13189</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add upper limit for session cache, configurable on ssl + application level.</p> + <p> + If upper limit is reached, invalidate the current cache + entries, e.i the session lifetime is the max time a + session will be keept, but it may be invalidated earlier + if the max limit for the table is reached. This will keep + the ssl manager process well behaved, not exhusting + memeory. Invalidating the entries will incrementally + empty the cache to make room for fresh sessions entries.</p> + <p> + Own Id: OTP-12392</p> + </item> + <item> + <p> + Use new time functions to measure passed time.</p> + <p> + Own Id: OTP-12457</p> + </item> + <item> + <p> + Improved error handling in TLS distribution</p> + <p> + Own Id: OTP-13142</p> + </item> + <item> + <p> + Distribution over TLS now honors the nodelay distribution + flag</p> + <p> + Own Id: OTP-13143</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 7.1</title> + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Add DER encoded ECPrivateKey as valid input format for + key option.</p> + <p> + Own Id: OTP-12974</p> + </item> + <item> + <p> + Correct return value of default session callback module</p> + <p> + This error had the symptom that the client check for + unique session would always fail, potentially making the + client session table grow a lot and causing long setup + times.</p> + <p> + Own Id: OTP-12980</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add possibility to downgrade an SSL/TLS connection to a + tcp connection, and give back the socket control to a + user process.</p> + <p> + This also adds the possibility to specify a timeout to + the ssl:close function.</p> + <p> + Own Id: OTP-11397</p> + </item> + <item> + <p> + Add application setting to be able to change fatal alert + shutdown timeout, also shorten the default timeout. The + fatal alert timeout is the number of milliseconds between + sending of a fatal alert and closing the connection. + Waiting a little while improves the peers chances to + properly receiving the alert so it may shutdown + gracefully.</p> + <p> + Own Id: OTP-12832</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 7.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Ignore signature_algorithm (TLS 1.2 extension) sent to + TLS 1.0 or TLS 1.1 server</p> + <p> + Own Id: OTP-12670</p> + </item> + <item> + <p> + Improve error handling in TLS distribution module to + avoid lingering sockets.</p> + <p> + Own Id: OTP-12799 Aux Id: Tom Briden </p> + </item> + <item> + <p> + Add option {client_renegotiation, boolean()} option to + the server-side of the SSL application.</p> + <p> + Own Id: OTP-12815</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add new API functions to handle CRL-verification</p> + <p> + Own Id: OTP-10362 Aux Id: kunagi-215 [126] </p> + </item> + <item> + <p> + Remove default support for SSL-3.0, due to Poodle + vunrability in protocol specification.</p> + <p> + Add padding check for TLS-1.0 to remove Poodle + vunrability from TLS 1.0, also add the option + padding_check. This option only affects TLS-1.0 + connections and if set to false it disables the block + cipher padding check to be able to interoperate with + legacy software.</p> + <p> + Remove default support for RC4 cipher suites, as they are + consider too weak.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-12390</p> + </item> + <item> + <p> + Add support for TLS ALPN (Application-Layer Protocol + Negotiation) extension.</p> + <p> + Own Id: OTP-12580</p> + </item> + <item> + <p> + Add SNI (Server Name Indication) support for the server + side.</p> + <p> + Own Id: OTP-12736</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 6.0.1.1</title> + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Gracefully ignore proprietary hash_sign algorithms</p> + <p> + Own Id: OTP-12829</p> + </item> + </list> + </section> +</section> + + +<section><title>SSL 6.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Terminate gracefully when receving bad input to premaster + secret calculation</p> + <p> + Own Id: OTP-12783</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 6.0</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Exclude self-signed trusted anchor certificates from + certificate prospective certification path according to + RFC 3280.</p> + <p> + This will avoid some unnecessary certificate processing.</p> + <p> + Own Id: OTP-12449</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Separate client and server session cache internally.</p> + <p> + Avoid session table growth when client starts many + connections in such a manner that many connections are + started before session reuse is possible. Only save a new + session in client if there is no equivalent session + already stored.</p> + <p> + Own Id: OTP-11365</p> + </item> + <item> + <p> + The PEM cache is now validated by a background process, + instead of always keeping it if it is small enough and + clearing it otherwise. That strategy required that small + caches where cleared by API function if a file changes on + disk.</p> + <p> + However export the API function to clear the cache as it + may still be useful.</p> + <p> + Own Id: OTP-12391</p> + </item> + <item> + <p> + Add padding check for TLS-1.0 to remove Poodle + vulnerability from TLS 1.0, also add the option + padding_check. This option only affects TLS-1.0 + connections and if set to false it disables the block + cipher padding check to be able to interoperate with + legacy software.</p> + <p> + *** POTENTIAL INCOMPATIBILITY ***</p> + <p> + Own Id: OTP-12420</p> + </item> + <item> + <p> + Add support for TLS_FALLBACK_SCSV used to prevent + undesired TLS version downgrades. If used by a client + that is vulnerable to the POODLE attack, and the server + also supports TLS_FALLBACK_SCSV, the attack can be + prevented.</p> + <p> + Own Id: OTP-12458</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.8</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Make sure the clean rule for ssh, ssl, eunit and otp_mibs + actually removes generated files.</p> + <p> + Own Id: OTP-12200</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Change code to reflect that state data may be secret to + avoid breaking dialyzer contracts.</p> + <p> + Own Id: OTP-12341</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.7</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Handle the fact that servers may send an empty SNI + extension to the client.</p> + <p> + Own Id: OTP-12198</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.6</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Corrected handling of ECC certificates, there where + several small issues with the handling of such + certificates in the ssl and public_key application. Now + ECC signed ECC certificates shall work and not only RSA + signed ECC certificates.</p> + <p> + Own Id: OTP-12026</p> + </item> + <item> + <p> + Check that the certificate chain ends with a trusted ROOT + CA e.i. a self-signed certificate, but provide an option + partial_chain to enable the application to define an + intermediat CA as trusted.</p> + <p> + Own Id: OTP-12149</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add decode functions for SNI (Server Name Indication)</p> + <p> + Own Id: OTP-12048</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.5</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + ssl:recv now returns {error, einval} if applied to a non + passive socket, the same as gen_tcp:recv. </p> + <p> + Thanks to Danil Zagoskin for reporting this issue</p> + <p> + Own Id: OTP-11878</p> + </item> + <item> + <p> + Corrected handling of default values for + signature_algorithms extension in TLS-1.2 and + corresponding values used in previous versions that does + not support this extension. </p> + <p> + Thanks to Danil Zagoskin</p> + <p> + Own Id: OTP-11886</p> + </item> + <item> + <p> + Handle socket option inheritance when pooling of accept + sockets is used</p> + <p> + Own Id: OTP-11897</p> + </item> + <item> + <p> + Make sure that the list of versions, possibly supplied in + the versions option, is not order dependent.</p> + <p> + Thanks to Ransom Richardson for reporting this issue</p> + <p> + Own Id: OTP-11912</p> + </item> + <item> + <p> + Reject connection if the next_protocol message is sent + twice.</p> + <p> + Own Id: OTP-11926</p> + </item> + <item> + <p> + Correct options handling when ssl:ssl_accept/3 is called + with new ssl options after calling ssl:listen/2</p> + <p> + Own Id: OTP-11950</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Gracefully handle unknown alerts</p> + <p> + Thanks to Atul Atri for reporting this issue</p> + <p> + Own Id: OTP-11874</p> + </item> + <item> + <p> + Gracefully ignore cipher suites sent by client not + supported by the SSL/TLS version that the client has + negotiated.</p> + <p> + Thanks to Danil Zagoskin for reporting this issue</p> + <p> + Own Id: OTP-11875</p> + </item> + <item> + <p> + Gracefully handle structured garbage, i.e a client sends + some garbage in a ssl record instead of a valid fragment.</p> + <p> + Thanks to Danil Zagoskin</p> + <p> + Own Id: OTP-11880</p> + </item> + <item> + <p> + Gracefully handle invalid alerts</p> + <p> + Own Id: OTP-11890</p> + </item> + <item> + <p> + Generalize handling of default ciphers</p> + <p> + Thanks to Andreas Schultz</p> + <p> + Own Id: OTP-11966</p> + </item> + <item> + <p> + Make sure change cipher spec is correctly handled</p> + <p> + Own Id: OTP-11975</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.4</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix incorrect dialyzer spec and types, also enhance + documentation. </p> + <p> + Thanks to Ayaz Tuncer.</p> + <p> + Own Id: OTP-11627</p> + </item> + <item> + <p> + Fix possible mismatch between SSL/TLS version and default + ciphers. Could happen when you specified SSL/TLS-version + in optionlist to listen or accept.</p> + <p> + Own Id: OTP-11712</p> + </item> + <item> + <p> + Application upgrade (appup) files are corrected for the + following applications: </p> + <p> + <c>asn1, common_test, compiler, crypto, debugger, + dialyzer, edoc, eldap, erl_docgen, et, eunit, gs, hipe, + inets, observer, odbc, os_mon, otp_mibs, parsetools, + percept, public_key, reltool, runtime_tools, ssh, + syntax_tools, test_server, tools, typer, webtool, wx, + xmerl</c></p> + <p> + A new test utility for testing appup files is added to + test_server. This is now used by most applications in + OTP.</p> + <p> + (Thanks to Tobias Schlager)</p> + <p> + Own Id: OTP-11744</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Moved elliptic curve definition from the crypto + NIF/OpenSSL into Erlang code, adds the RFC-5639 brainpool + curves and makes TLS use them (RFC-7027).</p> + <p> + Thanks to Andreas Schultz</p> + <p> + Own Id: OTP-11578</p> + </item> + <item> + <p> + Unicode adaptations</p> + <p> + Own Id: OTP-11620</p> + </item> + <item> + <p> + Added option honor_cipher_order. This instructs the + server to prefer its own cipher ordering rather than the + client's and can help protect against things like BEAST + while maintaining compatability with clients which only + support older ciphers. </p> + <p> + Thanks to Andrew Thompson for the implementation, and + Andreas Schultz for the test cases.</p> + <p> + Own Id: OTP-11621</p> + </item> + <item> + <p> + Replace boolean checking in validate_option with + is_boolean guard. </p> + <p> + Thanks to Andreas Schultz.</p> + <p> + Own Id: OTP-11634</p> + </item> + <item> + <p> + Some function specs are corrected or moved and some edoc + comments are corrected in order to allow use of edoc. + (Thanks to Pierre Fenoll)</p> + <p> + Own Id: OTP-11702</p> + </item> + <item> + <p> + Correct clean up of certificate database when certs are + inputed in pure DER format.The incorrect code could cause + a memory leek when certs where inputed in DER. Thanks to + Bernard Duggan for reporting this.</p> + <p> + Own Id: OTP-11733</p> + </item> + <item> + <p> + Improved documentation of the cacertfile option</p> + <p> + Own Id: OTP-11759 Aux Id: seq12535 </p> + </item> + <item> + <p> + Avoid next protocol negotiation failure due to incorrect + option format.</p> + <p> + Own Id: OTP-11760</p> + </item> + <item> + <p> + Handle v1 CRLs, with no extensions and fixes issues with + IDP (Issuing Distribution Point) comparison during CRL + validation. </p> + <p> + Thanks to Andrew Thompson</p> + <p> + Own Id: OTP-11761</p> + </item> + <item> + <p> + Server now ignores client ECC curves that it does not + support instead of crashing. </p> + <p> + Thanks to Danil Zagoskin for reporting the issue and + suggesting a solution.</p> + <p> + Own Id: OTP-11780</p> + </item> + <item> + <p> + Handle SNI (Server Name Indication) alert + unrecognized_name and gracefully deal with unexpected + alerts. </p> + <p> + Thanks to Masatake Daimon for reporting this.</p> + <p> + Own Id: OTP-11815</p> + </item> + <item> + <p> + Add possibility to specify ssl options when calling + ssl:ssl_accept</p> + <p> + Own Id: OTP-11837</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.3</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Add missing validation of the server_name_indication + option and test for its explicit use. It was not possible + to set or disable the default server_name_indication as + the validation of the option was missing.</p> + <p> + Own Id: OTP-11567</p> + </item> + <item> + <p> + Elliptic curve selection in server mode now properly + selects a curve suggested by the client, if possible, and + the fallback alternative is changed to a more widely + supported curve.</p> + <p> + Own Id: OTP-11575</p> + </item> + <item> + <p> + Bug in the TLS hello extension handling caused the server + to behave as it did not understand secure renegotiation.</p> + <p> + Own Id: OTP-11595</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Honors the clients advertised support of elliptic curves + and no longer sends incorrect elliptic curve extension in + server hello.</p> + <p> + Own Id: OTP-11370</p> + </item> + <item> + <p> + Fix initialization of DTLS fragment reassembler, in + previously contributed code, for future support of DTLS . + Thanks to Andreas Schultz.</p> + <p> + Own Id: OTP-11376</p> + </item> + <item> + <p> + Corrected type error in client_preferred_next_protocols + documentation. Thanks to Julien Barbot.</p> + <p> + Own Id: OTP-11457</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + TLS code has been refactored to prepare for future DTLS + support. Also some DTLS code is in place but not yet + runnable, some of it contributed by Andreas Schultz and + some of it written by the OTP team. Thanks to to Andreas + for his participation.</p> + <p> + Own Id: OTP-11292</p> + </item> + <item> + <p> + Remove extraneous dev debug code left in the close + function. Thanks to Ken Key.</p> + <p> + Own Id: OTP-11447</p> + </item> + <item> + <p> + Add SSL Server Name Indication (SNI) client support. + Thanks to Julien Barbot.</p> + <p> + Own Id: OTP-11460</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Setopts during renegotiation caused the renegotiation to + be unsuccessful.</p> + <p> + If calling setopts during a renegotiation the FSM state + might change during the handling of the setopts messages, + this is now handled correctly.</p> + <p> + Own Id: OTP-11228</p> + </item> + <item> + <p> + Now handles signature_algorithm field in digitally_signed + properly with proper defaults. Prior to this change some + elliptic curve cipher suites could fail reporting the + error "bad certificate".</p> + <p> + Own Id: OTP-11229</p> + </item> + <item> + <p> + The code emulating the inet header option was changed in + the belief that it made it inet compatible. However the + testing is a bit hairy as the inet option is actually + broken, now the tests are corrected and the header option + should work in the same broken way as inet again, + preferably use the bitsyntax instead.</p> + <p> + Own Id: OTP-11230</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Make the ssl manager name for erlang distribution over + SSL/TLS relative to the module name of the ssl_manager.</p> + <p> + This can be beneficial when making tools that rename + modules for internal processing in the tool.</p> + <p> + Own Id: OTP-11255</p> + </item> + <item> + <p> + Add documentation regarding log_alert option.</p> + <p> + Own Id: OTP-11271</p> + </item> + </list> + </section> + +</section> + +<section><title>SSL 5.3</title> <section><title>Fixed Bugs and Malfunctions</title> <list> @@ -100,7 +1299,6 @@ </section> <section><title>SSL 5.2.1</title> - <section><title>Improvements and New Features</title> <list> <item> @@ -126,9 +1324,20 @@ </section> </section> - +<section><title>SSL 5.1.2.1</title> +<section><title>Improvements and New Features</title> +<list> + <item> + <p> + Make log_alert configurable as option in ssl, SSLLogLevel + added as option to inets conf file</p> + <p> + Own Id: OTP-11259</p> + </item> +</list> +</section> +</section> <section><title>SSL 5.2</title> - <section><title>Fixed Bugs and Malfunctions</title> <list> <item> diff --git a/lib/ssl/doc/src/pkix_certs.xml b/lib/ssl/doc/src/pkix_certs.xml index 1de807cadc..f365acef4d 100644 --- a/lib/ssl/doc/src/pkix_certs.xml +++ b/lib/ssl/doc/src/pkix_certs.xml @@ -1,23 +1,24 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> - <year>2003</year><year>2009</year> + <year>2003</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> diff --git a/lib/ssl/doc/src/refman.xml b/lib/ssl/doc/src/refman.xml index 011819e82b..317da00414 100644 --- a/lib/ssl/doc/src/refman.xml +++ b/lib/ssl/doc/src/refman.xml @@ -1,23 +1,24 @@ -<?xml version="1.0" encoding="iso-8859-1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE application SYSTEM "application.dtd"> <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1999</year><year>2011</year> + <year>1999</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> @@ -28,23 +29,10 @@ <rev>B</rev> <file>refman.sgml</file> </header> - <description> - <p>The <em>SSL</em> application provides secure communication over - sockets. - </p> - <p>This product includes software developed by the OpenSSL Project for - use in the OpenSSL Toolkit (http://www.openssl.org/). - </p> - <p>This product includes cryptographic software written by Eric Young - ([email protected]). - </p> - <p>This product includes software written by Tim Hudson - ([email protected]). - </p> - <p>For full OpenSSL and SSLeay license texts, see <seealso marker="licenses#licenses">Licenses</seealso>.</p> - </description> <xi:include href="ssl_app.xml"/> <xi:include href="ssl.xml"/> + <xi:include href="ssl_crl_cache.xml"/> + <xi:include href="ssl_crl_cache_api.xml"/> <xi:include href="ssl_session_cache_api.xml"/> </application> diff --git a/lib/ssl/doc/src/release_notes.xml b/lib/ssl/doc/src/release_notes.xml index e7c766bb91..2e263c69a7 100644 --- a/lib/ssl/doc/src/release_notes.xml +++ b/lib/ssl/doc/src/release_notes.xml @@ -1,23 +1,24 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE part SYSTEM "part.dtd"> <part xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1999</year><year>2009</year> + <year>1999</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index 1645eb15f3..abba5aaf59 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -1,264 +1,284 @@ -<?xml version="1.0" encoding="iso-8859-1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE erlref SYSTEM "erlref.dtd"> <erlref> <header> <copyright> - <year>1999</year><year>2013</year> + <year>1999</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> <title>ssl</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> <file>ssl.xml</file> </header> <module>ssl</module> <modulesummary>Interface Functions for Secure Socket Layer</modulesummary> <description> - <p>This module contains interface functions to the Secure Socket - Layer. + <p> + This module contains interface functions for the SSL/TLS protocol. + For detailed information about the supported standards see + <seealso marker="ssl_app">ssl(6)</seealso>. </p> </description> - + <section> - <title>SSL</title> + <title>DATA TYPES</title> + <p>The following data types are used in the functions for SSL:</p> - <list type="bulleted"> - <item>ssl requires the crypto and public_key applications.</item> - <item>Supported SSL/TLS-versions are SSL-3.0, TLS-1.0, - TLS-1.1 and TLS-1.2.</item> - <item>For security reasons sslv2 is not supported.</item> - <item>Ephemeral Diffie-Hellman cipher suites are supported - but not Diffie Hellman Certificates cipher suites.</item> - <item>Elliptic Curve cipher suites are supported if crypto - supports it and named curves are used. + <taglist> + + <tag><c>boolean() =</c></tag> + <item><p><c>true | false</c></p></item> + + <tag><c>option() =</c></tag> + <item><p><c>socketoption() | ssl_option() | transport_option()</c></p> </item> - <item>Export cipher suites are not supported as the - U.S. lifted its export restrictions in early 2000.</item> - <item>IDEA cipher suites are not supported as they have - become deprecated by the latest TLS spec so there is not any - real motivation to implement them.</item> - <item>CRL and policy certificate extensions are not supported - yet. However CRL verification is supported by public_key, only not integrated - in ssl yet. </item> - </list> - - </section> - - <section> - <title>COMMON DATA TYPES</title> - <p>The following data types are used in the functions below: - </p> - <p><c>boolean() = true | false</c></p> + <tag><c>socketoption() =</c></tag> + <item><p><c>proplists:property()</c></p> + <p>The default socket options are + <c>[{mode,list},{packet, 0},{header, 0},{active, true}]</c>.</p> + <p>For valid options, see the + <seealso marker="kernel:inet">inet(3)</seealso> and + <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> manual pages + in Kernel.</p></item> - <p><c>option() = socketoption() | ssloption() | transportoption()</c></p> + <tag><marker id="type-ssloption"/><c>ssl_option() =</c></tag> + <item> + <p><c>{verify, verify_type()}</c></p> + <p><c>| {verify_fun, {fun(), term()}}</c></p> + <p><c>| {fail_if_no_peer_cert, boolean()}</c></p> + <p><c>| {depth, integer()}</c></p> + <p><c>| {cert, public_key:der_encoded()}</c></p> + <p><c>| {certfile, path()}</c></p> + <p><c>| {key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' + | 'PrivateKeyInfo', public_key:der_encoded()}}</c></p> + <p><c>| {keyfile, path()}</c></p> + <p><c>| {password, string()}</c></p> + <p><c>| {cacerts, [public_key:der_encoded()]}</c></p> + <p><c>| {cacertfile, path()}</c></p> + <p><c>| {dh, public_key:der_encoded()}</c></p> + <p><c>| {dhfile, path()}</c></p> + <p><c>| {ciphers, ciphers()}</c></p> + <p><c>| {user_lookup_fun, {fun(), term()}}, {psk_identity, string()}, + {srp_identity, {string(), string()}}</c></p> + <p><c>| {reuse_sessions, boolean()}</c></p> + <p><c>| {reuse_session, fun()} {next_protocols_advertised, [binary()]}</c></p> + <p><c>| {client_preferred_next_protocols, {client | server, + [binary()]} | {client | server, [binary()], binary()}}</c></p> + <p><c>| {log_alert, boolean()}</c></p> + <p><c>| {server_name_indication, hostname() | disable}</c></p> + <p><c>| {sni_hosts, [{hostname(), [ssl_option()]}]}</c></p> + <p><c>| {sni_fun, SNIfun::fun()}</c></p> + </item> + + <tag><c>transport_option() =</c></tag> + <item><p><c>{cb_info, {CallbackModule::atom(), DataTag::atom(), + + ClosedTag::atom(), ErrTag:atom()}}</c></p> + <p>Defaults to <c>{gen_tcp, tcp, tcp_closed, tcp_error}</c>. Can be used + to customize the transport layer. The callback module must implement a + reliable transport protocol, behave as <c>gen_tcp</c>, and have functions + corresponding to <c>inet:setopts/2</c>, <c>inet:getopts/2</c>, + <c>inet:peername/1</c>, <c>inet:sockname/1</c>, and <c>inet:port/1</c>. + The callback <c>gen_tcp</c> is treated specially and calls <c>inet</c> + directly.</p> + <taglist> + <tag><c>CallbackModule =</c></tag> + <item><p><c>atom()</c></p></item> + <tag><c>DataTag =</c></tag> + <item><p><c>atom()</c></p> + <p>Used in socket data message.</p></item> + <tag><c>ClosedTag =</c></tag> + <item><p><c>atom()</c></p> + <p>Used in socket close message.</p></item> + </taglist> + </item> - <p><c>socketoption() = proplists:property() - The default socket options are - [{mode,list},{packet, 0},{header, 0},{active, true}]. - </c></p> + <tag><c>verify_type() =</c></tag> + <item><p><c>verify_none | verify_peer</c></p></item> - <p>For valid options - see <seealso marker="kernel:inet">inet(3)</seealso> and - <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>. - </p> - - <p> <c>ssloption() = {verify, verify_type()} | - {verify_fun, {fun(), term()}} | - {fail_if_no_peer_cert, boolean()} - {depth, integer()} | - {cert, der_encoded()}| {certfile, path()} | - {key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo', der_encoded()}} | - {keyfile, path()} | {password, string()} | - {cacerts, [der_encoded()]} | {cacertfile, path()} | - |{dh, der_encoded()} | {dhfile, path()} | {ciphers, ciphers()} | - {user_lookup_fun, {fun(), term()}}, {psk_identity, string()}, {srp_identity, {string(), string()}} | - {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | {reuse_session, fun()} - {next_protocols_advertised, [binary()]} | - {client_preferred_next_protocols, client | server, [binary()]} - </c></p> - - <p><c>transportoption() = {cb_info, {CallbackModule::atom(), DataTag::atom(), ClosedTag::atom(), ErrTag:atom()}} - - defaults to {gen_tcp, tcp, tcp_closed, tcp_error}. Can be used to customize - the transport layer. The callback module must implement a reliable transport - protocol and behave as gen_tcp and in addition have functions corresponding to - inet:setopts/2, inet:getopts/2, inet:peername/1, inet:sockname/1 and inet:port/1. - The callback gen_tcp is treated specially and will call inet directly. - </c></p> - - <p><c> CallbackModule = - atom()</c> - </p> <p><c> DataTag = - atom() - tag used in socket data message.</c></p> - <p><c> ClosedTag = atom() - tag used in - socket close message.</c></p> - - <p><c>verify_type() = verify_none | verify_peer</c></p> - - <p><c>path() = string() - representing a file path.</c></p> + <tag><c>path() =</c></tag> + <item><p><c>string()</c></p> + <p>Represents a file path.</p></item> - <p><c>der_encoded() = binary() -Asn1 DER encoded entity as an erlang binary.</c></p> - - <p><c>host() = hostname() | ipaddress()</c></p> - - <p><c>hostname() = string()</c></p> - - <p><c> - ip_address() = {N1,N2,N3,N4} % IPv4 - | {K1,K2,K3,K4,K5,K6,K7,K8} % IPv6 </c></p> + <tag><c>public_key:der_encoded() =</c></tag> + <item><p><c>binary()</c></p> + <p>ASN.1 DER-encoded entity as an Erlang binary.</p></item> - <p><c>sslsocket() - opaque to the user. </c></p> - - <p><c>protocol() = sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2' </c></p> - - <p><c>ciphers() = [ciphersuite()] | string() (according to old API)</c></p> - - <p><c>ciphersuite() = - {key_exchange(), cipher(), hash()}</c></p> - - <p><c>key_exchange() = rsa | dhe_dss | dhe_rsa | dh_anon - | psk | dhe_psk | rsa_psk | srp_anon | srp_dss | srp_rsa - | ecdh_anon | ecdh_ecdsa | ecdhe_ecdsa | ecdh_rsa | ecdhe_rsa - </c></p> + <tag><c>host() =</c></tag> + <item><p><c>hostname() | ipaddress()</c></p></item> + + <tag><c>hostname() =</c></tag> + <item><p><c>string()</c></p></item> + + <tag><c>ip_address() =</c></tag> + <item><p><c>{N1,N2,N3,N4} % IPv4 | {K1,K2,K3,K4,K5,K6,K7,K8} % IPv6 + </c></p></item> + + <tag><c>sslsocket() =</c></tag> + <item><p>opaque()</p></item> + + <tag><marker id="type-protocol"/><c>protocol() =</c></tag> + <item><p><c>sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'</c></p></item> + + <tag><c>ciphers() =</c></tag> + <item><p><c>= [ciphersuite()] | string()</c></p> + <p>According to old API.</p></item> + + <tag><c>ciphersuite() =</c></tag> - <p><c>cipher() = rc4_128 | des_cbc | '3des_ede_cbc' - | aes_128_cbc | aes_256_cbc </c></p> + <item><p><c>{key_exchange(), cipher(), MAC::hash()} | + {key_exchange(), cipher(), MAC::hash(), PRF::hash()}</c></p></item> - <p> <c>hash() = md5 | sha - </c></p> + <tag><c>key_exchange()=</c></tag> + <item><p><c>rsa | dhe_dss | dhe_rsa | dh_anon | psk | dhe_psk + | rsa_psk | srp_anon | srp_dss | srp_rsa | ecdh_anon | ecdh_ecdsa + | ecdhe_ecdsa | ecdh_rsa | ecdhe_rsa</c></p></item> - <p><c>prf_random() = client_random | server_random - </c></p> + <tag><c>cipher() =</c></tag> + <item><p><c>rc4_128 | des_cbc | '3des_ede_cbc' + | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm</c></p></item> - <p><c>srp_param_type() = srp_1024 | srp_1536 | srp_2048 | srp_3072 - | srp_4096 | srp_6144 | srp_8192</c></p> + <tag><c>hash() =</c></tag> + <item><p><c>md5 | sha | sha224 | sha256 | sha348 | sha512</c></p></item> + <tag><c>prf_random() =</c></tag> + <item><p><c>client_random | server_random</c></p></item> + + <tag><c>srp_param_type() =</c></tag> + <item><p><c>srp_1024 | srp_1536 | srp_2048 | srp_3072 + | srp_4096 | srp_6144 | srp_8192</c></p></item> + + <tag><c>SNIfun::fun()</c></tag> + <item><p><c>= fun(ServerName :: string()) -> [ssl_option()]</c></p></item> + + </taglist> </section> <section> <title>SSL OPTION DESCRIPTIONS - COMMON for SERVER and CLIENT</title> - <p>Options described here are options that are have the same - meaning in the client and the server. - </p> + <p>The following options have the same meaning in the client and + the server:</p> <taglist> - <tag>{cert, der_encoded()}</tag> - <item> The DER encoded users certificate. If this option - is supplied it will override the certfile option.</item> + <tag><c>{cert, public_key:der_encoded()}</c></tag> + <item><p>The DER-encoded users certificate. If this option + is supplied, it overrides option <c>certfile</c>.</p></item> - <tag>{certfile, path()}</tag> - <item>Path to a file containing the user's certificate.</item> + <tag><c>{certfile, path()}</c></tag> + <item><p>Path to a file containing the user certificate.</p></item> - <tag>{key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' |'PrivateKeyInfo', der_encoded()}}</tag> - <item> The DER encoded users private key. If this option - is supplied it will override the keyfile option.</item> + <tag><c>{key, {'RSAPrivateKey'| 'DSAPrivateKey' | 'ECPrivateKey' + |'PrivateKeyInfo', public_key:der_encoded()}}</c></tag> + <item><p>The DER-encoded user's private key. If this option + is supplied, it overrides option <c>keyfile</c>.</p></item> - <tag>{keyfile, path()}</tag> - <item>Path to file containing user's - private PEM encoded key. As PEM-files may contain several - entries this option defaults to the same file as given by - certfile option.</item> - - <tag>{password, string()}</tag> - <item>String containing the user's password. - Only used if the private keyfile is password protected. - </item> - - <tag>{cacerts, [der_encoded()]}</tag> - <item> The DER encoded trusted certificates. If this option - is supplied it will override the cacertfile option.</item> - - <tag>{cacertfile, path()}</tag> - <item>Path to file containing PEM encoded - CA certificates (trusted certificates used for verifying a peer - certificate). May be omitted if you do not want to verify - the peer.</item> - - <tag>{ciphers, ciphers()}</tag> - <item>The cipher suites that should be supported. The function + <tag><c>{keyfile, path()}</c></tag> + <item><p>Path to the file containing the user's + private PEM-encoded key. As PEM-files can contain several + entries, this option defaults to the same file as given by + option <c>certfile</c>.</p></item> + + <tag><c>{password, string()}</c></tag> + <item><p>String containing the user's password. Only used if the + private keyfile is password-protected.</p></item> + + <tag><c>{ciphers, ciphers()}</c></tag> + <item><p>Supported cipher suites. The function <c>cipher_suites/0</c> can be used to find all ciphers that are - supported by default. <c>cipher_suites(all)</c> may be called - to find all available cipher suites. - Pre-Shared Key (<url href="http://www.ietf.org/rfc/rfc4279.txt">RFC 4279</url> and + supported by default. <c>cipher_suites(all)</c> can be called + to find all available cipher suites. Pre-Shared Key + (<url href="http://www.ietf.org/rfc/rfc4279.txt">RFC 4279</url> and <url href="http://www.ietf.org/rfc/rfc5487.txt">RFC 5487</url>), - Secure Remote Password (<url href="http://www.ietf.org/rfc/rfc5054.txt">RFC 5054</url>) + Secure Remote Password + (<url href="http://www.ietf.org/rfc/rfc5054.txt">RFC 5054</url>), RC4 cipher suites, and anonymous cipher suites only work if explicitly enabled by - this option and they are supported/enabled by the peer also. - Note that anonymous cipher suites are supported for testing purposes - only and should not be used when security matters. + this option; they are supported/enabled by the peer also. + Anonymous cipher suites are supported for testing purposes + only and are not be used when security matters.</p></item> + + <tag><c>{secure_renegotiate, boolean()}</c></tag> + <item><p>Specifies if to reject renegotiation attempt that does + not live up to + <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>. + By default <c>secure_renegotiate</c> is set to <c>false</c>, + that is, secure renegotiation is used if possible, + but it falls back to insecure renegotiation if the peer + does not support + <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>.</p> </item> - <tag>{ssl_imp, new | old}</tag> - <item>No longer has any meaning as the old implementation has - been removed, it will be ignored. - </item> + <tag><c>{depth, integer()}</c></tag> + <item><p>Maximum number of non-self-issued + intermediate certificates that can follow the peer certificate + in a valid certification path. So, if depth is 0 the PEER must + be signed by the trusted ROOT-CA directly; if 1 the path can + be PEER, CA, ROOT-CA; if 2 the path can be PEER, CA, CA, + ROOT-CA, and so on. The default value is 1.</p></item> - <tag>{secure_renegotiate, boolean()}</tag> - <item>Specifies if to reject renegotiation attempt that does - not live up to <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>. By default secure_renegotiate is - set to false i.e. secure renegotiation will be used if possible - but it will fallback to unsecure renegotiation if the peer - does not support <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url>. - </item> - - <tag>{depth, integer()}</tag> - <item> - The depth is the maximum number of non-self-issued - intermediate certificates that may follow the peer certificate - in a valid certification path. So if depth is 0 the PEER must - be signed by the trusted ROOT-CA directly, if 1 the path can - be PEER, CA, ROOT-CA, if it is 2 PEER, CA, CA, ROOT-CA and so - on. The default value is 1. - </item> - - <tag>{verify_fun, {Verifyfun :: fun(), InitialUserState :: term()}}</tag> - <item> - <p>The verification fun should be defined as:</p> + <tag><c>{verify_fun, {Verifyfun :: fun(), InitialUserState :: + term()}}</c></tag> + <item><p>The verification fun is to be defined as follows:</p> <code> -fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | +fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom() | {revoked, +atom()}} | {extension, #'Extension'{}}, InitialUserState :: term()) -> {valid, UserState :: term()} | {valid_peer, UserState :: term()} | {fail, Reason :: term()} | {unknown, UserState :: term()}. </code> - <p>The verify fun will be called during the X509-path - validation when an error or an extension unknown to the ssl - application is encountered. Additionally it will be called + <p>The verification fun is called during the X509-path + validation when an error or an extension unknown to the SSL + application is encountered. It is also called when a certificate is considered valid by the path validation to allow access to each certificate in the path to the user - application. Note that it will differentiate between the - peer certificate and CA certificates by using valid_peer or - valid as the second argument to the verify fun. See <seealso - marker="public_key:cert_records">the public_key User's - Guide</seealso> for definition of #'OTPCertificate'{} and - #'Extension'{}.</p> - - <p>If the verify callback fun returns {fail, Reason}, the - verification process is immediately stopped and an alert is - sent to the peer and the TLS/SSL handshake is terminated. If - the verify callback fun returns {valid, UserState}, the - verification process is continued. If the verify callback fun - always returns {valid, UserState}, the TLS/SSL handshake will - not be terminated with respect to verification failures and - the connection will be established. If called with an - extension unknown to the user application the return value - {unknown, UserState} should be used.</p> - - <p>The default verify_fun option in verify_peer mode:</p> + application. It differentiates between the peer + certificate and the CA certificates by using <c>valid_peer</c> or + <c>valid</c> as second argument to the verification fun. See the + <seealso marker="public_key:public_key_records">public_key User's + Guide</seealso> for definition of <c>#'OTPCertificate'{}</c> and + <c>#'Extension'{}</c>.</p> + + <list type="bulleted"> + <item><p>If the verify callback fun returns <c>{fail, Reason}</c>, + the verification process is immediately stopped, an alert is + sent to the peer, and the TLS/SSL handshake terminates.</p></item> + <item><p>If the verify callback fun returns <c>{valid, UserState}</c>, + the verification process continues.</p></item> + <item><p>If the verify callback fun always returns + <c>{valid, UserState}</c>, the TLS/SSL handshake does not + terminate regarding verification failures and the connection is + established.</p></item> + <item><p>If called with an extension unknown to the user application, + return value <c>{unknown, UserState}</c> is to be used.</p> + + <p>Note that if the fun returns <c>unknown</c> for an extension marked + as critical, validation will fail.</p> + </item> + </list> + + <p>Default option <c>verify_fun</c> in <c>verify_peer mode</c>:</p> <code> {fun(_,{bad_cert, _} = Reason, _) -> @@ -272,11 +292,13 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | end, []} </code> - <p>The default verify_fun option in verify_none mode:</p> + <p>Default option <c>verify_fun</c> in mode <c>verify_none</c>:</p> <code> {fun(_,{bad_cert, _}, UserState) -> {valid, UserState}; + (_,{extension, #'Extension'{critical = true}}, UserState) -> + {valid, UserState}; (_,{extension, _}, UserState) -> {unknown, UserState}; (_, valid, UserState) -> @@ -286,29 +308,137 @@ fun(OtpCert :: #'OTPCertificate'{}, Event :: {bad_cert, Reason :: atom()} | end, []} </code> -<p>Possible path validation errors: </p> - -<p> {bad_cert, cert_expired}, {bad_cert, invalid_issuer}, {bad_cert, invalid_signature}, {bad_cert, unknown_ca},{bad_cert, selfsigned_peer}, {bad_cert, name_not_permitted}, {bad_cert, missing_basic_constraint}, {bad_cert, invalid_key_usage}</p> + <p>The possible path validation errors are given on form + <c>{bad_cert, Reason}</c> where <c>Reason</c> is:</p> + + <taglist> + <tag><c>unknown_ca</c></tag> + <item><p>No trusted CA was found in the trusted store. The trusted CA is + normally a so called ROOT CA, which is a self-signed certificate. Trust can + be claimed for an intermediate CA (trusted anchor does not have to be + self-signed according to X-509) by using option <c>partial_chain</c>.</p> + </item> + + <tag><c>selfsigned_peer</c></tag> + <item><p>The chain consisted only of one self-signed certificate.</p></item> + + <tag><c>PKIX X-509-path validation error</c></tag> + <item><p>For possible reasons, see <seealso +marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso> + </p></item> + </taglist> </item> - <tag>{versions, [protocol()]}</tag> - <item>TLS protocol versions that will be supported by started clients and servers. - This option overrides the application environment option <c>protocol_version</c>. If the - environment option is not set it defaults to all versions supported by the SSL application. See also - <seealso marker="ssl:ssl_app">ssl(6)</seealso> + <tag><c>{crl_check, boolean() | peer | best_effort }</c></tag> + <item> + <p>Perform CRL (Certificate Revocation List) verification + <seealso marker="public_key:public_key#pkix_crls_validate-3"> + (public_key:pkix_crls_validate/3)</seealso> on all the certificates during the path validation + <seealso + marker="public_key:public_key#pkix_path_validation-3">(public_key:pkix_path_validation/3) + </seealso> + of the certificate chain. Defaults to <c>false</c>.</p> + + <taglist> + <tag><c>peer</c></tag> + <item>check is only performed on the peer certificate.</item> + + <tag><c>best_effort</c></tag> + <item>if certificate revocation status can not be determined + it will be accepted as valid.</item> + </taglist> + + <p>The CA certificates specified for the connection will be used to + construct the certificate chain validating the CRLs.</p> + + <p>The CRLs will be fetched from a local or external cache. See + <seealso marker="ssl:ssl_crl_cache_api">ssl_crl_cache_api(3)</seealso>.</p> </item> - <tag>{hibernate_after, integer()|undefined}</tag> - <item>When an integer-value is specified, the <c>ssl_connection</c> - will go into hibernation after the specified number of milliseconds - of inactivity, thus reducing its memory footprint. When - <c>undefined</c> is specified (this is the default), the process - will never go into hibernation. + <tag><c>{crl_cache, {Module :: atom(), {DbHandle :: internal | term(), Args :: list()}}}</c></tag> + <item> + <p>Specify how to perform lookup and caching of certificate revocation lists. + <c>Module</c> defaults to <seealso marker="ssl:ssl_crl_cache">ssl_crl_cache</seealso> + with <c> DbHandle </c> being <c>internal</c> and an + empty argument list.</p> + + <p>There are two implementations available:</p> + + <taglist> + <tag><c>ssl_crl_cache</c></tag> + <item> + <p>This module maintains a cache of CRLs. CRLs can be + added to the cache using the function <seealso + marker="ssl:ssl_crl_cache#insert-1">ssl_crl_cache:insert/1</seealso>, + and optionally automatically fetched through HTTP if the + following argument is specified:</p> + + <taglist> + <tag><c>{http, timeout()}</c></tag> + <item><p> + Enables fetching of CRLs specified as http URIs in<seealso + marker="public_key:public_key_records">X509 certificate extensions</seealso>. + Requires the OTP inets application.</p> + </item> + </taglist> + </item> + + <tag><c>ssl_crl_hash_dir</c></tag> + <item> + <p>This module makes use of a directory where CRLs are + stored in files named by the hash of the issuer name.</p> + + <p>The file names consist of eight hexadecimal digits + followed by <c>.rN</c>, where <c>N</c> is an integer, + e.g. <c>1a2b3c4d.r0</c>. For the first version of the + CRL, <c>N</c> starts at zero, and for each new version, + <c>N</c> is incremented by one. The OpenSSL utility + <c>c_rehash</c> creates symlinks according to this + pattern.</p> + + <p>For a given hash value, this module finds all + consecutive <c>.r*</c> files starting from zero, and those + files taken together make up the revocation list. CRL + files whose <c>nextUpdate</c> fields are in the past, or + that are issued by a different CA that happens to have the + same name hash, are excluded.</p> + + <p>The following argument is required:</p> + + <taglist> + <tag><c>{dir, string()}</c></tag> + <item><p>Specifies the directory in which the CRLs can be found.</p></item> + </taglist> + + </item> + </taglist> + </item> - <tag>{user_lookup_fun, {Lookupfun :: fun(), UserState :: term()}}</tag> - <item> - <p>The lookup fun should be defined as:</p> + <tag><c>{partial_chain, fun(Chain::[DerCert]) -> {trusted_ca, DerCert} | + unknown_ca }</c></tag> + <item><p>Claim an intermediate CA in the chain as trusted. TLS then + performs <seealso + marker="public_key:public_key#pkix_path_validation-3">public_key:pkix_path_validation/3</seealso> + with the selected CA as trusted anchor and the rest of the chain.</p></item> + + <tag><c>{versions, [protocol()]}</c></tag> + <item><p>TLS protocol versions supported by started clients and servers. + This option overrides the application environment option + <c>protocol_version</c>. If the environment option is not set, it defaults + to all versions, except SSL-3.0, supported by the SSL application. + See also <seealso marker="ssl:ssl_app">ssl(6).</seealso></p></item> + + <tag><c>{hibernate_after, integer()|undefined}</c></tag> + <item><p>When an integer-value is specified, <c>ssl_connection</c> + goes into hibernation after the specified number of milliseconds + of inactivity, thus reducing its memory footprint. When + <c>undefined</c> is specified (this is the default), the process + never goes into hibernation.</p></item> + + <tag><c>{user_lookup_fun, {Lookupfun :: fun(), UserState :: term()}}</c></tag> + <item><p>The lookup fun is to defined as follows:</p> + <code> fun(psk, PSKIdentity ::string(), UserState :: term()) -> {ok, SharedSecret :: binary()} | error; @@ -316,197 +446,390 @@ fun(srp, Username :: string(), UserState :: term()) -> {ok, {SRPParams :: srp_param_type(), Salt :: binary(), DerivedKey :: binary()}} | error. </code> - <p>For Pre-Shared Key (PSK) cipher suites, the lookup fun will - be called by the client and server to determine the shared - secret. When called by the client, PSKIdentity will be set to the - hint presented by the server or undefined. When called by the - server, PSKIdentity is the identity presented by the client. + <p>For Pre-Shared Key (PSK) cipher suites, the lookup fun is + called by the client and server to determine the shared + secret. When called by the client, <c>PSKIdentity</c> is set to the + hint presented by the server or to undefined. When called by the + server, <c>PSKIdentity</c> is the identity presented by the client.</p> + + <p>For Secure Remote Password (SRP), the fun is only used by the server to + obtain parameters that it uses to generate its session keys. + <c>DerivedKey</c> is to be derived according to + <url href="http://tools.ietf.org/html/rfc2945#section-3"> RFC 2945</url> and + <url href="http://tools.ietf.org/html/rfc5054#section-2.4"> RFC 5054</url>: + <c>crypto:sha([Salt, crypto:sha([Username, <<$:>>, Password])])</c> </p> + </item> - <p>For Secure Remote Password (SRP), the fun will only be used by the server to obtain - parameters that it will use to generate its session keys. <c>DerivedKey</c> should be - derived according to <url href="http://tools.ietf.org/html/rfc2945#section-3"> RFC 2945</url> and - <url href="http://tools.ietf.org/html/rfc5054#section-2.4"> RFC 5054</url>: - <c>crypto:sha([Salt, crypto:sha([Username, <<$:>>, Password])]) </c> - </p> + <tag><c>{padding_check, boolean()}</c></tag> + <item><p>Affects TLS-1.0 connections only. + If set to <c>false</c>, it disables the block cipher padding check + to be able to interoperate with legacy software.</p> + <warning><p>Using <c>{padding_check, boolean()}</c> makes TLS + vulnerable to the Poodle attack.</p></warning> </item> - </taglist> + - </section> + <tag><c>{beast_mitigation, one_n_minus_one | zero_n | disabled}</c></tag> + <item><p>Affects SSL-3.0 and TLS-1.0 connections only. Used to change the BEAST + mitigation strategy to interoperate with legacy software. + Defaults to <c>one_n_minus_one</c>.</p> + + <p><c>one_n_minus_one</c> - Perform 1/n-1 BEAST mitigation.</p> + + <p><c>zero_n</c> - Perform 0/n BEAST mitigation.</p> + + <p><c>disabled</c> - Disable BEAST mitigation.</p> + + <warning><p>Using <c>{beast_mitigation, disabled}</c> makes SSL or TLS + vulnerable to the BEAST attack.</p></warning> + </item> + </taglist> - <section> + </section> + + <section> <title>SSL OPTION DESCRIPTIONS - CLIENT SIDE</title> - <p>Options described here are client specific or has a slightly different - meaning in the client than in the server.</p> + <p>The following options are client-specific or have a slightly different + meaning in the client than in the server:</p> <taglist> - <tag>{verify, verify_type()}</tag> - <item> In verify_none mode the default behavior will be to - allow all x509-path validation errors. See also the verify_fun - option. + + <tag><c>{verify, verify_type()}</c></tag> + <item><p>In mode <c>verify_none</c> the default behavior is to allow + all x509-path validation errors. See also option <c>verify_fun</c>.</p> </item> - <tag>{reuse_sessions, boolean()}</tag> - <item>Specifies if client should try to reuse sessions - when possible. + + <tag><c>{reuse_sessions, boolean()}</c></tag> + <item><p>Specifies if the client is to try to reuse sessions + when possible.</p></item> + + <tag><c>{cacerts, [public_key:der_encoded()]}</c></tag> + <item><p>The DER-encoded trusted certificates. If this option + is supplied it overrides option <c>cacertfile</c>.</p></item> + + <tag><c>{cacertfile, path()}</c></tag> + <item><p>Path to a file containing PEM-encoded CA certificates. The CA + certificates are used during server authentication and when building the + client certificate chain.</p> + </item> + + <tag><c>{alpn_advertised_protocols, [binary()]}</c></tag> + <item> + <p>The list of protocols supported by the client to be sent to the + server to be used for an Application-Layer Protocol Negotiation (ALPN). + If the server supports ALPN then it will choose a protocol from this + list; otherwise it will fail the connection with a "no_application_protocol" + alert. A server that does not support ALPN will ignore this value.</p> + + <p>The list of protocols must not contain an empty binary.</p> + + <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p> </item> - <tag>{client_preferred_next_protocols, Precedence :: server | client, ClientPrefs :: [binary()]}</tag> - <tag>{client_preferred_next_protocols, Precedence :: server | client, ClientPrefs :: [binary()], Default :: binary()}</tag> + <tag><c>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()]}}</c><br/> + <c>{client_preferred_next_protocols, {Precedence :: server | client, ClientPrefs :: [binary()], Default :: binary()}}</c></tag> <item> - <p>Indicates the client will try to perform Next Protocol + <p>Indicates that the client is to try to perform Next Protocol Negotiation.</p> - <p>If precedence is server the negotiated protocol will be the - first protocol that appears on the server advertised list that is + <p>If precedence is server, the negotiated protocol is the + first protocol to be shown on the server advertised list, which is also on the client preference list.</p> - <p>If precedence is client the negotiated protocol will be the - first protocol that appears on the client preference list that is + <p>If precedence is client, the negotiated protocol is the + first protocol to be shown on the client preference list, which is also on the server advertised list.</p> <p>If the client does not support any of the server advertised - protocols or the server does not advertise any protocols the - client will fallback to the first protocol in its list or if a - default is supplied it will fallback to that instead. If the - server does not support Next Protocol Negotiation the - connection will be aborted if no default protocol is supplied.</p> + protocols or the server does not advertise any protocols, the + client falls back to the first protocol in its list or to the + default protocol (if a default is supplied). If the + server does not support Next Protocol Negotiation, the + connection terminates if no default protocol is supplied.</p> </item> - <tag>{psk_identity, string()}</tag> - <item>Specifies the identity the client presents to the server. The matching secret is - found by calling the user_look_fun. + <tag><c>{psk_identity, string()}</c></tag> + <item><p>Specifies the identity the client presents to the server. + The matching secret is found by calling <c>user_lookup_fun</c>.</p> </item> - <tag>{srp_identity, {Username :: string(), Password :: string()}</tag> - <item>Specifies the Username and Password to use to authenticate to the server. + + <tag><c>{srp_identity, {Username :: string(), Password :: string()} + </c></tag> + <item><p>Specifies the username and password to use to authenticate + to the server.</p></item> + + <tag><c>{server_name_indication, hostname()}</c></tag> + <item><p>Can be specified when upgrading a TCP socket to a TLS + socket to use the TLS Server Name Indication extension.</p></item> + + <tag><c>{server_name_indication, disable}</c></tag> + <item> + <p>When starting a TLS connection without upgrade, the Server Name + Indication extension is sent if possible. This option can be + used to disable that behavior.</p> </item> - </taglist> + <tag><c>{fallback, boolean()}</c></tag> + <item> + <p> Send special cipher suite TLS_FALLBACK_SCSV to avoid undesired TLS version downgrade. + Defaults to false</p> + <warning><p>Note this option is not needed in normal TLS usage and should not be used + to implement new clients. But legacy clients that retries connections in the following manner</p> + + <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv2', 'tlsv1.1', 'tlsv1', 'sslv3']}])</c></p> + <p><c> ssl:connect(Host, Port, [...{versions, [tlsv1.1', 'tlsv1', 'sslv3']}, {fallback, true}])</c></p> + <p><c> ssl:connect(Host, Port, [...{versions, ['tlsv1', 'sslv3']}, {fallback, true}]) </c></p> + <p><c> ssl:connect(Host, Port, [...{versions, ['sslv3']}, {fallback, true}]) </c></p> + + <p>may use it to avoid undesired TLS version downgrade. Note that TLS_FALLBACK_SCSV must also + be supported by the server for the prevention to work. + </p></warning> + </item> + <tag><marker id="client_signature_algs"/><c>{signature_algs, [{hash(), ecdsa | rsa | dsa}]}</c></tag> + <item> + <p>In addition to the algorithms negotiated by the cipher + suite used for key exchange, payload encryption, message + authentication and pseudo random calculation, the TLS signature + algorithm extension <url + href="http://www.ietf.org/rfc/rfc5246.txt">Section 7.4.1.4.1 in RFC 5246</url> may be + used, from TLS 1.2, to negotiate which signature algorithm to use during the + TLS handshake. If no lower TLS versions than 1.2 are supported, + the client will send a TLS signature algorithm extension + with the algorithms specified by this option. + Defaults to</p> + + <code>[ +%% SHA2 +{sha512, ecdsa}, +{sha512, rsa}, +{sha384, ecdsa}, +{sha384, rsa}, +{sha256, ecdsa}, +{sha256, rsa}, +{sha224, ecdsa}, +{sha224, rsa}, +%% SHA +{sha, ecdsa}, +{sha, rsa}, +{sha, dsa}, +]</code> +<p> + The algorithms should be in the preferred order. + Selected signature algorithm can restrict which hash functions + that may be selected. Default support for {md5, rsa} removed in ssl-8.0 + </p> + </item> + </taglist> </section> - + <section> <title>SSL OPTION DESCRIPTIONS - SERVER SIDE</title> - <p>Options described here are server specific or has a slightly different - meaning in the server than in the client.</p> + <p>The following options are server-specific or have a slightly different + meaning in the server than in the client:</p> <taglist> - <tag>{dh, der_encoded()}</tag> - <item>The DER encoded Diffie Hellman parameters. If this option - is supplied it will override the dhfile option. - </item> - - <tag>{dhfile, path()}</tag> - <item>Path to file containing PEM encoded Diffie Hellman parameters, - for the server to use if a cipher suite using Diffie Hellman key exchange - is negotiated. If not specified default parameters will be used. + <tag><c>{cacerts, [public_key:der_encoded()]}</c></tag> + <item><p>The DER-encoded trusted certificates. If this option + is supplied it overrides option <c>cacertfile</c>.</p></item> + + <tag><c>{cacertfile, path()}</c></tag> + <item><p>Path to a file containing PEM-encoded CA + certificates. The CA certificates are used to build the server + certificate chain and for client authentication. The CAs are + also used in the list of acceptable client CAs passed to the + client when a certificate is requested. Can be omitted if there + is no need to verify the client and if there are no + intermediate CAs for the server certificate.</p></item> + + <tag><c>{dh, public_key:der_encoded()}</c></tag> + <item><p>The DER-encoded Diffie-Hellman parameters. If specified, + it overrides option <c>dhfile</c>.</p></item> + + <tag><c>{dhfile, path()}</c></tag> + <item><p>Path to a file containing PEM-encoded Diffie Hellman parameters + to be used by the server if a cipher suite using Diffie Hellman key + exchange is negotiated. If not specified, default parameters are used. + </p></item> + + <tag><c>{verify, verify_type()}</c></tag> + <item><p>A server only does x509-path validation in mode <c>verify_peer</c>, + as it then sends a certificate request to the client + (this message is not sent if the verify option is <c>verify_none</c>). + You can then also want to specify option <c>fail_if_no_peer_cert</c>. + </p></item> + + <tag><c>{fail_if_no_peer_cert, boolean()}</c></tag> + <item><p>Used together with <c>{verify, verify_peer}</c> by an SSL server. + If set to <c>true</c>, the server fails if the client does not have + a certificate to send, that is, sends an empty certificate. If set to + <c>false</c>, it fails only if the client sends an invalid + certificate (an empty certificate is considered valid). Defaults to false.</p> </item> - <tag>{verify, verify_type()}</tag> - <item>Servers only do the x509-path validation in verify_peer - mode, as it then will send a certificate request to the client - (this message is not sent if the verify option is verify_none) - and you may then also want to specify the option - fail_if_no_peer_cert. - </item> + <tag><c>{reuse_sessions, boolean()}</c></tag> + <item><p>Specifies if the server is to agree to reuse sessions + when requested by the clients. See also option <c>reuse_session</c>. + </p></item> + + <tag><c>{reuse_session, fun(SuggestedSessionId, + PeerCert, Compression, CipherSuite) -> boolean()}</c></tag> + <item><p>Enables the SSL server to have a local policy + for deciding if a session is to be reused or not. + Meaningful only if <c>reuse_sessions</c> is set to <c>true</c>. + <c>SuggestedSessionId</c> is a <c>binary()</c>, <c>PeerCert</c> is + a DER-encoded certificate, <c>Compression</c> is an enumeration integer, + and <c>CipherSuite</c> is of type <c>ciphersuite()</c>.</p></item> + + <tag><c>{alpn_preferred_protocols, [binary()]}</c></tag> + <item> + <p>Indicates the server will try to perform Application-Layer + Protocol Negotiation (ALPN).</p> - <tag>{fail_if_no_peer_cert, boolean()}</tag> - <item>Used together with {verify, verify_peer} by an ssl server. - If set to true, the server will fail if the client does not have - a certificate to send, i.e. sends a empty certificate, if set to - false it will only fail if the client sends an invalid - certificate (an empty certificate is considered valid). - </item> + <p>The list of protocols is in order of preference. The protocol + negotiated will be the first in the list that matches one of the + protocols advertised by the client. If no protocol matches, the + server will fail the connection with a "no_application_protocol" alert.</p> - <tag>{reuse_sessions, boolean()}</tag> - <item>Specifies if the server should agree to reuse sessions - when the clients request to do so. See also the reuse_session - option. + <p>The negotiated protocol can be retrieved using the <c>negotiated_protocol/1</c> function.</p> </item> - <tag>{reuse_session, fun(SuggestedSessionId, - PeerCert, Compression, CipherSuite) -> boolean()}</tag> - <item>Enables the ssl server to have a local policy - for deciding if a session should be reused or not, - only meaningful if <c>reuse_sessions</c> is set to true. - SuggestedSessionId is a binary(), PeerCert is a DER encoded - certificate, Compression is an enumeration integer - and CipherSuite is of type ciphersuite(). - </item> - - <tag>{next_protocols_advertised, Protocols :: [binary()]}</tag> - <item>The list of protocols to send to the client if the client indicates - it supports the Next Protocol extension. The client may select a protocol + <tag><c>{next_protocols_advertised, Protocols :: [binary()]}</c></tag> + <item><p>List of protocols to send to the client if the client indicates that + it supports the Next Protocol extension. The client can select a protocol that is not on this list. The list of protocols must not contain an empty - binary. If the server negotiates a Next Protocol it can be accessed - using <c>negotiated_next_protocol/1</c> method. + binary. If the server negotiates a Next Protocol, it can be accessed + using the <c>negotiated_next_protocol/1</c> method.</p></item> + + <tag><c>{psk_identity, string()}</c></tag> + <item><p>Specifies the server identity hint, which the server presents to + the client.</p></item> + + <tag><c>{log_alert, boolean()}</c></tag> + <item><p>If set to <c>false</c>, error reports are not displayed.</p></item> + + <tag><c>{honor_cipher_order, boolean()}</c></tag> + <item><p>If set to <c>true</c>, use the server preference for cipher + selection. If set to <c>false</c> (the default), use the client + preference.</p></item> + + <tag><c>{sni_hosts, [{hostname(), [ssl_option()]}]}</c></tag> + <item><p>If the server receives a SNI (Server Name Indication) from the client + matching a host listed in the <c>sni_hosts</c> option, the specific options for + that host will override previously specified options. + + The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p></item> + + <tag><c>{sni_fun, SNIfun::fun()}</c></tag> + <item><p>If the server receives a SNI (Server Name Indication) from the client, + the given function will be called to retrieve <c>[ssl_option()]</c> for the indicated server. + These options will be merged into predefined <c>[ssl_option()]</c>. + + The function should be defined as: + <c>fun(ServerName :: string()) -> [ssl_option()]</c> + and can be specified as a fun or as named <c>fun module:function/1</c> + + The option <c>sni_fun</c>, and <c>sni_hosts</c> are mutually exclusive.</p></item> + + <tag><c>{client_renegotiation, boolean()}</c></tag> + <item>In protocols that support client-initiated renegotiation, the cost + of resources of such an operation is higher for the server than the + client. This can act as a vector for denial of service attacks. The SSL + application already takes measures to counter-act such attempts, + but client-initiated renegotiation can be strictly disabled by setting + this option to <c>false</c>. The default value is <c>true</c>. + Note that disabling renegotiation can result in long-lived connections + becoming unusable due to limits on the number of messages the underlying + cipher suite can encipher. </item> - <tag>{psk_identity, string()}</tag> - <item>Specifies the server identity hint the server presents to the client. + <tag><c>{honor_cipher_order, boolean()}</c></tag> + <item>If true, use the server's preference for cipher selection. If false + (the default), use the client's preference. </item> - + + <tag><c>{signature_algs, [{hash(), ecdsa | rsa | dsa}]}</c></tag> + <item><p> The algorithms specified by + this option will be the ones accepted by the server in a signature algorithm + negotiation, introduced in TLS-1.2. The algorithms will also be offered to the client if a + client certificate is requested. For more details see the <seealso marker="#client_signature_algs">corresponding client option</seealso>. + </p> </item> + + <tag><c>{v2_hello_compatible, boolean()}</c></tag> + <item>If true, the server accepts clients that send hello messages on SSL-2.0 format but offers + supported SSL/TLS versions. Defaults to false, that is the server will not interoperate with clients that + offers SSL-2.0. + </item> + </taglist> </section> <section> <title>General</title> - <p>When an ssl socket is in active mode (the default), data from the + <p>When an SSL socket is in active mode (the default), data from the socket is delivered to the owner of the socket in the form of - messages: - </p> + messages:</p> + <list type="bulleted"> - <item>{ssl, Socket, Data} - </item> - <item>{ssl_closed, Socket} - </item> - <item> - {ssl_error, Socket, Reason} - </item> + <item><p><c>{ssl, Socket, Data}</c></p></item> + <item><p><c>{ssl_closed, Socket}</c></p></item> + <item><p><c>{ssl_error, Socket, Reason}</c></p></item> </list> - - <p>A <c>Timeout</c> argument specifies a timeout in milliseconds. The - default value for a <c>Timeout</c> argument is <c>infinity</c>. - </p> + + <p>A <c>Timeout</c> argument specifies a time-out in milliseconds. The + default value for argument <c>Timeout</c> is <c>infinity</c>.</p> </section> <funcs> <func> <name>cipher_suites() -></name> <name>cipher_suites(Type) -> ciphers()</name> - <fsummary> Returns a list of supported cipher suites</fsummary> + <fsummary>Returns a list of supported cipher suites.</fsummary> <type> <v>Type = erlang | openssl | all</v> - </type> <desc><p>Returns a list of supported cipher suites. - cipher_suites() is equivalent to cipher_suites(erlang). - Type openssl is provided for backwards compatibility with - old ssl that used openssl. cipher_suites(all) returns + <c>cipher_suites()</c> is equivalent to <c>cipher_suites(erlang).</c> + Type <c>openssl</c> is provided for backwards compatibility with the + old SSL, which used OpenSSL. <c>cipher_suites(all)</c> returns all available cipher suites. The cipher suites not present - in cipher_suites(erlang) but in included in cipher_suites(all) - will not be used unless explicitly configured by the user. - </p> + in <c>cipher_suites(erlang)</c> but included in + <c>cipher_suites(all)</c> are not used unless explicitly configured + by the user.</p> </desc> </func> - + + <func> + <name>clear_pem_cache() -> ok </name> + <fsummary> Clears the pem cache</fsummary> + + <desc><p>PEM files, used by ssl API-functions, are cached. The + cache is regularly checked to see if any cache entries should be + invalidated, however this function provides a way to + unconditionally clear the whole cache. + </p> + </desc> + </func> + <func> <name>connect(Socket, SslOptions) -> </name> <name>connect(Socket, SslOptions, Timeout) -> {ok, SslSocket} | {error, Reason}</name> - <fsummary> Upgrades a gen_tcp, or - equivalent, connected socket to an ssl socket. </fsummary> + <fsummary>Upgrades a <c>gen_tcp</c>, or + equivalent, connected socket to an SSL socket.</fsummary> <type> - <v>Socket = socket()</v> - <v>SslOptions = [ssloption()]</v> + <v>Socket = socket()</v> + <v>SslOptions = [ssl_option()]</v> <v>Timeout = integer() | infinity</v> <v>SslSocket = sslsocket()</v> <v>Reason = term()</v> </type> - <desc> <p>Upgrades a gen_tcp, or equivalent, - connected socket to an ssl socket i.e. performs the + <desc><p>Upgrades a <c>gen_tcp</c>, or equivalent, + connected socket to an SSL socket, that is, performs the client-side ssl handshake.</p> </desc> </func> @@ -515,7 +838,7 @@ fun(srp, Username :: string(), UserState :: term()) -> <name>connect(Host, Port, Options) -></name> <name>connect(Host, Port, Options, Timeout) -> {ok, SslSocket} | {error, Reason}</name> - <fsummary>Opens an ssl connection to Host, Port. </fsummary> + <fsummary>Opens an SSL connection to <c>Host</c>, <c>Port</c>.</fsummary> <type> <v>Host = host()</v> <v>Port = integer()</v> @@ -524,107 +847,174 @@ fun(srp, Username :: string(), UserState :: term()) -> <v>SslSocket = sslsocket()</v> <v>Reason = term()</v> </type> - <desc> <p>Opens an ssl connection to Host, Port.</p> </desc> + <desc><p>Opens an SSL connection to <c>Host</c>, <c>Port</c>.</p></desc> </func> <func> <name>close(SslSocket) -> ok | {error, Reason}</name> - <fsummary>Close an ssl connection</fsummary> + <fsummary>Closes an SSL connection.</fsummary> <type> <v>SslSocket = sslsocket()</v> <v>Reason = term()</v> </type> - <desc><p>Close an ssl connection.</p> + <desc><p>Closes an SSL connection.</p> </desc> </func> <func> + <name>close(SslSocket, How) -> ok | {ok, port()} | {error, Reason}</name> + <fsummary>Closes an SSL connection.</fsummary> + <type> + <v>SslSocket = sslsocket()</v> + <v>How = timeout() | {NewController::pid(), timeout()} </v> + <v>Reason = term()</v> + </type> + <desc><p>Closes or downgrades an SSL connection. In the latter case the transport + connection will be handed over to the <c>NewController</c> process after receiving + the TLS close alert from the peer. The returned transport socket will have + the following options set: <c>[{active, false}, {packet, 0}, {mode, binary}]</c></p> + </desc> + </func> + + <func> <name>controlling_process(SslSocket, NewOwner) -> ok | {error, Reason}</name> - <fsummary>Assigns a new controlling process to the - ssl-socket.</fsummary> - + SSL socket.</fsummary> <type> <v>SslSocket = sslsocket()</v> <v>NewOwner = pid()</v> <v>Reason = term()</v> </type> - <desc><p>Assigns a new controlling process to the ssl-socket. A - controlling process is the owner of an ssl-socket, and receives - all messages from the socket.</p> + <desc><p>Assigns a new controlling process to the SSL socket. A + controlling process is the owner of an SSL socket, and receives + all messages from the socket.</p> + </desc> + </func> + + <func> + <name>connection_information(SslSocket) -> + {ok, Result} | {error, Reason} </name> + <fsummary>Returns all the connection information. + </fsummary> + <type> + <v>Item = protocol | cipher_suite | sni_hostname | atom()</v> + <d>Meaningful atoms, not specified above, are the ssl option names.</d> + <v>Result = [{Item::atom(), Value::term()}]</v> + <v>Reason = term()</v> + </type> + <desc><p>Returns all relevant information about the connection, ssl options that + are undefined will be filtered out.</p> </desc> </func> <func> - <name>connection_info(SslSocket) -> - {ok, {ProtocolVersion, CipherSuite}} | {error, Reason} </name> - <fsummary>Returns the negotiated protocol version and cipher suite. + <name>connection_information(SslSocket, Items) -> + {ok, Result} | {error, Reason} </name> + <fsummary>Returns the requested connection information. </fsummary> <type> - <v>CipherSuite = ciphersuite()</v> - <v>ProtocolVersion = protocol()</v> + <v>Items = [Item]</v> + <v>Item = protocol | cipher_suite | sni_hostname | atom()</v> + <d>Meaningful atoms, not specified above, are the ssl option names.</d> + <v>Result = [{Item::atom(), Value::term()}]</v> + <v>Reason = term()</v> </type> - <desc><p>Returns the negotiated protocol version and cipher suite.</p> + <desc><p>Returns the requested information items about the connection, + if they are defined.</p> + <note><p>If only undefined options are requested the + resulting list can be empty.</p></note> </desc> </func> - <func> + <func> <name>format_error(Reason) -> string()</name> - <fsummary>Return an error string.</fsummary> + <fsummary>Returns an error string.</fsummary> <type> <v>Reason = term()</v> </type> <desc> - <p>Presents the error returned by an ssl function as a printable string.</p> + <p>Presents the error returned by an SSL function as a printable string.</p> </desc> </func> <func> <name>getopts(Socket, OptionNames) -> {ok, [socketoption()]} | {error, Reason}</name> - <fsummary>Get the value of the specified options.</fsummary> + <fsummary>Gets the values of the specified options.</fsummary> <type> <v>Socket = sslsocket()</v> <v>OptionNames = [atom()]</v> </type> <desc> - <p>Get the value of the specified socket options. + <p>Gets the values of the specified socket options. </p> </desc> </func> <func> + <name>getstat(Socket) -> + {ok, OptionValues} | {error, inet:posix()}</name> + <name>getstat(Socket, OptionNames) -> + {ok, OptionValues} | {error, inet:posix()}</name> + <fsummary>Get one or more statistic options for a socket</fsummary> + <type> + <v>Socket = sslsocket()</v> + <v>OptionNames = [atom()]</v> + <v>OptionValues = [{inet:stat_option(), integer()}]</v> + </type> + <desc> + <p>Gets one or more statistic options for the underlying TCP socket.</p> + <p>See inet:getstat/2 for statistic options description.</p> + </desc> + </func> + + <func> <name>listen(Port, Options) -> {ok, ListenSocket} | {error, Reason}</name> - <fsummary>Creates an ssl listen socket.</fsummary> + <fsummary>Creates an SSL listen socket.</fsummary> <type> <v>Port = integer()</v> <v>Options = options()</v> <v>ListenSocket = sslsocket()</v> </type> <desc> - <p>Creates an ssl listen socket.</p> + <p>Creates an SSL listen socket.</p> </desc> </func> <func> + <name>negotiated_protocol(Socket) -> {ok, Protocol} | {error, protocol_not_negotiated}</name> + <fsummary>Returns the protocol negotiated through ALPN or NPN extensions.</fsummary> + <type> + <v>Socket = sslsocket()</v> + <v>Protocol = binary()</v> + </type> + <desc> + <p> + Returns the protocol negotiated through ALPN or NPN extensions. + </p> + </desc> + </func> + + <func> <name>peercert(Socket) -> {ok, Cert} | {error, Reason}</name> - <fsummary>Return the peer certificate.</fsummary> + <fsummary>Returns the peer certificate.</fsummary> <type> <v>Socket = sslsocket()</v> <v>Cert = binary()</v> </type> <desc> - <p>The peer certificate is returned as a DER encoded binary. - The certificate can be decoded with <c>public_key:pkix_decode_cert/2</c>. - </p> + <p>The peer certificate is returned as a DER-encoded binary. + The certificate can be decoded with + <c>public_key:pkix_decode_cert/2</c>.</p> </desc> </func> + <func> <name>peername(Socket) -> {ok, {Address, Port}} | {error, Reason}</name> - <fsummary>Return peer address and port.</fsummary> + <fsummary>Returns the peer address and port.</fsummary> <type> <v>Socket = sslsocket()</v> <v>Address = ipaddress()</v> @@ -634,12 +1024,32 @@ fun(srp, Username :: string(), UserState :: term()) -> <p>Returns the address and port number of the peer.</p> </desc> </func> + + <func> + <name>prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name> + <fsummary>Uses a session Pseudo-Random Function to generate key material.</fsummary> + <type> + <v>Socket = sslsocket()</v> + <v>Secret = binary() | master_secret</v> + <v>Label = binary()</v> + <v>Seed = [binary() | prf_random()]</v> + <v>WantedLength = non_neg_integer()</v> + </type> + <desc> + <p>Uses the Pseudo-Random Function (PRF) of a TLS session to generate + extra key material. It either takes user-generated values for + <c>Secret</c> and <c>Seed</c> or atoms directing it to use a specific + value from the session security parameters.</p> + <p>Can only be used with TLS connections; <c>{error, undefined}</c> + is returned for SSLv3 connections.</p> + </desc> + </func> <func> <name>recv(Socket, Length) -> </name> <name>recv(Socket, Length, Timeout) -> {ok, Data} | {error, Reason}</name> - <fsummary>Receive data on a socket.</fsummary> + <fsummary>Receives data on a socket.</fsummary> <type> <v>Socket = sslsocket()</v> <v>Length = integer()</v> @@ -647,63 +1057,43 @@ fun(srp, Username :: string(), UserState :: term()) -> <v>Data = [char()] | binary()</v> </type> <desc> - <p>This function receives a packet from a socket in passive - mode. A closed socket is indicated by a return value + <p>Receives a packet from a socket in passive + mode. A closed socket is indicated by return value <c>{error, closed}</c>.</p> - <p>The <c>Length</c> argument is only meaningful when - the socket is in <c>raw</c> mode and denotes the number of + <p>Argument <c>Length</c> is meaningful only when + the socket is in mode <c>raw</c> and denotes the number of bytes to read. If <c>Length</c> = 0, all available bytes are returned. If <c>Length</c> > 0, exactly <c>Length</c> bytes are returned, or an error; possibly discarding less than <c>Length</c> bytes of data when the socket gets closed from the other side.</p> - <p>The optional <c>Timeout</c> parameter specifies a timeout in + <p>Optional argument <c>Timeout</c> specifies a time-out in milliseconds. The default value is <c>infinity</c>.</p> </desc> </func> <func> - <name>prf(Socket, Secret, Label, Seed, WantedLength) -> {ok, binary()} | {error, reason()}</name> - <fsummary>Use a sessions pseudo random function to generate key material.</fsummary> - <type> - <v>Socket = sslsocket()</v> - <v>Secret = binary() | master_secret</v> - <v>Label = binary()</v> - <v>Seed = [binary() | prf_random()]</v> - <v>WantedLength = non_neg_integer()</v> - </type> - <desc> - <p>Use the pseudo random function (PRF) of a TLS session to generate - additional key material. It either takes user generated values for - <c>Secret</c> and <c>Seed</c> or atoms directing it use a specific - value from the session security parameters.</p> - <p>This function can only be used with TLS connections, <c>{error, undefined}</c> - is returned for SSLv3 connections.</p> - </desc> - </func> - - <func> <name>renegotiate(Socket) -> ok | {error, Reason}</name> - <fsummary> Initiates a new handshake.</fsummary> + <fsummary>Initiates a new handshake.</fsummary> <type> <v>Socket = sslsocket()</v> </type> <desc><p>Initiates a new handshake. A notable return value is <c>{error, renegotiation_rejected}</c> indicating that the peer - refused to go through with the renegotiation but the connection + refused to go through with the renegotiation, but the connection is still active using the previously negotiated session.</p> </desc> </func> <func> <name>send(Socket, Data) -> ok | {error, Reason}</name> - <fsummary>Write data to a socket.</fsummary> + <fsummary>Writes data to a socket.</fsummary> <type> <v>Socket = sslsocket()</v> <v>Data = iodata()</v> </type> <desc> - <p>Writes <c>Data</c> to <c>Socket</c>. </p> + <p>Writes <c>Data</c> to <c>Socket</c>.</p> <p>A notable return value is <c>{error, closed}</c> indicating that the socket is closed.</p> </desc> @@ -711,83 +1101,91 @@ fun(srp, Username :: string(), UserState :: term()) -> <func> <name>setopts(Socket, Options) -> ok | {error, Reason}</name> - <fsummary>Set socket options.</fsummary> + <fsummary>Sets socket options.</fsummary> <type> <v>Socket = sslsocket()</v> <v>Options = [socketoption]()</v> </type> <desc> - <p>Sets options according to <c>Options</c> for the socket - <c>Socket</c>. </p> + <p>Sets options according to <c>Options</c> for socket + <c>Socket</c>.</p> </desc> </func> <func> <name>shutdown(Socket, How) -> ok | {error, Reason}</name> - <fsummary>Immediately close a socket</fsummary> + <fsummary>Immediately closes a socket.</fsummary> <type> <v>Socket = sslsocket()</v> <v>How = read | write | read_write</v> <v>Reason = reason()</v> </type> <desc> - <p>Immediately close a socket in one or two directions.</p> + <p>Immediately closes a socket in one or two directions.</p> <p><c>How == write</c> means closing the socket for writing, reading from it is still possible.</p> <p>To be able to handle that the peer has done a shutdown on - the write side, the <c>{exit_on_close, false}</c> option + the write side, option <c>{exit_on_close, false}</c> is useful.</p> </desc> </func> <func> - <name>ssl_accept(ListenSocket) -> </name> - <name>ssl_accept(ListenSocket, Timeout) -> ok | {error, Reason}</name> - <fsummary>Perform server-side SSL handshake</fsummary> + <name>ssl_accept(Socket) -> </name> + <name>ssl_accept(Socket, Timeout) -> ok | {error, Reason}</name> + <fsummary>Performs server-side SSL/TLS handshake.</fsummary> <type> - <v>ListenSocket = sslsocket()</v> + <v>Socket = sslsocket()</v> <v>Timeout = integer()</v> <v>Reason = term()</v> </type> <desc> - <p>The <c>ssl_accept</c> function establish the SSL connection - on the server side. It should be called directly after - <c>transport_accept</c>, in the spawned server-loop.</p> + <p>Performs the SSL/TLS server-side handshake.</p> + <p><c>Socket</c> is a socket as returned by + <seealso marker="#transport_accept-2">ssl:transport_accept/[1,2]</seealso> + </p> </desc> </func> <func> - <name>ssl_accept(ListenSocket, SslOptions) -> </name> - <name>ssl_accept(ListenSocket, SslOptions, Timeout) -> {ok, Socket} | {error, Reason}</name> - <fsummary>Perform server-side SSL handshake</fsummary> + <name>ssl_accept(Socket, SslOptions) -> </name> + <name>ssl_accept(Socket, SslOptions, Timeout) -> {ok, Socket} | ok | {error, Reason}</name> + <fsummary>Performs server-side SSL/TLS handshake.</fsummary> <type> - <v>ListenSocket = socket()</v> - <v>SslOptions = ssloptions()</v> + <v>Socket = socket() | sslsocket() </v> + <v>SslOptions = [ssl_option()]</v> <v>Timeout = integer()</v> <v>Reason = term()</v> </type> <desc> - <p> Upgrades a gen_tcp, or - equivalent, socket to an ssl socket i.e. performs the - ssl server-side handshake.</p> - <warning><p>Note that the listen socket should be in {active, false} mode + <p>If <c>Socket</c> is a <c>socket()</c>: upgrades a <c>gen_tcp</c>, + or equivalent, socket to an SSL socket, that is, performs + the SSL/TLS server-side handshake and returns the SSL socket.</p> + + <warning><p>The listen socket is to be in mode <c>{active, false}</c> before telling the client that the server is ready to upgrade - and calling this function, otherwise the upgrade may - or may not succeed depending on timing.</p></warning> + by calling this function, else the upgrade succeeds or does not + succeed depending on timing.</p></warning> + + <p>If <c>Socket</c> is an <c>sslsocket()</c>: provides extra SSL/TLS + options to those specified in + <seealso marker="#listen-2">ssl:listen/2 </seealso> and then performs + the SSL/TLS handshake. + </p> </desc> </func> <func> <name>sockname(Socket) -> {ok, {Address, Port}} | {error, Reason}</name> - <fsummary>Return the local address and port.</fsummary> + <fsummary>Returns the local address and port.</fsummary> <type> <v>Socket = sslsocket()</v> <v>Address = ipaddress()</v> <v>Port = integer()</v> </type> <desc> - <p>Returns the local address and port number of the socket + <p>Returns the local address and port number of socket <c>Socket</c>.</p> </desc> </func> @@ -795,94 +1193,98 @@ fun(srp, Username :: string(), UserState :: term()) -> <func> <name>start() -> </name> <name>start(Type) -> ok | {error, Reason}</name> - <fsummary>Starts the Ssl application. </fsummary> + <fsummary>Starts the SSL application.</fsummary> <type> - <v>Type = permanent | transient | temporary</v> + <v>Type = permanent | transient | temporary</v> </type> <desc> - <p>Starts the Ssl application. Default type - is temporary. - <seealso marker="kernel:application">application(3)</seealso></p> + <p>Starts the SSL application. Default type + is <c>temporary</c>.</p> </desc> </func> + <func> <name>stop() -> ok </name> - <fsummary>Stops the Ssl application.</fsummary> + <fsummary>Stops the SSL application.</fsummary> <desc> - <p>Stops the Ssl application. - <seealso marker="kernel:application">application(3)</seealso></p> + <p>Stops the SSL application.</p> </desc> </func> <func> - <name>transport_accept(Socket) -></name> - <name>transport_accept(Socket, Timeout) -> + <name>transport_accept(ListenSocket) -></name> + <name>transport_accept(ListenSocket, Timeout) -> {ok, NewSocket} | {error, Reason}</name> - <fsummary>Accept an incoming connection and - prepare for <c>ssl_accept</c></fsummary> + <fsummary>Accepts an incoming connection and + prepares for <c>ssl_accept</c>.</fsummary> <type> - <v>Socket = NewSocket = sslsocket()</v> + <v>ListenSocket = NewSocket = sslsocket()</v> <v>Timeout = integer()</v> <v>Reason = reason()</v> </type> <desc> <p>Accepts an incoming connection request on a listen socket. - <c>ListenSocket</c> must be a socket returned from - <c>listen/2</c>. The socket returned should be passed to - <c>ssl_accept</c> to complete ssl handshaking and - establishing the connection.</p> + <c>ListenSocket</c> must be a socket returned from + <seealso marker="#listen-2"> ssl:listen/2</seealso>. + The socket returned is to be passed to + <seealso marker="#ssl_accept-2"> ssl:ssl_accept[2,3]</seealso> + to complete handshaking, that is, + establishing the SSL/TLS connection.</p> <warning> - <p>The socket returned can only be used with <c>ssl_accept</c>, - no traffic can be sent or received before that call.</p> + <p>The socket returned can only be used with + <seealso marker="#ssl_accept-2"> ssl:ssl_accept[2,3]</seealso>. + No traffic can be sent or received before that call.</p> </warning> <p>The accepted socket inherits the options set for - <c>ListenSocket</c> in <c>listen/2</c>.</p> + <c>ListenSocket</c> in + <seealso marker="#listen-2"> ssl:listen/2</seealso>.</p> <p>The default - value for <c>Timeout</c> is <c>infinity</c>. If - <c>Timeout</c> is specified, and no connection is accepted - within the given time, <c>{error, timeout}</c> is - returned.</p> + value for <c>Timeout</c> is <c>infinity</c>. If + <c>Timeout</c> is specified and no connection is accepted + within the given time, <c>{error, timeout}</c> is + returned.</p> </desc> </func> <func> - <name>versions() -> - [{SslAppVer, SupportedSslVer, AvailableSslVsn}]</name> + <name>versions() -> [versions_info()]</name> <fsummary>Returns version information relevant for the - ssl application.</fsummary> + SSL application.</fsummary> <type> - <v>SslAppVer = string()</v> - <v>SupportedSslVer = [protocol()]</v> - <v>AvailableSslVsn = [protocol()]</v> + <v>versions_info() = {app_vsn, string()} | {supported | available, [protocol()] </v> </type> <desc> - <p> - Returns version information relevant for the - ssl application.</p> - </desc> - </func> - <func> - <name>negotiated_next_protocol(Socket) -> {ok, Protocol} | {error, next_protocol_not_negotiated}</name> - <fsummary>Returns the Next Protocol negotiated.</fsummary> - <type> - <v>Socket = sslsocket()</v> - <v>Protocol = binary()</v> - </type> - <desc> - <p> - Returns the Next Protocol negotiated. - </p> + <p>Returns version information relevant for the SSL + application.</p> + <taglist> + <tag><c>app_vsn</c></tag> + <item>The application version of the SSL application.</item> + + <tag><c>supported</c></tag> + <item>TLS/SSL versions supported by default. + Overridden by a version option on + <seealso marker="#connect-2"> connect/[2,3,4]</seealso>, + <seealso marker="#listen-2"> listen/2</seealso>, and <seealso + marker="#ssl_accept-2">ssl_accept/[1,2,3]</seealso>. + For the negotiated TLS/SSL version, see <seealso + marker="#connection_information-1">ssl:connection_information/1 + </seealso>.</item> + + <tag><c>available</c></tag> + <item>All TLS/SSL versions supported by the SSL application. + TLS 1.2 requires sufficient support from the Crypto + application.</item> + </taglist> </desc> </func> - + </funcs> <section> <title>SEE ALSO</title> - <p><seealso marker="kernel:inet">inet(3) </seealso> and - <seealso marker="kernel:gen_tcp">gen_tcp(3) </seealso> + <p><seealso marker="kernel:inet">inet(3)</seealso> and + <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso> </p> </section> </erlref> - diff --git a/lib/ssl/doc/src/ssl_app.xml b/lib/ssl/doc/src/ssl_app.xml index 0ee5b23e47..a66e947bc1 100644 --- a/lib/ssl/doc/src/ssl_app.xml +++ b/lib/ssl/doc/src/ssl_app.xml @@ -1,92 +1,167 @@ -<?xml version="1.0" encoding="iso-8859-1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE appref SYSTEM "appref.dtd"> <appref> <header> <copyright> - <year>1999</year><year>2013</year> + <year>1999</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> <title>ssl</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> <file>ssl_app.sgml</file> </header> <app>ssl</app> - <appsummary>The SSL application provides secure communication over + <appsummary>The ssl application provides secure communication over sockets.</appsummary> + <description> + <p> + The ssl application is an implementation of the SSL/TLS protocol in Erlang. + </p> + <list type="bulleted"> + <item>Supported SSL/TLS-versions are SSL-3.0, TLS-1.0, + TLS-1.1, and TLS-1.2.</item> + <item>For security reasons SSL-2.0 is not supported.</item> + <item>For security reasons SSL-3.0 is no longer supported by default, + but can be configured.</item> + <item>For security reasons DES cipher suites are no longer supported by default, + but can be configured.</item> + <item> Renegotiation Indication Extension <url href="http://www.ietf.org/rfc/rfc5746.txt">RFC 5746</url> is supported + </item> + <item>Ephemeral Diffie-Hellman cipher suites are supported, + but not Diffie Hellman Certificates cipher suites.</item> + <item>Elliptic Curve cipher suites are supported if the Crypto + application supports it and named curves are used. + </item> + <item>Export cipher suites are not supported as the + U.S. lifted its export restrictions in early 2000.</item> + <item>IDEA cipher suites are not supported as they have + become deprecated by the latest TLS specification so it is not + motivated to implement them.</item> + <item>Compression is not supported.</item> + <item>CRL validation is supported.</item> + <item>Policy certificate extensions are not supported.</item> + <item>'Server Name Indication' extension + (<url href="http://www.ietf.org/rfc/rfc6066.txt">RFC 6066</url>) is supported.</item> + <item>Application Layer Protocol Negotiation (ALPN) and its successor Next Protocol Negotiation (NPN) + are supported. </item> + <item>It is possible to use Pre-Shared Key (PSK) and Secure Remote Password (SRP) + cipher suites, but they are not enabled by default. + </item> + </list> + </description> + <section> <title>DEPENDENCIES</title> - <p>The ssl application uses the Erlang applications public_key and - crypto to handle public keys and encryption, hence these - applications needs to be loaded for the ssl application to work. In - an embedded environment that means they need to be started with - application:start/[1,2] before the ssl application is started. - </p> + <p>The SSL application uses the <c>public_key</c> and + Crypto application to handle public keys and encryption, hence + these applications must be loaded for the SSL application to work. + In an embedded environment this means they must be started with + <c>application:start/[1,2]</c> before the SSL application is + started.</p> </section> <section> - <title>ENVIRONMENT</title> - <p>The following application environment configuration parameters - are defined for the SSL application. See <seealso - marker="kernel:application">application(3)</seealso>for more - information about configuration parameters. - </p> - <p>Note that the environment parameters can be set on the command line, - for instance,</p> - <p><c>erl ... -ssl protocol_version '[sslv3, tlsv1]' ...</c>. - </p> + <title>CONFIGURATION</title> + <p>The application environment configuration parameters in this section + are defined for the SSL application. For more information + about configuration parameters, see the + <seealso marker="kernel:application">application(3)</seealso> + manual page in Kernel.</p> + + <p>The environment parameters can be set on the command line, + for example:</p> + + <p><c>erl -ssl protocol_version "['tlsv1.2', 'tlsv1.1']"</c></p> + <taglist> - <tag><c><![CDATA[protocol_version = [sslv3|tlsv1] <optional>]]></c>.</tag> - <item> - <p>Protocol that will be supported by started clients and - servers. If this option is not set it will default to all - protocols currently supported by the erlang ssl application. - Note that this option may be overridden by the version option - to ssl:connect/[2,3] and ssl:listen/2. - </p> - </item> + <tag><c>protocol_version = </c><seealso marker="ssl#type-protocol">ssl:protocol()</seealso><c><![CDATA[<optional>]]></c></tag> + <item><p>Protocol supported by started clients and + servers. If this option is not set, it defaults to all + protocols currently supported by the SSL application. + This option can be overridden by the version option + to <c>ssl:connect/[2,3]</c> and <c>ssl:listen/2</c>.</p></item> <tag><c><![CDATA[session_lifetime = integer() <optional>]]></c></tag> - <item> - <p>The lifetime of session data in seconds. - </p> - </item> + <item><p>Maximum lifetime of the session data in seconds. Defaults to 24 hours which is the maximum + recommended lifetime by <url href="http://www.ietf.org/rfc/5246rfc.txt">RFC 5246</url>. However + sessions may be invalidated earlier due to the maximum limitation of the session cache table. + </p></item> + + <tag><c><![CDATA[session_cb = atom() <optional>]]></c></tag> + <item><p>Name of the session cache callback module that implements + the <c>ssl_session_cache_api</c> behavior. Defaults to + <c>ssl_session_cache</c>.</p></item> + + <tag><c><![CDATA[session_cb_init_args = proplist:proplist() <optional>]]></c></tag> - <tag><c><![CDATA[session_cb = atom() <optional>]]></c></tag> + <item><p>List of extra user-defined arguments to the <c>init</c> function + in the session cache callback module. Defaults to <c>[]</c>.</p></item> + + <tag><c><![CDATA[session_cache_client_max = integer() <optional>]]></c><br/></tag> + <item><p>Limits the growth of the clients session cache, that is + how many sessions towards servers that are cached to be used by + new client connections. If the maximum number of sessions is + reached, the current cache entries will be invalidated + regardless of their remaining lifetime. Defaults to + 1000.</p></item> + + <tag> <c><![CDATA[session_cache_server_max = integer() <optional>]]></c></tag> + <item><p>Limits the growth of the servers session cache, that is + how many client sessions are cached by the server. If the + maximum number of sessions is reached, the current cache entries + will be invalidated regardless of their remaining + lifetime. Defaults to 1000.</p></item> + + <tag><c><![CDATA[ssl_pem_cache_clean = integer() <optional>]]></c></tag> <item> - <p> - Name of session cache callback module that implements - the ssl_session_cache_api behavior, defaults to - ssl_session_cache.erl. - </p> + <p> + Number of milliseconds between PEM cache validations. Defaults to 2 minutes. + </p> + <seealso + marker="ssl#clear_pem_cache-0">ssl:clear_pem_cache/0</seealso> + </item> - - <tag><c><![CDATA[session_cb_init_args = list() <optional>]]></c></tag> + <tag><c><![CDATA[alert_timeout = integer() <optional>]]></c></tag> <item> <p> - List of arguments to the init function in session cache - callback module, defaults to []. + Number of milliseconds between sending of a fatal alert and + closing the connection. Waiting a little while improves the + peers chances to properly receiving the alert so it may + shutdown gracefully. Defaults to 5000 milliseconds. </p> </item> - </taglist> </section> <section> + <title>ERROR LOGGER AND EVENT HANDLERS</title> + <p>The SSL application uses the default <seealso + marker="kernel:error_logger">OTP error logger</seealso> to log + unexpected errors and TLS alerts. The logging of TLS alerts may be + turned off with the <c>log_alert</c> option. </p> + </section> + + <section> <title>SEE ALSO</title> <p><seealso marker="kernel:application">application(3)</seealso></p> </section> diff --git a/lib/ssl/doc/src/ssl_crl_cache.xml b/lib/ssl/doc/src/ssl_crl_cache.xml new file mode 100644 index 0000000000..7a67de3971 --- /dev/null +++ b/lib/ssl/doc/src/ssl_crl_cache.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2015</year><year>2015</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + </legalnotice> + <title>ssl_crl_cache</title> + <file>ssl_crl_cache.xml</file> + </header> + + <module>ssl_crl_cache</module> + <modulesummary>CRL cache </modulesummary> + <description> + <p> + Implements an internal CRL (Certificate Revocation List) cache. + In addition to implementing the <seealso + marker="ssl_crl_cache_api"> ssl_crl_cache_api</seealso> behaviour + the following functions are available. + </p> + </description> + + <funcs> + <func> + <name>delete(Entries) -> ok | {error, Reason} </name> + <fsummary> </fsummary> + <type> + <v> Entries = <seealso marker="inets:http_uri">http_uri:uri() </seealso> | {file, string()} | {der, [<seealso + marker="public_key:public_key"> public_key:der_encoded() </seealso>]}</v> + <v> Reason = term()</v> + </type> + <desc> + <p>Delete CRLs from the ssl applications local cache. </p> + </desc> + </func> + <func> + <name>insert(CRLSrc) -> ok | {error, Reason}</name> + <name>insert(URI, CRLSrc) -> ok | {error, Reason}</name> + <fsummary> </fsummary> + <type> + <v> CRLSrc = {file, string()} | {der, [ <seealso + marker="public_key:public_key"> public_key:der_encoded() </seealso> ]}</v> + <v> URI = <seealso marker="inets:http_uri">http_uri:uri() </seealso> </v> + <v> Reason = term()</v> + </type> + <desc> + <p>Insert CRLs into the ssl applications local cache. </p> + </desc> + </func> + </funcs> +</erlref>
\ No newline at end of file diff --git a/lib/ssl/doc/src/ssl_crl_cache_api.xml b/lib/ssl/doc/src/ssl_crl_cache_api.xml new file mode 100644 index 0000000000..7440b6ef04 --- /dev/null +++ b/lib/ssl/doc/src/ssl_crl_cache_api.xml @@ -0,0 +1,121 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2015</year><year>2015</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + </legalnotice> + <title>ssl_crl_cache_api</title> + <file>ssl_crl_cache_api.xml</file> + </header> + + <module>ssl_crl_cache_api</module> + <modulesummary>API for a SSL/TLS CRL (Certificate Revocation List) cache.</modulesummary> + <description> + <p> + When SSL/TLS performs certificate path validation according to + <url href="http://www.ietf.org/rfc/rfc5280.txt">RFC 5280 </url> + it should also perform CRL validation checks. To enable the CRL + checks the application needs access to CRLs. A database of CRLs + can be set up in many different ways. This module provides the + behavior of the API needed to integrate an arbitrary CRL cache + with the erlang ssl application. It is also used by the + application itself to provide a simple default implementation of + a CRL cache. + </p> + </description> + + <section> + <title>DATA TYPES</title> + + <p>The following data types are used in the functions below: + </p> + + <taglist> + + <tag><c>cache_ref() =</c></tag> + <item>opaque()</item> + <tag><c>dist_point() =</c></tag> + <item><p>#'DistributionPoint'{} see <seealso + marker="public_key:public_key_records"> X509 certificates records</seealso></p></item> + + </taglist> + + </section> + <funcs> + <func> + <name>fresh_crl(DistributionPoint, CRL) -> FreshCRL</name> + <fsummary> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to + public_key:pkix_crls_validate/3 </fsummary> + <type> + <v> DistributionPoint = dist_point() </v> + <v> CRL = [<seealso + marker="public_key:public_key">public_key:der_encoded()</seealso>] </v> + <v> FreshCRL = [<seealso + marker="public_key:public_key">public_key:der_encoded()</seealso>] </v> + </type> + <desc> + <p> <c>fun fresh_crl/2 </c> will be used as input option <c>update_crl</c> to + <seealso marker="public_key:public_key#pkix_crls_validate-3">public_key:pkix_crls_validate/3 </seealso> </p> + </desc> + </func> + + <func> + <name>lookup(DistributionPoint, Issuer, DbHandle) -> not_available | CRLs </name> + <name>lookup(DistributionPoint, DbHandle) -> not_available | CRLs </name> + <fsummary> </fsummary> + <type> + <v> DistributionPoint = dist_point() </v> + <v> Issuer = <seealso + marker="public_key:public_key">public_key:issuer_name()</seealso> </v> + <v> DbHandle = cache_ref() </v> + <v> CRLs = [<seealso + marker="public_key:public_key">public_key:der_encoded()</seealso>] </v> + </type> + <desc> <p>Lookup the CRLs belonging to the distribution point <c> Distributionpoint</c>. + This function may choose to only look in the cache or to follow distribution point + links depending on how the cache is administrated. </p> + + <p>The <c>Issuer</c> argument contains the issuer name of the + certificate to be checked. Normally the returned CRL should + be issued by this issuer, except if the <c>cRLIssuer</c> field + of <c>DistributionPoint</c> has a value, in which case that + value should be used instead.</p> + + <p>In an earlier version of this API, the <c>lookup</c> + function received two arguments, omitting <c>Issuer</c>. For + compatibility, this is still supported: if there is no + <c>lookup/3</c> function in the callback module, + <c>lookup/2</c> is called instead.</p> + </desc> + </func> + + <func> + <name>select(Issuer, DbHandle) -> CRLs </name> + <fsummary>Select the CRLs in the cache that are issued by <c>Issuer</c></fsummary> + <type> + <v> Issuer = <seealso + marker="public_key:public_key">public_key:issuer_name()</seealso></v> + <v> DbHandle = cache_ref() </v> + </type> + <desc> + <p>Select the CRLs in the cache that are issued by <c>Issuer</c> </p> + </desc> + </func> + </funcs> +</erlref> diff --git a/lib/ssl/doc/src/ssl_distribution.xml b/lib/ssl/doc/src/ssl_distribution.xml index 4ae4ead3ee..4bd5f67202 100644 --- a/lib/ssl/doc/src/ssl_distribution.xml +++ b/lib/ssl/doc/src/ssl_distribution.xml @@ -1,23 +1,24 @@ -<?xml version="1.0" encoding="iso-8859-1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> - <year>2000</year><year>2011</year> + <year>2000</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> @@ -31,23 +32,20 @@ <rev>B</rev> <file>ssl_distribution.xml</file> </header> - <p>This chapter describes how the Erlang distribution can use - SSL to get additional verification and security. - </p> + <p>This section describes how the Erlang distribution can use + SSL to get extra verification and security.</p> - <section> - <title>Introduction</title> - <p>The Erlang distribution can in theory use almost any connection - based protocol as bearer. A module that implements the protocol - specific parts of the connection setup is however needed. The - default distribution module is <c>inet_tcp_dist</c> which is - included in the Kernel application. When starting an + <p>The Erlang distribution can in theory use almost any + connection-based protocol as bearer. However, a module that + implements the protocol-specific parts of the connection setup is + needed. The default distribution module is <c>inet_tcp_dist</c> + in the Kernel application. When starting an Erlang node distributed, <c>net_kernel</c> uses this module to - setup listen ports and connections. </p> + set up listen ports and connections.</p> - <p>In the SSL application there is an additional distribution - module, <c>inet_tls_dist</c> which can be used as an - alternative. All distribution connections will be using SSL and + <p>In the SSL application, an exra distribution + module, <c>inet_tls_dist</c>, can be used as an + alternative. All distribution connections will use SSL and all participating Erlang nodes in a distributed system must use this distribution module.</p> @@ -55,62 +53,79 @@ SSL connection setup. Erlang node cookies are however always used, as they can be used to differentiate between two different Erlang networks.</p> - <p>Setting up Erlang distribution over SSL involves some simple but - necessary steps:</p> + + <p>To set up Erlang distribution over SSL:</p> <list type="bulleted"> - <item>Building boot scripts including the SSL application</item> - <item>Specifying the distribution module for net_kernel</item> - <item>Specifying security options and other SSL options</item> + <item><em>Step 1:</em> Build boot scripts including the + SSL application.</item> + <item><em>Step 2:</em> Specify the distribution module for + <c>net_kernel</c>.</item> + <item><em>Step 3:</em> Specify the security options and other + SSL options.</item> + <item><em>Step 4:</em> Set up the environment to always use SSL.</item> </list> - <p>The rest of this chapter describes the above mentioned steps in - more detail.</p> - </section> + + <p>The following sections describe these steps.</p> <section> - <title>Building boot scripts including the SSL application</title> + <title>Building Boot Scripts Including the ssl Application</title> <p>Boot scripts are built using the <c>systools</c> utility in the - SASL application. Refer to the SASL documentations - for more information on systools. This is only an example of + <c>sasl</c> application. For more information on <c>systools</c>, + see the <c>sasl</c> documentation. This is only an example of what can be done.</p> - <p>The simplest boot script possible includes only the Kernel - and STDLIB applications. Such a script is located in the - Erlang distributions bin directory. The source for the script - can be found under the Erlang installation top directory under - <c><![CDATA[releases/<OTP version>/start_clean.rel]]></c>. Copy that - script to another location (and preferably another name) - and add the applications crypto, public_key and SSL with their current version numbers - after the STDLIB application.</p> - <p>An example .rel file with SSL added may look like this:</p> + <p>The simplest boot script possible includes only the Kernel + and STDLIB applications. Such a script is located in the + <c>bin</c> directory of the Erlang distribution. The source for the + script is found under the Erlang installation top directory under + <c><![CDATA[releases/<OTP version>/start_clean.rel]]></c>.</p> + + <p>Do the following:</p> + <list type="bulleted"> + <item><p>Copy that script to another location (and preferably another + name).</p></item> + <item><p>Add the applications Crypto, Public Key, and + SSL with their current version numbers after the + STDLIB application.</p></item> + </list> + <p>The following shows an example <c>.rel</c> file with SSL + added:</p> <code type="none"> {release, {"OTP APN 181 01","R15A"}, {erts, "5.9"}, [{kernel,"2.15"}, {stdlib,"1.18"}, {crypto, "2.0.3"}, {public_key, "0.12"}, + {asn1, "4.0"}, {ssl, "5.0"} ]}. </code> - <p>Note that the version numbers surely will differ in your system. - Whenever one of the applications included in the script is - upgraded, the script has to be changed.</p> - <p>Assuming the above .rel file is stored in a file - <c>start_ssl.rel</c> in the current directory, a boot script - can be built like this:</p> + <p>The version numbers differ in your system. Whenever one of the + applications included in the script is upgraded, change the script.</p> + <p>Do the following:</p> + <list type="bulleted"> + <item><p>Build the boot script.</p> + <p>Assuming the <c>.rel file</c> is stored in a file + <c>start_ssl.rel</c> in the current directory, a boot script + can be built as follows:</p></item> + </list> <code type="none"> 1> systools:make_script("start_ssl",[]). </code> - <p>There will now be a file <c>start_ssl.boot</c> in the current - directory. To test the boot script, start Erlang with the - <c>-boot</c> command line parameter specifying this boot script - (with its full path but without the <c>.boot</c> suffix), in - Unix it could look like this:</p> - <p></p> + <p>There is now a <c>start_ssl.boot</c> file in the current + directory.</p> + <p>Do the following:</p> + <list type="bulleted"> + <item><p>Test the boot script. To do this, start Erlang with the + <c>-boot</c> command-line parameter specifying this boot script + (with its full path, but without the <c>.boot</c> suffix). In + UNIX it can look as follows:</p></item> + </list> <code type="none"><![CDATA[ $ erl -boot /home/me/ssl/start_ssl Erlang (BEAM) emulator version 5.0 @@ -118,86 +133,102 @@ Erlang (BEAM) emulator version 5.0 Eshell V5.0 (abort with ^G) 1> whereis(ssl_manager). <0.41.0> ]]></code> - <p>The <c>whereis</c> function call verifies that the SSL - application is really started.</p> - <p>As an alternative to building a bootscript, one can explicitly + <p>The <c>whereis</c> function-call verifies that the SSL + application is started.</p> + + <p>As an alternative to building a bootscript, you can explicitly add the path to the SSL <c>ebin</c> directory on the command - line. This is done with the command line option <c>-pa</c>. This + line. This is done with command-line option <c>-pa</c>. This works as the SSL application does not need to be started for the distribution to come up, as a clone of the SSL application is - hooked into the kernel application, so as long as the - SSL applications code can be reached, the distribution will - start. The <c>-pa</c> method is only recommended for testing - purposes.</p> + hooked into the Kernel application. So, as long as the + SSL application code can be reached, the distribution starts. + The <c>-pa</c> method is only recommended for testing purposes.</p> - <note><p>Note that the clone of the SSL application is necessary to + <note><p>The clone of the SSL application must enable the use of the SSL code in such an early bootstage as - needed to setup the distribution, however this will make it + needed to set up the distribution. However, this makes it impossible to soft upgrade the SSL application.</p></note> </section> <section> - <title>Specifying distribution module for net_kernel</title> + <title>Specifying Distribution Module for net_kernel</title> <p>The distribution module for SSL is named <c>inet_tls_dist</c> - and is specified on the command line with the <c>-proto_dist</c> - option. The argument to <c>-proto_dist</c> should be the module - name without the <c>_dist</c> suffix, so this distribution + and is specified on the command line with option <c>-proto_dist</c>. + The argument to <c>-proto_dist</c> is to be the module + name without suffix <c>_dist</c>. So, this distribution module is specified with <c>-proto_dist inet_tls</c> on the command line.</p> - <p></p> - <p>Extending the command line from above gives us the following:</p> + <p>Extending the command line gives the following:</p> <code type="none"> $ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls </code> -<p>For the distribution to actually be started, we need to give -the emulator a name as well:</p> +<p>For the distribution to be started, give the emulator a name as well:</p> <code type="none"> $ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls -sname ssl_test Erlang (BEAM) emulator version 5.0 [source] Eshell V5.0 (abort with ^G) (ssl_test@myhost)1> </code> - <p>Note however that a node started in this way will refuse to talk - to other nodes, as no ssl parameters are supplied - (see below).</p> + + <p>However, a node started in this way refuses to talk + to other nodes, as no SSL parameters are supplied + (see the next section).</p> </section> <section> - <title>Specifying SSL options</title> <p>For SSL to work, at least - a public key and certificate needs to be specified for the server - side. In the following example the PEM-files consists of two - entries the servers certificate and its private key.</p> - - <p>On the <c>erl</c> command line one can specify options that the - SSL distribution will add when creating a socket.</p> - - <p>One can specify the simpler SSL options certfile, keyfile, - password, cacertfile, verify, reuse_sessions, - secure_renegotiate, depth, hibernate_after and ciphers (use old - string format) by adding the prefix server_ or client_ to the - option name. The server can also take the options dhfile and - fail_if_no_peer_cert (also prefixed). - <c>client_</c>-prfixed options are used when the distribution initiates a - connection to another node and the <c>server_</c>-prefixed options are used - when accepting a connection from a remote node. </p> - - <p> More complex options such as verify_fun are not available at - the moment but a mechanism to handle such options may be added in - a future release. </p> - - <p> Raw socket options such as packet and size must not be specified on - the command line</p>. - - <p>The command line argument for specifying the SSL options is named - <c>-ssl_dist_opt</c> and should be followed by pairs of - SSL options and their values. The <c>-ssl_dist_opt</c> argument can + <title>Specifying SSL Options</title> + <p>For SSL to work, at least + a public key and a certificate must be specified for the server + side. In the following example, the PEM-files consist of two + entries, the server certificate and its private key.</p> + + <p>On the <c>erl</c> command line you can specify options that the + SSL distribution adds when creating a socket.</p> + + <p>The simplest SSL options in the following list can be specified + by adding the + prefix <c>server_</c> or <c>client_</c> to the option name:</p> + <list type="bulleted"> + <item><c>certfile</c></item> + <item><c>keyfile</c></item> + <item><c>password</c></item> + <item><c>cacertfile</c></item> + <item><c>verify</c></item> + <item><c>verify_fun</c> (write as <c>{Module, Function, InitialUserState}</c>)</item> + <item><c>crl_check</c></item> + <item><c>crl_cache</c> (write as Erlang term)</item> + <item><c>reuse_sessions</c></item> + <item><c>secure_renegotiate</c></item> + <item><c>depth</c></item> + <item><c>hibernate_after</c></item> + <item><c>ciphers</c> (use old string format)</item> + </list> + + <p>Note that <c>verify_fun</c> needs to be written in a different + form than the corresponding SSL option, since funs are not + accepted on the command line.</p> + + <p>The server can also take the options <c>dhfile</c> and + <c>fail_if_no_peer_cert</c> (also prefixed).</p> + + <p><c>client_</c>-prefixed options are used when the distribution + initiates a connection to another node. <c>server_</c>-prefixed + options are used when accepting a connection from a remote node.</p> + + <p>Raw socket options, such as <c>packet</c> and <c>size</c> must not + be specified on the command line.</p> + + <p>The command-line argument for specifying the SSL options is named + <c>-ssl_dist_opt</c> and is to be followed by pairs of + SSL options and their values. Argument <c>-ssl_dist_opt</c> can be repeated any number of times.</p> - <p>An example command line would now look something like this + <p>An example command line can now look as follows (line breaks in the command are for readability, - they should not be there when typed):</p> + and are not be there when typed):</p> <code type="none"> $ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls -ssl_dist_opt server_certfile "/home/me/ssl/erlserver.pem" @@ -207,20 +238,20 @@ Erlang (BEAM) emulator version 5.0 [source] Eshell V5.0 (abort with ^G) (ssl_test@myhost)1> </code> - <p>A node started in this way will be fully functional, using SSL + <p>A node started in this way is fully functional, using SSL as the distribution protocol.</p> </section> <section> - <title>Setting up environment to always use SSL</title> - <p>A convenient way to specify arguments to Erlang is to use the - <c>ERL_FLAGS</c> environment variable. All the flags needed to - use SSL distribution can be specified in that variable and will - then be interpreted as command line arguments for all + <title>Setting up Environment to Always Use SSL</title> + <p>A convenient way to specify arguments to Erlang is to use environment + variable <c>ERL_FLAGS</c>. All the flags needed to + use the SSL distribution can be specified in that variable and are + then interpreted as command-line arguments for all subsequent invocations of Erlang.</p> - <p></p> - <p>In a Unix (Bourne) shell it could look like this (line breaks for - readability, they should not be there when typed):</p> + + <p>In a Unix (Bourne) shell, it can look as follows (line breaks are for + readability, they are not to be there when typed):</p> <code type="none"> $ ERL_FLAGS="-boot /home/me/ssl/start_ssl -proto_dist inet_tls -ssl_dist_opt server_certfile /home/me/ssl/erlserver.pem @@ -240,7 +271,31 @@ Eshell V5.0 (abort with ^G) {ssl_dist_opt,["server_secure_renegotiate","true", "client_secure_renegotiate","true"] {home,["/home/me"]}] </code> + <p>The <c>init:get_arguments()</c> call verifies that the correct - arguments are supplied to the emulator. </p> + arguments are supplied to the emulator.</p> + </section> + + <section> + <title>Using SSL distribution over IPv6</title> + <p>It is possible to use SSL distribution over IPv6 instead of + IPv4. To do this, pass the option <c>-proto_dist inet6_tls</c> + instead of <c>-proto_dist inet_tls</c> when starting Erlang, + either on the command line or in the <c>ERL_FLAGS</c> environment + variable.</p> + + <p>An example command line with this option would look like this:</p> + <code type="none"> +$ erl -boot /home/me/ssl/start_ssl -proto_dist inet6_tls + -ssl_dist_opt server_certfile "/home/me/ssl/erlserver.pem" + -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true + -sname ssl_test +Erlang (BEAM) emulator version 5.0 [source] + +Eshell V5.0 (abort with ^G) +(ssl_test@myhost)1> </code> + + <p>A node started in this way will only be able to communicate with + other nodes using SSL distribution over IPv6.</p> </section> </chapter> diff --git a/lib/ssl/doc/src/ssl_introduction.xml b/lib/ssl/doc/src/ssl_introduction.xml new file mode 100644 index 0000000000..d3e39dbb01 --- /dev/null +++ b/lib/ssl/doc/src/ssl_introduction.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE chapter SYSTEM "chapter.dtd"> + +<chapter> + <header> + <copyright> + <year>2015</year> + <year>2015</year> + <holder>Ericsson AB, All Rights Reserved</holder> + </copyright> + <legalnotice> + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + The Initial Developer of the Original Code is Ericsson AB. + </legalnotice> + + <title>Introduction</title> + <prepared>OTP team</prepared> + <docno></docno> + <date>2015-03-05</date> + <rev>A</rev> + <file>ssl_introduction.xml</file> + </header> + + <section> + <title>Purpose</title> + <p>Transport Layer Security (TLS) and its predecessor, the Secure + Sockets Layer (SSL), are cryptographic protocols designed to + provide communications security over a computer network. The protocols use + use X.509 certificates and hence public key (asymmetric) cryptography to + authenticate the counterpart with whom they communicate, + and to exchange a symmetric key for payload encryption. The protocol provides + data/message confidentiality (encryption), integrity (through message authentication code checks) + and host verification (through certificate path validation).</p> + </section> + + <section> + <title>Prerequisites</title> + <p>It is assumed that the reader is familiar with the Erlang + programming language, the concepts of OTP, and has a basic + understanding of SSL/TLS.</p> + </section> + +</chapter> diff --git a/lib/ssl/doc/src/ssl_protocol.xml b/lib/ssl/doc/src/ssl_protocol.xml index f540dc999b..31a22db58b 100644 --- a/lib/ssl/doc/src/ssl_protocol.xml +++ b/lib/ssl/doc/src/ssl_protocol.xml @@ -1,53 +1,63 @@ -<?xml version="1.0" encoding="iso-8859-1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> - <year>2003</year><year>2012</year> + <year>2003</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> - <title>Transport Layer Security (TLS) and its predecessor, Secure Socket Layer (SSL)</title> + <title>TLS and its Predecessor, SSL</title> + <prepared></prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date></date> + <rev></rev> <file>ssl_protocol.xml</file> </header> - <p>The erlang SSL application currently implements the protocol SSL/TLS - for currently supported versions see <seealso marker="ssl">ssl(3)</seealso> + <p>The Erlang SSL application implements the SSL/TLS protocol + for the currently supported versions, see the + <seealso marker="ssl">ssl(3)</seealso> manual page. </p> - <p>By default erlang SSL is run over the TCP/IP protocol even - though you could plug in any other reliable transport protocol - with the same API as gen_tcp.</p> + <p>By default SSL/TLS is run over the TCP/IP protocol even + though you can plug in any other reliable transport protocol + with the same Application Programming Interface (API) as the + <c>gen_tcp</c> module in Kernel.</p> - <p>If a client and server wants to use an upgrade mechanism, such as - defined by RFC2817, to upgrade a regular TCP/IP connection to an SSL - connection the erlang SSL API supports this. This can be useful for - things such as supporting HTTP and HTTPS on the same port and + <p>If a client and a server wants to use an upgrade mechanism, such as + defined by RFC 2817, to upgrade a regular TCP/IP connection to an SSL + connection, this is supported by the Erlang SSL application API. This can be + useful for, for example, supporting HTTP and HTTPS on the same port and implementing virtual hosting. </p> <section> - <title>Security overview</title> + <title>Security Overview</title> - <p>To achieve authentication and privacy the client and server will - perform a TLS Handshake procedure before transmitting or receiving - any data. During the handshake they agree on a protocol version and - cryptographic algorithms, they generate shared secrets using public - key cryptographics and optionally authenticate each other with + <p>To achieve authentication and privacy, the client and server + perform a TLS handshake procedure before transmitting or receiving + any data. During the handshake, they agree on a protocol version and + cryptographic algorithms, generate shared secrets using public + key cryptographies, and optionally authenticate each other with digital certificates.</p> </section> @@ -55,20 +65,21 @@ <title>Data Privacy and Integrity</title> <p>A <em>symmetric key</em> algorithm has one key only. The key is - used for both encryption and decryption. These algorithms are fast - compared to public key algorithms (using two keys, a public and a - private one) and are therefore typically used for encrypting bulk + used for both encryption and decryption. These algorithms are fast, + compared to public key algorithms (using two keys, one public and one + private) and are therefore typically used for encrypting bulk data. </p> <p>The keys for the symmetric encryption are generated uniquely for each connection and are based on a secret negotiated - in the TLS handshake. </p> + in the TLS handshake.</p> - <p>The TLS handshake protocol and data transfer is run on top of - the TLS Record Protocol that uses a keyed-hash MAC (Message - Authenticity Code), or HMAC, to protect the message's data - integrity. From the TLS RFC "A Message Authentication Code is a + <p>The TLS handshake protocol and data transfer is run on top of + the TLS Record Protocol, which uses a keyed-hash Message + Authenticity Code (MAC), or a Hash-based MAC (HMAC), + to protect the message data + integrity. From the TLS RFC: "A Message Authentication Code is a one-way hash computed from a message and some secret data. It is difficult to forge without knowing the secret data. Its purpose is to detect if the message has been altered." @@ -82,40 +93,43 @@ passport. The holder of the certificate is called the <em>subject</em>. The certificate is signed with the private key of the issuer of the certificate. A chain - of trust is build by having the issuer in its turn being - certified by an other certificate and so on until you reach the - so called root certificate that is self signed i.e. issued + of trust is built by having the issuer in its turn being + certified by another certificate, and so on, until you reach the + so called root certificate, which is self-signed, that is, issued by itself.</p> - <p>Certificates are issued by <em>certification - authorities</em> (<em>CA</em>s) only. There are a handful of - top CAs in the world that issue root certificates. You can - examine the certificates of several of them by clicking + <p>Certificates are issued by Certification Authorities (CAs) only. + A handful of top CAs in the world issue root certificates. You can + examine several of these certificates by clicking through the menus of your web browser. </p> </section> <section> - <title>Authentication of Sender</title> + <title>Peer Authentication</title> - <p>Authentication of the sender is done by public key path - validation as defined in RFC 3280. Simplified that means that - each certificate in the certificate chain is issued by the one - before, the certificates attributes are valid ones, and the - root cert is a trusted cert that is present in the trusted - certs database kept by the peer.</p> + <p>Authentication of the peer is done by public key path + validation as defined in RFC 3280. This means basically + the following:</p> + <list type="bulleted"> + <item>Each certificate in the certificate chain is issued by the + previous one.</item> + <item>The certificates attributes are valid.</item> + <item>The root certificate is a trusted certificate that is present + in the trusted certificate database kept by the peer.</item> + </list> - <p>The server will always send a certificate chain as part of - the TLS handshake, but the client will only send one if - the server requests it. If the client does not have - an appropriate certificate it may send an "empty" certificate + <p>The server always sends a certificate chain as part of + the TLS handshake, but the client only sends one if requested + by the server. If the client does not have + an appropriate certificate, it can send an "empty" certificate to the server.</p> - <p>The client may choose to accept some path evaluation errors - for instance a web browser may ask the user if they want to - accept an unknown CA root certificate. The server, if it request - a certificate, will on the other hand not accept any path validation - errors. It is configurable if the server should accept + <p>The client can choose to accept some path evaluation errors, + for example, a web browser can ask the user whether to + accept an unknown CA root certificate. The server, if it requests + a certificate, does however not accept any path validation + errors. It is configurable if the server is to accept or reject an "empty" certificate as response to a certificate request.</p> </section> @@ -123,25 +137,24 @@ <section> <title>TLS Sessions</title> - <p>From the TLS RFC "A TLS session is an association between a - client and a server. Sessions are created by the handshake + <p>From the TLS RFC: "A TLS session is an association between a + client and a server. Sessions are created by the handshake protocol. Sessions define a set of cryptographic security parameters, which can be shared among multiple connections. Sessions are used to avoid the expensive negotiation of new security parameters for each connection."</p> <p>Session data is by default kept by the SSL application in a - memory storage hence session data will be lost at application - restart or takeover. Users may define their own callback module + memory storage, hence session data is lost at application + restart or takeover. Users can define their own callback module to handle session data storage if persistent data storage is - required. Session data will also be invalidated after 24 hours - from it was saved, for security reasons. It is of course - possible to configure the amount of time the session data should be - saved.</p> + required. Session data is also invalidated after 24 hours + from it was saved, for security reasons. The amount of time the + session data is to be saved can be configured.</p> - <p>SSL clients will by default try to reuse an available session, - SSL servers will by default agree to reuse sessions when clients - ask to do so.</p> + <p>By default the SSL clients try to reuse an available session and + by default the SSL servers agree to reuse sessions when clients + ask for it.</p> </section> </chapter> diff --git a/lib/ssl/doc/src/ssl_session_cache_api.xml b/lib/ssl/doc/src/ssl_session_cache_api.xml index e0b07961fb..b85d8fb284 100644 --- a/lib/ssl/doc/src/ssl_session_cache_api.xml +++ b/lib/ssl/doc/src/ssl_session_cache_api.xml @@ -1,62 +1,79 @@ -<?xml version="1.0" encoding="iso-8859-1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE erlref SYSTEM "erlref.dtd"> <erlref> <header> <copyright> - <year>1999</year><year>2010</year> + <year>1999</year><year>2015</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> <title>ssl</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> <file>ssl_session_cache_api.xml</file> </header> <module>ssl_session_cache_api</module> - <modulesummary>Defines the API for the TLS session cache so - that the data storage scheme can be replaced by - defining a new callback module implementing this API.</modulesummary> + <modulesummary>TLS session cache API</modulesummary> + <description> + <p> + Defines the API for the TLS session cache so + that the data storage scheme can be replaced by + defining a new callback module implementing this API. + </p> + </description> <section> - <title>Common Data Types</title> + <title>DATA TYPES</title> - <p>The following data types are used in the functions below: - </p> + <p>The following data types are used in the functions for + <c>ssl_session_cache_api</c>:</p> - <p><c>cache_ref() = opaque()</c></p> - - <p><c>key() = {partialkey(), session_id()}</c></p> - - <p><c>partialkey() = opaque()</c></p> - - <p><c>session_id() = binary()</c></p> + <taglist> + <tag><c>cache_ref() =</c></tag> + <item><p><c>opaque()</c></p></item> + + <tag><c>key() =</c></tag> + <item><p><c>{partialkey(), session_id()}</c></p></item> + + <tag><c>partialkey() =</c></tag> + <item><p><c>opaque()</c></p></item> + + <tag><c>session_id() =</c></tag> + <item><p><c>binary()</c></p></item> + + <tag><c>session()</c> =</tag> + <item><p><c>opaque()</c></p></item> + </taglist> - <p><c>session() = opaque()</c></p> - </section> <funcs> <func> <name>delete(Cache, Key) -> _</name> - <fsummary></fsummary> + <fsummary>Deletes a cache entry.</fsummary> <type> - <v> Cache = cache_ref()</v> - <v> Key = key()</v> + <v>Cache = cache_ref()</v> + <v>Key = key()</v> </type> <desc> - <p> Deletes a cache entry. Will only be called from the cache + <p>Deletes a cache entry. Is only called from the cache handling process. </p> </desc> @@ -69,41 +86,50 @@ <v></v> </type> <desc> - <p>Calls Fun(Elem, AccIn) on successive elements of the - cache, starting with AccIn == Acc0. Fun/2 must return a new - accumulator which is passed to the next call. The function returns - the final value of the accumulator. Acc0 is returned if the cache is - empty. + <p>Calls <c>Fun(Elem, AccIn)</c> on successive elements of the + cache, starting with <c>AccIn == Acc0</c>. <c>Fun/2</c> must + return a new accumulator, which is passed to the next call. + The function returns the final value of the accumulator. + <c>Acc0</c> is returned if the cache is empty. </p> </desc> </func> <func> - <name>init() -> opaque() </name> - <fsummary>Return cache reference</fsummary> + <name>init(Args) -> opaque() </name> + <fsummary>Returns cache reference.</fsummary> <type> - <v></v> + <v>Args = proplists:proplist()</v> </type> <desc> + <p>Includes property <c>{role, client | server}</c>. + Currently this is the only predefined property, + there can also be user-defined properties. See also + application environment variable + <seealso marker="ssl_app">session_cb_init_args</seealso>. + </p> <p>Performs possible initializations of the cache and returns - a reference to it that will be used as parameter to the other - api functions. Will be called by the cache handling processes - init function, hence putting the same requirements on it as - a normal process init function. + a reference to it that is used as parameter to the other + API functions. Is called by the cache handling processes + <c>init</c> function, hence putting the same requirements on it + as a normal process <c>init</c> function. This function is + called twice when starting the SSL application, once with + the role client and once with the role server, as the SSL + application must be prepared to take on both roles. </p> </desc> </func> <func> <name>lookup(Cache, Key) -> Entry</name> - <fsummary> Looks up a cache entry.</fsummary> + <fsummary>Looks up a cache entry.</fsummary> <type> - <v> Cache = cache_ref()</v> - <v> Key = key()</v> - <v> Entry = session() | undefined </v> + <v>Cache = cache_ref()</v> + <v>Key = key()</v> + <v>Entry = session() | undefined</v> </type> <desc> - <p>Looks up a cache entry. Should be callable from any + <p>Looks up a cache entry. Is to be callable from any process. </p> </desc> @@ -111,14 +137,14 @@ <func> <name>select_session(Cache, PartialKey) -> [session()]</name> - <fsummary>>Selects sessions that could be reused.</fsummary> + <fsummary>Selects sessions that can be reused.</fsummary> <type> - <v> Cache = cache_ref()</v> - <v> PartialKey = partialkey()</v> - <v> Session = session()</v> + <v>Cache = cache_ref()</v> + <v>PartialKey = partialkey()</v> + <v>Session = session()</v> </type> <desc> - <p>Selects sessions that could be reused. Should be callable + <p>Selects sessions that can be reused. Is to be callable from any process. </p> </desc> @@ -129,7 +155,7 @@ <fsummary>Called by the process that handles the cache when it is about to terminate.</fsummary> <type> - <v>Cache = term() - as returned by init/0</v> + <v>Cache = term() - as returned by init/0</v> </type> <desc> <p>Takes care of possible cleanup that is needed when the @@ -140,15 +166,15 @@ <func> <name>update(Cache, Key, Session) -> _</name> - <fsummary> Caches a new session or updates a already cached one.</fsummary> + <fsummary>Caches a new session or updates an already cached one.</fsummary> <type> - <v> Cache = cache_ref()</v> - <v> Key = key()</v> - <v> Session = session()</v> + <v>Cache = cache_ref()</v> + <v>Key = key()</v> + <v>Session = session()</v> </type> <desc> - <p> Caches a new session or updates a already cached one. Will - only be called from the cache handling process. + <p>Caches a new session or updates an already cached one. Is + only called from the cache handling process. </p> </desc> </func> diff --git a/lib/ssl/doc/src/usersguide.xml b/lib/ssl/doc/src/usersguide.xml index 6528c00a0b..23ccf668c3 100644 --- a/lib/ssl/doc/src/usersguide.xml +++ b/lib/ssl/doc/src/usersguide.xml @@ -1,36 +1,40 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE part SYSTEM "part.dtd"> <part xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>2000</year><year>2010</year> + <year>2000</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> <title>SSL User's Guide</title> <prepared>OTP Team</prepared> + <docno></docno> <date>2003-05-26</date> + <rev></rev> <file>usersguide.sgml</file> </header> <description> - <p>The <em>SSL</em> application provides secure communication over + <p>The Secure Socket Layer (SSL) application provides secure communication over sockets. </p> </description> + <xi:include href="ssl_introduction.xml"/> <xi:include href="ssl_protocol.xml"/> <xi:include href="using_ssl.xml"/> <xi:include href="ssl_distribution.xml"/> diff --git a/lib/ssl/doc/src/using_ssl.xml b/lib/ssl/doc/src/using_ssl.xml index ab837a156a..f84cd6e391 100644 --- a/lib/ssl/doc/src/using_ssl.xml +++ b/lib/ssl/doc/src/using_ssl.xml @@ -1,146 +1,152 @@ -<?xml version="1.0" encoding="latin1" ?> +<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE chapter SYSTEM "chapter.dtd"> <chapter> <header> <copyright> - <year>2003</year><year>2011</year> + <year>2003</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> - The contents of this file are subject to the Erlang Public License, - Version 1.1, (the "License"); you may not use this file except in - compliance with the License. You should have received a copy of the - Erlang Public License along with this software. If not, it can be - retrieved online at http://www.erlang.org/. - - Software distributed under the License is distributed on an "AS IS" - basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See - the License for the specific language governing rights and limitations - under the License. + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. </legalnotice> - <title>Using the SSL API</title> + <title>Using SSL API</title> + <prepared></prepared> + <responsible></responsible> + <docno></docno> + <approved></approved> + <checked></checked> + <date></date> + <rev></rev> <file>using_ssl.xml</file> </header> - - <section> - <title>General information</title> - <p>To see relevant version information for ssl you can - call ssl:versions/0</p> + <p>To see relevant version information for ssl, call + <seealso marker="ssl:ssl#versions-0"><c>ssl:versions/0</c></seealso> + .</p> - <p>To see all supported cipher suites - call ssl:cipher_suites/0. Note that available cipher suites - for a connection will depend on your certificate. It is also - possible to specify a specific cipher suite(s) that you - want your connection to use. Default is to use the strongest - available.</p> - - </section> + <p>To see all supported cipher suites, call <seealso marker="ssl:ssl#cipher_suites-1"><c>ssl:cipher_suites(all)</c> </seealso>. + The available cipher suites for a connection depend on your certificate. + Specific cipher suites that you want your connection to use can also be + specified. Default is to use the strongest available.</p> <section> - <title>Setting up connections</title> + <title>Setting up Connections</title> - <p>Here follows some small example of how to set up client/server connections - using the erlang shell. The returned value of the sslsocket has been abbreviated with - <c>[...]</c> as it can be fairly large and is opaque.</p> + <p>This section shows a small example of how to set up client/server connections + using the Erlang shell. The returned value of the <c>sslsocket</c> is abbreviated + with <c>[...]</c> as it can be fairly large and is opaque.</p> <section> - <title>Minmal example</title> + <title>Minimal Example</title> - <note><p> The minimal setup is not the most secure setup of ssl.</p> + <note><p> The minimal setup is not the most secure setup of SSL.</p> </note> - - <p> Start server side</p> + + <p>To set up client/server connections:</p> + + <p><em>Step 1:</em> Start the server side:</p> <code type="erl">1 server> ssl:start(). ok</code> - <p>Create an ssl listen socket</p> + <p><em>Step 2:</em> Create an SSL listen socket:</p> <code type="erl">2 server> {ok, ListenSocket} = ssl:listen(9999, [{certfile, "cert.pem"}, {keyfile, "key.pem"},{reuseaddr, true}]). {ok,{sslsocket, [...]}}</code> - <p>Do a transport accept on the ssl listen socket</p> + <p><em>Step 3:</em> Do a transport accept on the SSL listen socket:</p> <code type="erl">3 server> {ok, Socket} = ssl:transport_accept(ListenSocket). {ok,{sslsocket, [...]}}</code> - <p>Start client side</p> + <p><em>Step 4:</em> Start the client side:</p> <code type="erl">1 client> ssl:start(). ok</code> <code type="erl">2 client> {ok, Socket} = ssl:connect("localhost", 9999, [], infinity). {ok,{sslsocket, [...]}}</code> - <p>Do the ssl handshake</p> + <p><em>Step 5:</em> Do the SSL handshake:</p> <code type="erl">4 server> ok = ssl:ssl_accept(Socket). ok</code> - <p>Send a messag over ssl</p> + <p><em>Step 6:</em> Send a message over SSL:</p> <code type="erl">5 server> ssl:send(Socket, "foo"). ok</code> - <p>Flush the shell message queue to see that we got the message - sent on the server side</p> + <p><em>Step 7:</em> Flush the shell message queue to see that the message + was sent on the server side:</p> <code type="erl">3 client> flush(). Shell got {ssl,{sslsocket,[...]},"foo"} ok</code> </section> <section> - <title>Upgrade example</title> + <title>Upgrade Example</title> - <note><p> To upgrade a TCP/IP connection to an ssl connection the - client and server have to aggre to do so. Agreement - may be accompliced by using a protocol such the one used by HTTP - specified in RFC 2817.</p> </note> + <note><p>To upgrade a TCP/IP connection to an SSL connection, the + client and server must agree to do so. The agreement + can be accomplished by using a protocol, for example, the one used by HTTP + specified in RFC 2817.</p></note> + + <p>To upgrade to an SSL connection:</p> - <p>Start server side</p> + <p><em>Step 1:</em> Start the server side:</p> <code type="erl">1 server> ssl:start(). ok</code> - <p>Create a normal tcp listen socket</p> + <p><em>Step 2:</em> Create a normal TCP listen socket:</p> <code type="erl">2 server> {ok, ListenSocket} = gen_tcp:listen(9999, [{reuseaddr, true}]). {ok, #Port<0.475>}</code> - <p>Accept client connection</p> + <p><em>Step 3:</em> Accept client connection:</p> <code type="erl">3 server> {ok, Socket} = gen_tcp:accept(ListenSocket). {ok, #Port<0.476>}</code> - <p>Start client side</p> + <p><em>Step 4:</em> Start the client side:</p> <code type="erl">1 client> ssl:start(). ok</code> <code type="erl">2 client> {ok, Socket} = gen_tcp:connect("localhost", 9999, [], infinity).</code> - <p>Make sure active is set to false before trying - to upgrade a connection to an ssl connection, otherwhise - ssl handshake messages may be deliverd to the wrong process.</p> + <p><em>Step 5:</em> Ensure <c>active</c> is set to <c>false</c> before trying + to upgrade a connection to an SSL connection, otherwise + SSL handshake messages can be delivered to the wrong process:</p> <code type="erl">4 server> inet:setopts(Socket, [{active, false}]). ok</code> - <p>Do the ssl handshake.</p> + <p><em>Step 6:</em> Do the SSL handshake:</p> <code type="erl">5 server> {ok, SSLSocket} = ssl:ssl_accept(Socket, [{cacertfile, "cacerts.pem"}, {certfile, "cert.pem"}, {keyfile, "key.pem"}]). {ok,{sslsocket,[...]}}</code> - <p> Upgrade to an ssl connection. Note that the client and server - must agree upon the upgrade and the server must call - ssl:accept/2 before the client calls ssl:connect/3.</p> + <p><em>Step 7:</em> Upgrade to an SSL connection. The client and server + must agree upon the upgrade. The server must call + <c>ssl:accept/2</c> before the client calls <c>ssl:connect/3.</c></p> <code type="erl">3 client>{ok, SSLSocket} = ssl:connect(Socket, [{cacertfile, "cacerts.pem"}, {certfile, "cert.pem"}, {keyfile, "key.pem"}], infinity). {ok,{sslsocket,[...]}}</code> - <p>Send a messag over ssl</p> + <p><em>Step 8:</em> Send a message over SSL:</p> <code type="erl">4 client> ssl:send(SSLSocket, "foo"). ok</code> - <p>Set active true on the ssl socket</p> + <p><em>Step 9:</em> Set <c>active true</c> on the SSL socket:</p> <code type="erl">4 server> ssl:setopts(SSLSocket, [{active, true}]). ok</code> - <p>Flush the shell message queue to see that we got the message - sent on the client side</p> + <p><em>Step 10:</em> Flush the shell message queue to see that the message + was sent on the client side:</p> <code type="erl">5 server> flush(). Shell got {ssl,{sslsocket,[...]},"foo"} ok</code> diff --git a/lib/ssl/examples/certs/Makefile b/lib/ssl/examples/certs/Makefile index be2546fe82..5c456c6a1a 100644 --- a/lib/ssl/examples/certs/Makefile +++ b/lib/ssl/examples/certs/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2003-2012. All Rights Reserved. +# Copyright Ericsson AB 2003-2016. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. -# -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # diff --git a/lib/ssl/examples/src/Makefile b/lib/ssl/examples/src/Makefile index 3e033c08d9..7335bb2bb8 100644 --- a/lib/ssl/examples/src/Makefile +++ b/lib/ssl/examples/src/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 2003-2012. All Rights Reserved. +# Copyright Ericsson AB 2003-2016. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. -# -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # diff --git a/lib/ssl/examples/src/client_server.erl b/lib/ssl/examples/src/client_server.erl index 133a1764bc..c150f43bff 100644 --- a/lib/ssl/examples/src/client_server.erl +++ b/lib/ssl/examples/src/client_server.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2003-2012. All Rights Reserved. +%% Copyright Ericsson AB 2003-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -25,9 +26,7 @@ start() -> %% Start ssl application - application:start(crypto), - application:start(public_key), - application:start(ssl), + {ok, StartedApps} = application:ensure_all_started(ssl), %% Let the current process be the server that listens and accepts %% Listen @@ -51,7 +50,8 @@ start() -> ssl:close(ASock), io:fwrite("Listen: closing and terminating.~n"), ssl:close(LSock), - application:stop(ssl). + + lists:foreach(fun application:stop/1, lists:reverse(StartedApps)). %% Client connect diff --git a/lib/ssl/internal_doc/ssl-implementation.txt b/lib/ssl/internal_doc/ssl-implementation.txt deleted file mode 100644 index e5d6ac8cd0..0000000000 --- a/lib/ssl/internal_doc/ssl-implementation.txt +++ /dev/null @@ -1,52 +0,0 @@ - -Important modules: - - module behaviour children - ------ --------- - ssl_app application ssl_sup - ssl_sup supervisor ssl_server, ssl_broker_sup - ssl_server gen_server - - ssl_broker_sup supervisor ssl_broker - ssl_broker gen_server - - -The ssl_server controls a port program that implements the SSL functionality. -That port program uses the OpenSSL package. - -Each socket has a corresponding broker (listen, accept or connect). A broker -is created and supervised by the ssl_broker_sup. - -All communication is between a user and a broker. The broker communicates -with the ssl_server, that sends its commands to the port program and handles -the port program responses, that are distributed to users through the -brokers. - -There is a distinction between commands and data flow between the ssl_server -and the port program. Each established connection between the user and the -outside world consists of a local erlang socket (owned by the broker) that -is read from and written to by the broker. At the other end of the local -connection is a local socket in the port program. - -The "real" socket that connects to the outside world is in the port program -(including listen sockets). The main purpose of the port program is to -shuffle data between local sockets and outside world sockets, and detect and -propagate read and write errors (including detection of closed sockets) to -the ssl_server. - -There is documentation in the ssl_broker.erl module. - -There is also documentation in the esock.c and esock_openssl.c files. - -The ssl_pem.erl, ssl_pkix.erl and ssl_base64.erl modules are support -modules for reading SSL certificates. Modules for parsing certificates -are generated from ASN.1 modules in the `pkix' directory. - -The `examples' directory contains functions for generating certificates. -Those certificates are used in the test suites. - - - - - - - - diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile index cf9f7d5001..b625db0656 100644 --- a/lib/ssl/src/Makefile +++ b/lib/ssl/src/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2013. All Rights Reserved. +# Copyright Ericsson AB 1999-2015. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # @@ -38,7 +39,8 @@ RELSYSDIR = $(RELEASE_PATH)/lib/ssl-$(VSN) # ---------------------------------------------------- BEHAVIOUR_MODULES= \ - ssl_session_cache_api + ssl_session_cache_api \ + ssl_crl_cache_api MODULES= \ ssl \ @@ -49,29 +51,41 @@ MODULES= \ ssl_dist_sup\ ssl_sup \ inet_tls_dist \ + inet6_tls_dist \ ssl_certificate\ ssl_pkix_db\ ssl_cipher \ ssl_srp_primes \ tls_connection \ dtls_connection \ - ssl_connection_sup \ + ssl_config \ + ssl_connection \ + tls_connection_sup \ + dtls_connection_sup \ tls_handshake \ dtls_handshake\ + ssl_handshake\ ssl_manager \ ssl_session \ ssl_session_cache \ + ssl_crl\ + ssl_crl_cache \ + ssl_crl_hash_dir \ ssl_socket \ + ssl_listen_tracker_sup \ tls_record \ dtls_record \ - ssl_ssl2 \ - ssl_ssl3 \ - ssl_tls1 \ + ssl_record \ + ssl_v2 \ + ssl_v3 \ + tls_v1 \ + dtls_v1 \ ssl_tls_dist_proxy INTERNAL_HRL_FILES = \ - ssl_alert.hrl ssl_cipher.hrl ssl_handshake.hrl tls_handshake.hrl \ - dtls_handshake.hrl ssl_internal.hrl \ + ssl_alert.hrl ssl_cipher.hrl \ + tls_connection.hrl dtls_connection.hrl ssl_connection.hrl \ + ssl_handshake.hrl tls_handshake.hrl dtls_handshake.hrl ssl_api.hrl ssl_internal.hrl \ ssl_record.hrl tls_record.hrl dtls_record.hrl ssl_srp.hrl ERL_FILES= \ @@ -110,7 +124,7 @@ $(TARGET_FILES): $(BEHAVIOUR_TARGET_FILES) debug opt: $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) clean: - rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) + rm -f $(TARGET_FILES) $(APP_TARGET) $(APPUP_TARGET) $(BEHAVIOUR_TARGET_FILES) rm -f errs core *~ $(APP_TARGET): $(APP_SRC) ../vsn.mk @@ -145,9 +159,10 @@ $(EBIN)/ssl_alert.$(EMULATOR): ssl_alert.hrl ssl_record.hrl $(EBIN)/ssl_certificate.$(EMULATOR): ssl_internal.hrl ssl_alert.hrl ssl_handshake.hrl ../../public_key/include/public_key.hrl $(EBIN)/ssl_certificate_db.$(EMULATOR): ssl_internal.hrl ../../public_key/include/public_key.hrl ../../kernel/include/file.hrl $(EBIN)/ssl_cipher.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl -$(EBIN)/tls_connection.$(EMULATOR): ssl_internal.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl -$(EBIN)/dtls_connection.$(EMULATOR): ssl_internal.hrl dtls_record.hrl ssl_cipher.hrl dtls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl +$(EBIN)/tls_connection.$(EMULATOR): ssl_internal.hrl tls_connection.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl +$(EBIN)/dtls_connection.$(EMULATOR): ssl_internal.hrl dtls_connection.hrl dtls_record.hrl ssl_cipher.hrl dtls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl $(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl tls_record.hrl ssl_cipher.hrl tls_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl +$(EBIN)/tls_handshake.$(EMULATOR): ssl_internal.hrl ssl_connection.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl ../../public_key/include/public_key.hrl $(EBIN)/ssl_manager.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl ../../kernel/include/file.hrl $(EBIN)/ssl_record.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl ssl_handshake.hrl ssl_alert.hrl $(EBIN)/ssl_session.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl @@ -155,5 +170,5 @@ $(EBIN)/ssl_session_cache.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl $(EBIN)/ssl_session_cache_api.$(EMULATOR): ssl_internal.hrl ssl_handshake.hrl $(EBIN)/ssl_ssl3.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl $(EBIN)/ssl_tls1.$(EMULATOR): ssl_internal.hrl ssl_record.hrl ssl_cipher.hrl - +$(EBIN)/ssl_cache.$(EMULATOR): ssl_cache.erl ssl_internal.hrl ../../public_key/include/public_key.hrl diff --git a/lib/ssl/src/dtls.erl b/lib/ssl/src/dtls.erl index 013286c9bd..cd705152a8 100644 --- a/lib/ssl/src/dtls.erl +++ b/lib/ssl/src/dtls.erl @@ -1,25 +1,113 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. +%% Copyright Ericsson AB 1999-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% %% -%%% Purpose : API for DTLS. +%%% Purpose : Reflect DTLS specific API options (fairly simple wrapper at the moment) +%% First implementation will support DTLS connections only in a "TLS/TCP like way" -module(dtls). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). + +-export([connect/2, connect/3, listen/2, accept/1, accept/2, + handshake/1, handshake/2, handshake/3]). + +%%-------------------------------------------------------------------- +%% +%% Description: Connect to a DTLS server. +%%-------------------------------------------------------------------- + +-spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | + {error, reason()}. + +connect(Socket, Options) when is_port(Socket) -> + connect(Socket, Options, infinity). + +-spec connect(host() | port(), [connect_option()] | inet:port_number(), + timeout() | list()) -> + {ok, #sslsocket{}} | {error, reason()}. + +connect(Socket, SslOptions, Timeout) when is_port(Socket) -> + DTLSOpts = [{protocol, dtls} | SslOptions], + ssl:connect(Socket, DTLSOpts, Timeout); +connect(Host, Port, Options) -> + connect(Host, Port, Options, infinity). + +-spec connect(host() | port(), inet:port_number(), list(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. + +connect(Host, Port, Options, Timeout) -> + DTLSOpts = [{protocol, dtls} | Options], + ssl:connect(Host, Port, DTLSOpts, Timeout). + +%%-------------------------------------------------------------------- +-spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}. + +%% +%% Description: Creates an ssl listen socket. +%%-------------------------------------------------------------------- +listen(Port, Options) -> + DTLSOpts = [{protocol, dtls} | Options], + ssl:listen(Port, DTLSOpts). + +%%-------------------------------------------------------------------- +%% +%% Description: Performs transport accept on an ssl listen socket +%%-------------------------------------------------------------------- +-spec accept(#sslsocket{}) -> {ok, #sslsocket{}} | + {error, reason()}. +accept(ListenSocket) -> + accept(ListenSocket, infinity). + +-spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | + {error, reason()}. +accept(Socket, Timeout) -> + ssl:transport_accept(Socket, Timeout). + +%%-------------------------------------------------------------------- +%% +%% Description: Performs accept on an ssl listen socket. e.i. performs +%% ssl handshake. +%%-------------------------------------------------------------------- + +-spec handshake(#sslsocket{}) -> ok | {error, reason()}. + +handshake(ListenSocket) -> + handshake(ListenSocket, infinity). + + +-spec handshake(#sslsocket{} | port(), timeout()| [ssl_option() + | transport_option()]) -> + ok | {ok, #sslsocket{}} | {error, reason()}. + +handshake(#sslsocket{} = Socket, Timeout) -> + ssl:ssl_accept(Socket, Timeout); + +handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> + handshake(ListenSocket, SslOptions, infinity). + + +-spec handshake(port(), [ssl_option()| transport_option()], timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. + +handshake(Socket, SslOptions, Timeout) when is_port(Socket) -> + ssl:ssl_accept(Socket, SslOptions, Timeout). diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index ac2ee0d09f..b8be686b99 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -1,19 +1,635 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% -module(dtls_connection). + +%% Internal application API + +-behaviour(gen_statem). + +-include("dtls_connection.hrl"). +-include("dtls_handshake.hrl"). +-include("ssl_alert.hrl"). +-include("dtls_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_api.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%% Internal application API + +%% Setup +-export([start_fsm/8, start_link/7, init/1]). + +%% State transition handling +-export([next_record/1, next_event/3]). + +%% Handshake handling +-export([%%renegotiate/2, + send_handshake/2, queue_handshake/2, queue_change_cipher/2]). + +%% Alert and close handling +-export([%%send_alert/2, handle_own_alert/4, handle_close_alert/3, + handle_normal_shutdown/3 %%, close/5 + %%alert_user/6, alert_user/9 + ]). + +%% Data handling + +-export([%%write_application_data/3, + read_application_data/2, + passive_receive/2, next_record_if_active/1%, + %%handle_common_event/4, + %handle_packet/3 + ]). + +%% gen_statem state functions +-export([init/3, error/3, downgrade/3, %% Initiation and take down states + hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states + connection/3]). +%% gen_statem callbacks +-export([terminate/3, code_change/4, format_status/2]). + +-define(GEN_STATEM_CB_MODE, state_functions). + +%%==================================================================== +%% Internal application API +%%==================================================================== +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts, + User, {CbModule, _,_, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = dtls_connection_sup:start_child([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), + ok = ssl_connection:handshake(SslSocket, Timeout), + {ok, SslSocket} + catch + error:{badmatch, {error, _} = Error} -> + Error + end; + +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = Opts, + User, {CbModule, _,_, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = dtls_connection_sup:start_child_dist([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), + ok = ssl_connection:handshake(SslSocket, Timeout), + {ok, SslSocket} + catch + error:{badmatch, {error, _} = Error} -> + Error + end. + +send_handshake(Handshake, State) -> + send_handshake_flight(queue_handshake(Handshake, State)). + +queue_flight_buffer(Msg, #state{negotiated_version = Version, + connection_states = #connection_states{ + current_write = + #connection_state{epoch = Epoch}}, + flight_buffer = Flight} = State) -> + State#state{flight_buffer = Flight ++ [{Version, Epoch, Msg}]}. + +queue_handshake(Handshake, #state{negotiated_version = Version, + tls_handshake_history = Hist0, + connection_states = ConnectionStates0} = State0) -> + {Frag, ConnectionStates, Hist} = + encode_handshake(Handshake, Version, ConnectionStates0, Hist0), + queue_flight_buffer(Frag, State0#state{connection_states = ConnectionStates, + tls_handshake_history = Hist}). + +send_handshake_flight(#state{socket = Socket, + transport_cb = Transport, + flight_buffer = Flight, + connection_states = ConnectionStates0} = State0) -> + + {Encoded, ConnectionStates} = + encode_handshake_flight(Flight, ConnectionStates0), + + Transport:send(Socket, Encoded), + State0#state{flight_buffer = [], connection_states = ConnectionStates}. + +queue_change_cipher(Msg, State) -> + queue_flight_buffer(Msg, State). + +send_alert(Alert, #state{negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0} = State0) -> + {BinMsg, ConnectionStates} = + ssl_alert:encode(Alert, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + State0#state{connection_states = ConnectionStates}. + +%%==================================================================== +%% tls_connection_sup API +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec start_link(atom(), host(), inet:port_number(), port(), list(), pid(), tuple()) -> + {ok, pid()} | ignore | {error, reason()}. +%% +%% Description: Creates a gen_fsm process which calls Module:init/1 to +%% initialize. To ensure a synchronized start-up procedure, this function +%% does not return until Module:init/1 has returned. +%%-------------------------------------------------------------------- +start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> + {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. + +init([Role, Host, Port, Socket, Options, User, CbInfo]) -> + process_flag(trap_exit, true), + State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), + try + State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), + gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State) + catch + throw:Error -> + gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error,State0}) + end. + +%%-------------------------------------------------------------------- +%% State functionsconnection/2 +%%-------------------------------------------------------------------- + +init({call, From}, {start, Timeout}, + #state{host = Host, port = Port, role = client, + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + transport_cb = Transport, socket = Socket, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}, + session_cache = Cache, + session_cache_cb = CacheCb + } = State0) -> + Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), + Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Cache, CacheCb, Renegotiation, Cert), + + Version = Hello#client_hello.client_version, + HelloVersion = dtls_record:lowest_protocol_version(SslOpts#ssl_options.versions), + Handshake0 = ssl_handshake:init_handshake_history(), + {BinMsg, ConnectionStates, Handshake} = + encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), + Transport:send(Socket, BinMsg), + State1 = State0#state{connection_states = ConnectionStates, + negotiated_version = Version, %% Requested version + session = + Session0#session{session_id = Hello#client_hello.session_id}, + tls_handshake_history = Handshake, + start_or_recv_from = From, + timer = Timer}, + {Record, State} = next_record(State1), + next_event(hello, Record, State); +init(Type, Event, State) -> + ssl_connection:init(Type, Event, State, ?MODULE). + +error({call, From}, {start, _Timeout}, {Error, State}) -> + {stop_and_reply, normal, {reply, From, {error, Error}}, State}; +error({call, From}, Msg, State) -> + handle_call(Msg, From, error, State); +error(_, _, _) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #client_hello{} | #server_hello{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello(internal, #client_hello{client_version = ClientVersion, + extensions = #hello_extensions{ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves}} = Hello, + State = #state{connection_states = ConnectionStates0, + port = Port, session = #session{own_certificate = Cert} = Session0, + renegotiation = {Renegotiation, _}, + session_cache = Cache, + session_cache_cb = CacheCb, + negotiated_protocol = CurrentProtocol, + key_algorithm = KeyExAlg, + ssl_options = SslOpts}) -> + + case dtls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, + ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of + #alert{} = Alert -> + handle_own_alert(Alert, ClientVersion, hello, State); + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + + ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt}, + State#state{connection_states = ConnectionStates, + negotiated_version = Version, + hashsign_algorithm = HashSign, + session = Session, + client_ecc = {EllipticCurves, EcPointFormats}, + negotiated_protocol = Protocol}, ?MODULE) + end; +hello(internal, #server_hello{} = Hello, + #state{connection_states = ConnectionStates0, + negotiated_version = ReqVersion, + role = client, + renegotiation = {Renegotiation, _}, + ssl_options = SslOptions} = State) -> + case dtls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of + #alert{} = Alert -> + handle_own_alert(Alert, ReqVersion, hello, State); + {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> + ssl_connection:handle_session(Hello, + Version, NewId, ConnectionStates, ProtoExt, Protocol, State) + end; +hello(info, Event, State) -> + handle_info(Event, hello, State); + +hello(Type, Event, State) -> + ssl_connection:hello(Type, Event, State, ?MODULE). + +abbreviated(info, Event, State) -> + handle_info(Event, abbreviated, State); +abbreviated(Type, Event, State) -> + ssl_connection:abbreviated(Type, Event, State, ?MODULE). + +certify(info, Event, State) -> + handle_info(Event, certify, State); +certify(Type, Event, State) -> + ssl_connection:certify(Type, Event, State, ?MODULE). + +cipher(info, Event, State) -> + handle_info(Event, cipher, State); +cipher(Type, Event, State) -> + ssl_connection:cipher(Type, Event, State, ?MODULE). + +connection(info, Event, State) -> + handle_info(Event, connection, State); +connection(internal, #hello_request{}, #state{host = Host, port = Port, + session = #session{own_certificate = Cert} = Session0, + session_cache = Cache, session_cache_cb = CacheCb, + ssl_options = SslOpts, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} = State0) -> + Hello = dtls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, + Cache, CacheCb, Renegotiation, Cert), + State1 = send_handshake(Hello, State0), + {Record, State} = + next_record( + State1#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}), + next_event(hello, Record, State); + +connection(internal, #client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> + %% Mitigate Computational DoS attack + %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html + %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client + %% initiated renegotiation we will disallow many client initiated + %% renegotiations immediately after each other. + erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), + {next_state, hello, State#state{allow_renegotiate = false}, [{next_event, internal, Hello}]}; + + +connection(internal, #client_hello{}, #state{role = server, allow_renegotiate = false} = State0) -> + Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), + State1 = send_alert(Alert, State0), + {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), + next_event(connection, Record, State); + +connection(Type, Event, State) -> + ssl_connection:connection(Type, Event, State, ?MODULE). + +downgrade(Type, Event, State) -> + ssl_connection:downgrade(Type, Event, State, ?MODULE). + + +%%-------------------------------------------------------------------- +%% Description: This function is called by a gen_fsm when it receives any +%% other message than a synchronous or asynchronous event +%% (or a system message). +%%-------------------------------------------------------------------- + +%% raw data from socket, unpack records +handle_info({Protocol, _, Data}, StateName, + #state{data_tag = Protocol} = State0) -> + case next_tls_record(Data, State0) of + {Record, State} -> + next_event(StateName, Record, State); + #alert{} = Alert -> + handle_normal_shutdown(Alert, StateName, State0), + {stop, {shutdown, own_alert}} + end; +handle_info({CloseTag, Socket}, StateName, + #state{socket = Socket, close_tag = CloseTag, + negotiated_version = Version} = State) -> + %% Note that as of DTLS 1.2 (TLS 1.1), + %% failure to properly close a connection no longer requires that a + %% session not be resumed. This is a change from DTLS 1.0 to conform + %% with widespread implementation practice. + case Version of + {254, N} when N =< 253 -> + ok; + _ -> + %% As invalidate_sessions here causes performance issues, + %% we will conform to the widespread implementation + %% practice and go aginst the spec + %%invalidate_session(Role, Host, Port, Session) + ok + end, + handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + {stop, {shutdown, transport_closed}}; +handle_info(Msg, StateName, State) -> + ssl_connection:handle_info(Msg, StateName, State). + +handle_call(Event, From, StateName, State) -> + ssl_connection:handle_call(Event, From, StateName, State, ?MODULE). + +%%-------------------------------------------------------------------- +%% Description:This function is called by a gen_fsm when it is about +%% to terminate. It should be the opposite of Module:init/1 and do any +%% necessary cleaning up. When it returns, the gen_fsm terminates with +%% Reason. The return value is ignored. +%%-------------------------------------------------------------------- +terminate(Reason, StateName, State) -> + ssl_connection:terminate(Reason, StateName, State). + +%%-------------------------------------------------------------------- +%% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, StateName, State, _Extra) -> + {?GEN_STATEM_CB_MODE, StateName, State}. + +format_status(Type, Data) -> + ssl_connection:format_status(Type, Data). + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> + {Seq, ConnectionStates} = sequence(ConnectionStates0), + {EncHandshake, Frag} = dtls_handshake:encode_handshake(Handshake, Version, Seq), + Hist = ssl_handshake:update_handshake_history(Hist0, EncHandshake), + {Frag, ConnectionStates, Hist}. + +encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> + dtls_record:encode_change_cipher_spec(Version, ConnectionStates). + +encode_handshake_flight(Flight, ConnectionStates) -> + MSS = 1400, + encode_handshake_records(Flight, ConnectionStates, MSS, init_pack_records()). + +encode_handshake_records([], CS, _MSS, Recs) -> + {finish_pack_records(Recs), CS}; + +encode_handshake_records([{Version, _Epoch, Frag = #change_cipher_spec{}}|Tail], ConnectionStates0, MSS, Recs0) -> + {Encoded, ConnectionStates} = + encode_change_cipher(Frag, Version, ConnectionStates0), + Recs = append_pack_records([Encoded], MSS, Recs0), + encode_handshake_records(Tail, ConnectionStates, MSS, Recs); + +encode_handshake_records([{Version, Epoch, {MsgType, MsgSeq, Bin}}|Tail], CS0, MSS, Recs0 = {Buf0, _}) -> + Space = MSS - iolist_size(Buf0), + Len = byte_size(Bin), + {Encoded, CS} = + encode_handshake_record(Version, Epoch, Space, MsgType, MsgSeq, Len, Bin, 0, MSS, [], CS0), + Recs = append_pack_records(Encoded, MSS, Recs0), + encode_handshake_records(Tail, CS, MSS, Recs). + +%% TODO: move to dtls_handshake???? +encode_handshake_record(_Version, _Epoch, _Space, _MsgType, _MsgSeq, _Len, <<>>, _Offset, _MRS, Encoded, CS) + when length(Encoded) > 0 -> + %% make sure we encode at least one segment (for empty messages like Server Hello Done + {lists:reverse(Encoded), CS}; + +encode_handshake_record(Version, Epoch, Space, MsgType, MsgSeq, Len, Bin, + Offset, MRS, Encoded0, CS0) -> + MaxFragmentLen = Space - 25, + case Bin of + <<BinFragment:MaxFragmentLen/bytes, Rest/binary>> -> + ok; + _ -> + BinFragment = Bin, + Rest = <<>> + end, + FragLength = byte_size(BinFragment), + Frag = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragLength), BinFragment], + %% TODO Real solution, now avoid dialyzer error {Encoded, CS} = ssl_record:encode_handshake({Epoch, Frag}, Version, CS0), + {Encoded, CS} = ssl_record:encode_handshake(Frag, Version, CS0), + encode_handshake_record(Version, Epoch, MRS, MsgType, MsgSeq, Len, Rest, Offset + FragLength, MRS, [Encoded|Encoded0], CS). + +init_pack_records() -> + {[], []}. + +append_pack_records([], MSS, Recs = {Buf0, Acc0}) -> + Remaining = MSS - iolist_size(Buf0), + if Remaining < 12 -> + {[], [lists:reverse(Buf0)|Acc0]}; + true -> + Recs + end; +append_pack_records([Head|Tail], MSS, {Buf0, Acc0}) -> + TotLen = iolist_size(Buf0) + iolist_size(Head), + if TotLen > MSS -> + append_pack_records(Tail, MSS, {[Head], [lists:reverse(Buf0)|Acc0]}); + true -> + append_pack_records(Tail, MSS, {[Head|Buf0], Acc0}) + end. + +finish_pack_records({[], Acc}) -> + lists:reverse(Acc); +finish_pack_records({Buf, Acc}) -> + lists:reverse([lists:reverse(Buf)|Acc]). + +%% decode_alerts(Bin) -> +%% ssl_alert:decode(Bin). + +initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, + {CbModule, DataTag, CloseTag, ErrorTag}) -> + #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, + ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation), + + SessionCacheCb = case application:get_env(ssl, session_cb) of + {ok, Cb} when is_atom(Cb) -> + Cb; + _ -> + ssl_session_cache + end, + + Monitor = erlang:monitor(process, User), + + #state{socket_options = SocketOptions, + %% We do not want to save the password in the state so that + %% could be written in the clear into error logs. + ssl_options = SSLOptions#ssl_options{password = undefined}, + session = #session{is_resumable = new}, + transport_cb = CbModule, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + role = Role, + host = Host, + port = Port, + socket = Socket, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_application = {Monitor, User}, + user_data_buffer = <<>>, + session_cache_cb = SessionCacheCb, + renegotiation = {false, first}, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, + start_or_recv_from = undefined, + protocol_cb = ?MODULE + }. + +next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{ + dtls_record_buffer = Buf0, + dtls_cipher_texts = CT0} = Buffers} = State0) -> + case dtls_record:get_dtls_records(Data, Buf0) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(State0#state{protocol_buffers = + Buffers#protocol_buffers{dtls_record_buffer = Buf1, + dtls_cipher_texts = CT1}}); + + #alert{} = Alert -> + Alert + end. + +next_record(#state{%%flight = #flight{state = finished}, + protocol_buffers = + #protocol_buffers{dtls_packets = [], dtls_cipher_texts = [CT | Rest]} + = Buffers, + connection_states = ConnStates0} = State) -> + case dtls_record:decode_cipher_text(CT, ConnStates0) of + {Plain, ConnStates} -> + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{dtls_cipher_texts = Rest}, + connection_states = ConnStates}}; + #alert{} = Alert -> + {Alert, State} + end; +next_record(#state{socket = Socket, + transport_cb = Transport} = State) -> %% when FlightState =/= finished + ssl_socket:setopts(Transport, Socket, [{active,once}]), + {no_record, State}; +next_record(State) -> + {no_record, State}. + +next_record_if_active(State = + #state{socket_options = + #socket_options{active = false}}) -> + {no_record ,State}; + +next_record_if_active(State) -> + next_record(State). + +passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> + case Buffer of + <<>> -> + {Record, State} = next_record(State0), + next_event(StateName, Record, State); + _ -> + {Record, State} = read_application_data(<<>>, State0), + next_event(StateName, Record, State) + end. + +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). + +next_event(connection = StateName, no_record, State0, Actions) -> + case next_record_if_active(State0) of + {no_record, State} -> + ssl_connection:hibernate_after(StateName, State, Actions); + {#ssl_tls{} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]}; + {#alert{} = Alert, State} -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} + end; +next_event(StateName, Record, State, Actions) -> + case Record of + no_record -> + {next_state, StateName, State, Actions}; + #ssl_tls{} = Record -> + {next_state, StateName, State, [{next_event, internal, {dtls_record, Record}} | Actions]}; + #alert{} = Alert -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} + end. + +read_application_data(_,State) -> + {#ssl_tls{fragment = <<"place holder">>}, State}. + +handle_own_alert(_,_,_, State) -> %% Place holder + {stop, {shutdown, own_alert}, State}. + +handle_normal_shutdown(_, _, _State) -> %% Place holder + ok. + +%% TODO This generates dialyzer warnings, has to be handled differently. +%% handle_packet(Address, Port, Packet) -> +%% try dtls_record:get_dtls_records(Packet, <<>>) of +%% %% expect client hello +%% {[#ssl_tls{type = ?HANDSHAKE, version = {254, _}} = Record], <<>>} -> +%% handle_dtls_client_hello(Address, Port, Record); +%% _Other -> +%% {error, not_dtls} +%% catch +%% _Class:_Error -> +%% {error, not_dtls} +%% end. + +%% handle_dtls_client_hello(Address, Port, +%% #ssl_tls{epoch = Epoch, sequence_number = Seq, +%% version = Version} = Record) -> +%% {[{Hello, _}], _} = +%% dtls_handshake:get_dtls_handshake(Record, +%% dtls_handshake:dtls_handshake_new_flight(undefined)), +%% #client_hello{client_version = {Major, Minor}, +%% random = Random, +%% session_id = SessionId, +%% cipher_suites = CipherSuites, +%% compression_methods = CompressionMethods} = Hello, +%% CookieData = [address_to_bin(Address, Port), +%% <<?BYTE(Major), ?BYTE(Minor)>>, +%% Random, SessionId, CipherSuites, CompressionMethods], +%% Cookie = crypto:hmac(sha, <<"secret">>, CookieData), + +%% case Hello of +%% #client_hello{cookie = Cookie} -> +%% accept; + +%% _ -> +%% %% generate HelloVerifyRequest +%% {RequestFragment, _} = dtls_handshake:encode_handshake( +%% dtls_handshake:hello_verify_request(Cookie), +%% Version, 0), +%% HelloVerifyRequest = +%% dtls_record:encode_tls_cipher_text(?HANDSHAKE, Version, Epoch, Seq, RequestFragment), +%% {reply, HelloVerifyRequest} +%% end. + +%% address_to_bin({A,B,C,D}, Port) -> +%% <<0:80,16#ffff:16,A,B,C,D,Port:16>>; +%% address_to_bin({A,B,C,D,E,F,G,H}, Port) -> +%% <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>. + +sequence(#connection_states{dtls_write_msg_seq = Seq} = CS) -> + {Seq, CS#connection_states{dtls_write_msg_seq = Seq + 1}}. diff --git a/lib/ssl/src/dtls_connection.hrl b/lib/ssl/src/dtls_connection.hrl new file mode 100644 index 0000000000..ee3daa3c14 --- /dev/null +++ b/lib/ssl/src/dtls_connection.hrl @@ -0,0 +1,48 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: SSL/TLS specific state +%%---------------------------------------------------------------------- + +-ifndef(dtls_connection). +-define(dtls_connection, true). + +-include("ssl_connection.hrl"). + +-record(protocol_buffers, { + dtls_packets = [], %%::[binary()], % Not yet handled decode ssl/tls packets. + dtls_record_buffer = <<>>, %%:: binary(), % Buffer of incomplete records + dtls_fragment_state, %%:: [], % DTLS fragments + dtls_handshake_buffer = <<>>, %%:: binary(), % Buffer of incomplete handshakes + dtls_cipher_texts = [], %%:: [binary()], + dtls_cipher_texts_next %%:: [binary()] % Received for Epoch not yet active + }). + +-record(flight, { + last_retransmit, + last_read_seq, + msl_timer, + state, + buffer % buffer of not yet ACKed TLS records + }). + +-endif. % -ifdef(dtls_connection). diff --git a/lib/ssl/src/dtls_connection_sup.erl b/lib/ssl/src/dtls_connection_sup.erl new file mode 100644 index 0000000000..dc7601a684 --- /dev/null +++ b/lib/ssl/src/dtls_connection_sup.erl @@ -0,0 +1,67 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Supervisor of DTLS connection. +%%---------------------------------------------------------------------- +-module(dtls_connection_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0, start_link_dist/0]). +-export([start_child/1, start_child_dist/1]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +start_link_dist() -> + supervisor:start_link({local, dtls_connection_sup_dist}, ?MODULE, []). + +start_child(Args) -> + supervisor:start_child(?MODULE, Args). + +start_child_dist(Args) -> + supervisor:start_child(dtls_connection_sup_dist, Args). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 0, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {dtls_connection, start_link, []}, + Restart = temporary, % E.g. should not be restarted + Shutdown = 4000, + Modules = [dtls_connection], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl index b25daa59d9..5a799cf441 100644 --- a/lib/ssl/src/dtls_handshake.erl +++ b/lib/ssl/src/dtls_handshake.erl @@ -1,18 +1,502 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% -module(dtls_handshake). + +-include("dtls_handshake.hrl"). +-include("dtls_record.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_alert.hrl"). + +-export([client_hello/8, client_hello/9, hello/4, + hello_verify_request/1, get_dtls_handshake/2, + dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1, + encode_handshake/3]). + +-type dtls_handshake() :: #client_hello{} | #hello_verify_request{} | + ssl_handshake:ssl_handshake(). + +%%==================================================================== +%% Internal application API +%%==================================================================== +%%-------------------------------------------------------------------- +-spec client_hello(host(), inet:port_number(), #connection_states{}, + #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> + #client_hello{}. +%% +%% Description: Creates a client hello message. +%%-------------------------------------------------------------------- +client_hello(Host, Port, ConnectionStates, SslOpts, + Cache, CacheCb, Renegotiation, OwnCert) -> + %% First client hello (two sent in DTLS ) uses empty Cookie + client_hello(Host, Port, <<>>, ConnectionStates, SslOpts, + Cache, CacheCb, Renegotiation, OwnCert). + +%%-------------------------------------------------------------------- +-spec client_hello(host(), inet:port_number(), term(), #connection_states{}, + #ssl_options{}, integer(), atom(), boolean(), der_cert()) -> + #client_hello{}. +%% +%% Description: Creates a client hello message. +%%-------------------------------------------------------------------- +client_hello(Host, Port, Cookie, ConnectionStates, + #ssl_options{versions = Versions, + ciphers = UserSuites + } = SslOpts, + Cache, CacheCb, Renegotiation, OwnCert) -> + Version = dtls_record:highest_protocol_version(Versions), + Pending = ssl_record:pending_connection_state(ConnectionStates, read), + SecParams = Pending#connection_state.security_parameters, + CipherSuites = ssl_handshake:available_suites(UserSuites, Version), + + Extensions = ssl_handshake:client_hello_extensions(Host, dtls_v1:corresponding_tls_version(Version), CipherSuites, + SslOpts, ConnectionStates, Renegotiation), + + Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), + + #client_hello{session_id = Id, + client_version = Version, + cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation), + compression_methods = ssl_record:compressions(), + random = SecParams#security_parameters.client_random, + cookie = Cookie, + extensions = Extensions + }. + +hello(#server_hello{server_version = Version, random = Random, + cipher_suite = CipherSuite, + compression_method = Compression, + session_id = SessionId, extensions = HelloExt}, + #ssl_options{versions = SupportedVersions} = SslOpt, + ConnectionStates0, Renegotiation) -> + case dtls_record:is_acceptable_version(Version, SupportedVersions) of + true -> + handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation); + false -> + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) + end; + +hello(#client_hello{client_version = ClientVersion} = Hello, + #ssl_options{versions = Versions} = SslOpts, + Info, Renegotiation) -> + Version = ssl_handshake:select_version(dtls_record, ClientVersion, Versions), + %% + %% TODO: handle Cipher Fallback + %% + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation). + +-spec hello_verify_request(binary()) -> #hello_verify_request{}. +%% +%% Description: Creates a hello verify request message sent by server to +%% verify client +%%-------------------------------------------------------------------- +hello_verify_request(Cookie) -> + %% TODO: DTLS Versions????? + #hello_verify_request{protocol_version = {254, 255}, cookie = Cookie}. + +%%-------------------------------------------------------------------- + +%% %%-------------------------------------------------------------------- +encode_handshake(Handshake, Version, MsgSeq) -> + {MsgType, Bin} = enc_handshake(Handshake, Version), + Len = byte_size(Bin), + Enc = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(0), ?uint24(Len), Bin], + Frag = {MsgType, MsgSeq, Bin}, + {Enc, Frag}. + +%%-------------------------------------------------------------------- +-spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | undefined) -> + {[dtls_handshake()], #dtls_hs_state{}} | {retransmit, #dtls_hs_state{}}. +%% +%% Description: Given a DTLS state and new data from ssl_record, collects +%% and returns it as a list of handshake messages, also returns a new +%% DTLS state +%%-------------------------------------------------------------------- +get_dtls_handshake(Records, undefined) -> + HsState = #dtls_hs_state{highest_record_seq = 0, + starting_read_seq = 0, + fragments = gb_trees:empty(), + completed = []}, + get_dtls_handshake(Records, HsState); +get_dtls_handshake(Records, HsState0) when is_list(Records) -> + HsState1 = lists:foldr(fun get_dtls_handshake_aux/2, HsState0, Records), + get_dtls_handshake_completed(HsState1); +get_dtls_handshake(Record, HsState0) when is_record(Record, ssl_tls) -> + HsState1 = get_dtls_handshake_aux(Record, HsState0), + get_dtls_handshake_completed(HsState1). + +%%-------------------------------------------------------------------- +-spec dtls_handshake_new_epoch(#dtls_hs_state{}) -> #dtls_hs_state{}. +%% +%% Description: Reset the DTLS decoder state for a new Epoch +%%-------------------------------------------------------------------- +%% dtls_handshake_new_epoch(<<>>) -> +%% dtls_hs_state_init(); +dtls_handshake_new_epoch(HsState) -> + HsState#dtls_hs_state{highest_record_seq = 0, + starting_read_seq = HsState#dtls_hs_state.current_read_seq, + fragments = gb_trees:empty(), completed = []}. + +%-------------------------------------------------------------------- +-spec dtls_handshake_new_flight(integer() | undefined) -> #dtls_hs_state{}. +% +% Description: Init the DTLS decoder state for a new Flight +dtls_handshake_new_flight(ExpectedReadReq) -> + #dtls_hs_state{current_read_seq = ExpectedReadReq, + highest_record_seq = 0, + starting_read_seq = 0, + fragments = gb_trees:empty(), completed = []}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +handle_client_hello(Version, #client_hello{session_id = SugesstedId, + cipher_suites = CipherSuites, + compression_methods = Compressions, + random = Random, + extensions = #hello_extensions{elliptic_curves = Curves, + signature_algs = ClientHashSigns} = HelloExt}, + #ssl_options{versions = Versions, + signature_algs = SupportedHashSigns} = SslOpts, + {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) -> + case dtls_record:is_acceptable_version(Version, Versions) of + true -> + AvailableHashSigns = ssl_handshake:available_signature_algs( + ClientHashSigns, SupportedHashSigns, Cert, + dtls_v1:corresponding_tls_version(Version)), + ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)), + {Type, #session{cipher_suite = CipherSuite} = Session1} + = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, + Port, Session0#session{ecc = ECCCurve}, Version, + SslOpts, Cache, CacheCb, Cert), + case CipherSuite of + no_suite -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); + _ -> + {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite), + case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of + #alert{} = Alert -> + Alert; + HashSign -> + handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt, + SslOpts, Session1, ConnectionStates0, + Renegotiation, HashSign) + end + end; + false -> + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) + end. + +handle_client_hello_extensions(Version, Type, Random, CipherSuites, + HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) -> + try ssl_handshake:handle_client_hello_extensions(dtls_record, Random, CipherSuites, + HelloExt, dtls_v1:corresponding_tls_version(Version), + SslOpts, Session0, ConnectionStates0, Renegotiation) of + #alert{} = Alert -> + Alert; + {Session, ConnectionStates, Protocol, ServerHelloExt} -> + {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} + catch throw:Alert -> + Alert + end. + +handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + case ssl_handshake:handle_server_hello_extensions(dtls_record, Random, CipherSuite, + Compression, HelloExt, + dtls_v1:corresponding_tls_version(Version), + SslOpt, ConnectionStates0, Renegotiation) of + #alert{} = Alert -> + Alert; + {ConnectionStates, ProtoExt, Protocol} -> + {Version, SessionId, ConnectionStates, ProtoExt, Protocol} + end. + +get_dtls_handshake_completed(HsState = #dtls_hs_state{completed = Completed}) -> + {lists:reverse(Completed), HsState#dtls_hs_state{completed = []}}. + +get_dtls_handshake_aux(#ssl_tls{version = Version, + sequence_number = SeqNo, + fragment = Data}, HsState) -> + get_dtls_handshake_aux(Version, SeqNo, Data, HsState). + +get_dtls_handshake_aux(Version, SeqNo, + <<?BYTE(Type), ?UINT24(Length), + ?UINT16(MessageSeq), + ?UINT24(FragmentOffset), ?UINT24(FragmentLength), + Body:FragmentLength/binary, Rest/binary>>, + HsState0) -> + case reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, HsState0) of + {HsState1, HighestSeqNo, MsgBody} -> + HsState2 = dec_dtls_fragment(Version, HighestSeqNo, Type, Length, MessageSeq, MsgBody, HsState1), + HsState3 = process_dtls_fragments(Version, HsState2), + get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3); + + HsState2 -> + HsState3 = process_dtls_fragments(Version, HsState2), + get_dtls_handshake_aux(Version, SeqNo, Rest, HsState3) + end; + +get_dtls_handshake_aux(_Version, _SeqNo, <<>>, HsState) -> + HsState. + +dec_dtls_fragment(Version, SeqNo, Type, Length, MessageSeq, MsgBody, + HsState = #dtls_hs_state{highest_record_seq = HighestSeqNo, completed = Acc}) -> + Raw = <<?BYTE(Type), ?UINT24(Length), ?UINT16(MessageSeq), ?UINT24(0), ?UINT24(Length), MsgBody/binary>>, + H = decode_handshake(Version, Type, MsgBody), + HsState#dtls_hs_state{completed = [{H,Raw}|Acc], highest_record_seq = erlang:max(HighestSeqNo, SeqNo)}. + +process_dtls_fragments(Version, + HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, + fragments = Fragments0}) -> + case gb_trees:is_empty(Fragments0) of + true -> + HsState0; + _ -> + case gb_trees:smallest(Fragments0) of + {CurrentReadSeq, {SeqNo, Type, Length, CurrentReadSeq, {Length, [{0, Length}], MsgBody}}} -> + HsState1 = dtls_hs_state_process_seq(HsState0), + HsState2 = dec_dtls_fragment(Version, SeqNo, Type, Length, CurrentReadSeq, MsgBody, HsState1), + process_dtls_fragments(Version, HsState2); + _ -> + HsState0 + end + end. + +dtls_hs_state_process_seq(HsState0 = #dtls_hs_state{current_read_seq = CurrentReadSeq, + fragments = Fragments0}) -> + Fragments1 = gb_trees:delete_any(CurrentReadSeq, Fragments0), + HsState0#dtls_hs_state{current_read_seq = CurrentReadSeq + 1, + fragments = Fragments1}. + +dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState0 = #dtls_hs_state{fragments = Fragments0}) -> + Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), + HsState0#dtls_hs_state{fragments = Fragments1}. + +reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, + Body, HsState0 = #dtls_hs_state{current_read_seq = undefined}) + when Type == ?CLIENT_HELLO; + Type == ?SERVER_HELLO; + Type == ?HELLO_VERIFY_REQUEST -> + %% First message, should be client hello + %% return the current message and set the next expected Sequence + %% + %% Note: this could (should?) be restricted further, ClientHello and + %% HelloVerifyRequest have to have message_seq = 0, ServerHello + %% can have a message_seq of 0 or 1 + %% + {HsState0#dtls_hs_state{current_read_seq = MessageSeq + 1}, SeqNo, Body}; + +reassemble_dtls_fragment(_SeqNo, _Type, Length, _MessageSeq, _, Length, + _Body, HsState = #dtls_hs_state{current_read_seq = undefined}) -> + %% not what we expected, drop it + HsState; + +reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, + Body, HsState0 = + #dtls_hs_state{starting_read_seq = StartingReadSeq}) + when MessageSeq < StartingReadSeq -> + %% this has to be the start of a new flight, let it through + %% + %% Note: this could (should?) be restricted further, the first message of a + %% new flight has to have message_seq = 0 + %% + HsState = dtls_hs_state_process_seq(HsState0), + {HsState, SeqNo, Body}; + +reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, 0, Length, + _Body, HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) + when MessageSeq < CurrentReadSeq -> + HsState; + +reassemble_dtls_fragment(SeqNo, _Type, Length, MessageSeq, 0, Length, + Body, HsState0 = #dtls_hs_state{current_read_seq = MessageSeq}) -> + %% Message fully contained and it's the current seq + HsState1 = dtls_hs_state_process_seq(HsState0), + {HsState1, SeqNo, Body}; + +reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, 0, Length, + Body, HsState) -> + %% Message fully contained and it's the NOT the current seq -> buffer + Fragment = {SeqNo, Type, Length, MessageSeq, + dtls_fragment_init(Length, 0, Length, Body)}, + dtls_hs_state_add_fragment(MessageSeq, Fragment, HsState); + +reassemble_dtls_fragment(_SeqNo, _Type, Length, MessageSeq, FragmentOffset, FragmentLength, + _Body, + HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) + when FragmentOffset + FragmentLength == Length andalso MessageSeq == (CurrentReadSeq - 1) -> + {retransmit, HsState}; + +reassemble_dtls_fragment(_SeqNo, _Type, _Length, MessageSeq, _FragmentOffset, _FragmentLength, + _Body, + HsState = #dtls_hs_state{current_read_seq = CurrentReadSeq}) + when MessageSeq < CurrentReadSeq -> + HsState; + +reassemble_dtls_fragment(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, + HsState = #dtls_hs_state{fragments = Fragments0}) -> + case gb_trees:lookup(MessageSeq, Fragments0) of + {value, Fragment} -> + dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, Fragment, HsState); + none -> + dtls_fragment_start(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, HsState) + end. + +dtls_fragment_start(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, HsState = #dtls_hs_state{fragments = Fragments0}) -> + Fragment = {SeqNo, Type, Length, MessageSeq, + dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body)}, + Fragments1 = gb_trees:insert(MessageSeq, Fragment, Fragments0), + HsState#dtls_hs_state{fragments = Fragments1}. + +dtls_fragment_reassemble(SeqNo, Type, Length, MessageSeq, + FragmentOffset, FragmentLength, + Body, + {LastSeqNo, Type, Length, MessageSeq, FragBuffer0}, + HsState = #dtls_hs_state{fragments = Fragments0}) -> + FragBuffer1 = dtls_fragment_add(FragBuffer0, FragmentOffset, FragmentLength, Body), + Fragment = {erlang:max(SeqNo, LastSeqNo), Type, Length, MessageSeq, FragBuffer1}, + Fragments1 = gb_trees:enter(MessageSeq, Fragment, Fragments0), + HsState#dtls_hs_state{fragments = Fragments1}; + +%% Type, Length or Seq mismatch, drop everything... +%% Note: the RFC is not clear on how to handle this... +dtls_fragment_reassemble(_SeqNo, _Type, _Length, MessageSeq, + _FragmentOffset, _FragmentLength, _Body, _Fragment, + HsState = #dtls_hs_state{fragments = Fragments0}) -> + Fragments1 = gb_trees:delete_any(MessageSeq, Fragments0), + HsState#dtls_hs_state{fragments = Fragments1}. + +dtls_fragment_add({Length, FragmentList0, Bin0}, FragmentOffset, FragmentLength, Body) -> + Bin1 = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, Bin0), + FragmentList1 = add_fragment(FragmentList0, {FragmentOffset, FragmentLength}), + {Length, FragmentList1, Bin1}. + +dtls_fragment_init(Length, 0, Length, Body) -> + {Length, [{0, Length}], Body}; +dtls_fragment_init(Length, FragmentOffset, FragmentLength, Body) -> + Bin = dtls_fragment_bin_add(FragmentOffset, FragmentLength, Body, <<0:(Length*8)>>), + {Length, [{FragmentOffset, FragmentOffset + FragmentLength}], Bin}. + +dtls_fragment_bin_add(FragmentOffset, FragmentLength, Add, Buffer) -> + <<First:FragmentOffset/bytes, _:FragmentLength/bytes, Rest/binary>> = Buffer, + <<First/binary, Add/binary, Rest/binary>>. + +merge_fragment_list([], Fragment, Acc) -> + lists:reverse([Fragment|Acc]); + +merge_fragment_list([H = {_, HEnd}|Rest], Frag = {FStart, _}, Acc) + when FStart > HEnd -> + merge_fragment_list(Rest, Frag, [H|Acc]); + +merge_fragment_list(Rest = [{HStart, _HEnd}|_], Frag = {_FStart, FEnd}, Acc) + when FEnd < HStart -> + lists:reverse(Acc) ++ [Frag|Rest]; + +merge_fragment_list([{HStart, HEnd}|Rest], _Frag = {FStart, FEnd}, Acc) + when + FStart =< HEnd orelse FEnd >= HStart -> + Start = erlang:min(HStart, FStart), + End = erlang:max(HEnd, FEnd), + NewFrag = {Start, End}, + merge_fragment_list(Rest, NewFrag, Acc). + +add_fragment(List, {FragmentOffset, FragmentLength}) -> + merge_fragment_list(List, {FragmentOffset, FragmentOffset + FragmentLength}, []). + +enc_handshake(#hello_verify_request{protocol_version = {Major, Minor}, + cookie = Cookie}, _Version) -> + CookieLength = byte_size(Cookie), + {?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor), + ?BYTE(CookieLength), + Cookie/binary>>}; + +enc_handshake(#hello_request{}, _Version) -> + {?HELLO_REQUEST, <<>>}; +enc_handshake(#client_hello{client_version = {Major, Minor}, + random = Random, + session_id = SessionID, + cookie = Cookie, + cipher_suites = CipherSuites, + compression_methods = CompMethods, + extensions = HelloExtensions}, Version) -> + SIDLength = byte_size(SessionID), + BinCookie = enc_client_hello_cookie(Version, Cookie), + BinCompMethods = list_to_binary(CompMethods), + CmLength = byte_size(BinCompMethods), + BinCipherSuites = list_to_binary(CipherSuites), + CsLength = byte_size(BinCipherSuites), + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), + + {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SIDLength), SessionID/binary, + BinCookie/binary, + ?UINT16(CsLength), BinCipherSuites/binary, + ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; +enc_handshake(HandshakeMsg, Version) -> + ssl_handshake:encode_handshake(HandshakeMsg, Version). + +enc_client_hello_cookie(_, <<>>) -> + <<>>; +enc_client_hello_cookie(_, Cookie) -> + CookieLength = byte_size(Cookie), + <<?BYTE(CookieLength), Cookie/binary>>. + +decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SID_length), Session_ID:SID_length/binary, + ?BYTE(Cookie_length), Cookie:Cookie_length/binary, + ?UINT16(Cs_length), CipherSuites:Cs_length/binary, + ?BYTE(Cm_length), Comp_methods:Cm_length/binary, + Extensions/binary>>) -> + + DecodedExtensions = ssl_handshake:decode_hello_extensions(Extensions), + + #client_hello{ + client_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cookie = Cookie, + cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites), + compression_methods = Comp_methods, + extensions = DecodedExtensions + }; + +decode_handshake(_Version, ?HELLO_VERIFY_REQUEST, <<?BYTE(Major), ?BYTE(Minor), + ?BYTE(CookieLength), Cookie:CookieLength/binary>>) -> + + #hello_verify_request{ + protocol_version = {Major,Minor}, + cookie = Cookie}; +decode_handshake(Version, Tag, Msg) -> + ssl_handshake:decode_handshake(Version, Tag, Msg). + +%% address_to_bin({A,B,C,D}, Port) -> +%% <<0:80,16#ffff:16,A,B,C,D,Port:16>>; +%% address_to_bin({A,B,C,D,E,F,G,H}, Port) -> +%% <<A:16,B:16,C:16,D:16,E:16,F:16,G:16,H:16,Port:16>>. diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl index db7b8596ae..0298fd3105 100644 --- a/lib/ssl/src/dtls_handshake.hrl +++ b/lib/ssl/src/dtls_handshake.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -27,6 +28,8 @@ -include("ssl_handshake.hrl"). %% Common TLS and DTLS records and Constantes +-define(HELLO_VERIFY_REQUEST, 3). + -record(client_hello, { client_version, random, @@ -35,16 +38,20 @@ cipher_suites, % cipher_suites<2..2^16-1> compression_methods, % compression_methods<1..2^8-1>, %% Extensions - renegotiation_info, - hash_signs, % supported combinations of hashes/signature algos - next_protocol_negotiation = undefined % [binary()] + extensions }). --record(hello_verify_request { +-record(hello_verify_request, { protocol_version, cookie }). --define(HELLO_VERIFY_REQUEST, 3). +-record(dtls_hs_state, + {current_read_seq, + starting_read_seq, + highest_record_seq, + fragments, + completed + }). -endif. % -ifdef(dtls_handshake). diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl index 2469a7d26c..5387fcafa8 100644 --- a/lib/ssl/src/dtls_record.erl +++ b/lib/ssl/src/dtls_record.erl @@ -1,18 +1,481 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Handle DTLS record protocol. (Parts that are not shared with SSL/TLS) +%%---------------------------------------------------------------------- -module(dtls_record). + +-include("dtls_record.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_alert.hrl"). +-include("dtls_handshake.hrl"). +-include("ssl_cipher.hrl"). + +%% Handling of incoming data +-export([get_dtls_records/2]). + +%% Decoding +-export([decode_cipher_text/2]). + +%% Encoding +-export([encode_plain_text/4, encode_tls_cipher_text/5, encode_change_cipher_spec/2]). + +%% Protocol version handling +-export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2, + highest_protocol_version/1, highest_protocol_version/2, + is_higher/2, supported_protocol_versions/0, + is_acceptable_version/2]). + +%% DTLS Epoch handling +-export([init_connection_state_seq/2, current_connection_state_epoch/2, + set_connection_state_by_epoch/3, connection_state_by_epoch/3]). + +-export_type([dtls_version/0, dtls_atom_version/0]). + +-type dtls_version() :: ssl_record:ssl_version(). +-type dtls_atom_version() :: dtlsv1 | 'dtlsv1.2'. + +-compile(inline). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec get_dtls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. +%% +%% Description: Given old buffer and new data from UDP/SCTP, packs up a records +%% and returns it as a list of tls_compressed binaries also returns leftover +%% data +%%-------------------------------------------------------------------- +get_dtls_records(Data, <<>>) -> + get_dtls_records_aux(Data, []); +get_dtls_records(Data, Buffer) -> + get_dtls_records_aux(list_to_binary([Buffer, Data]), []). + +get_dtls_records_aux(<<?BYTE(?APPLICATION_DATA),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, Rest/binary>>, + Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?APPLICATION_DATA, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<<?BYTE(?HANDSHAKE),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), + Data:Length/binary, Rest/binary>>, Acc) when MajVer >= 128 -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?HANDSHAKE, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<<?BYTE(?ALERT),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, + Rest/binary>>, Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?ALERT, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); +get_dtls_records_aux(<<?BYTE(?CHANGE_CIPHER_SPEC),?BYTE(MajVer),?BYTE(MinVer), + ?UINT16(Epoch), ?UINT48(SequenceNumber), + ?UINT16(Length), Data:Length/binary, Rest/binary>>, + Acc) -> + get_dtls_records_aux(Rest, [#ssl_tls{type = ?CHANGE_CIPHER_SPEC, + version = {MajVer, MinVer}, + epoch = Epoch, sequence_number = SequenceNumber, + fragment = Data} | Acc]); + +get_dtls_records_aux(<<0:1, _CT:7, ?BYTE(_MajVer), ?BYTE(_MinVer), + ?UINT16(Length), _/binary>>, + _Acc) when Length > ?MAX_CIPHER_TEXT_LENGTH -> + ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); + +get_dtls_records_aux(<<1:1, Length0:15, _/binary>>,_Acc) + when Length0 > ?MAX_CIPHER_TEXT_LENGTH -> + ?ALERT_REC(?FATAL, ?RECORD_OVERFLOW); + +get_dtls_records_aux(Data, Acc) -> + case size(Data) =< ?MAX_CIPHER_TEXT_LENGTH + ?INITIAL_BYTES of + true -> + {lists:reverse(Acc), Data}; + false -> + ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) + end. + +encode_plain_text(Type, Version, Data, + #connection_states{current_write = + #connection_state{ + epoch = Epoch, + sequence_number = Seq, + compression_state=CompS0, + security_parameters= + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm=CompAlg} + }= WriteState0} = ConnectionStates) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + AAD = calc_aad(Type, Version, Epoch, Seq), + {CipherFragment, WriteState} = ssl_record:cipher_aead(dtls_v1:corresponding_tls_version(Version), + Comp, WriteState1, AAD), + CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment), + {CipherText, ConnectionStates#connection_states{current_write = + WriteState#connection_state{sequence_number = Seq +1}}}; + +encode_plain_text(Type, Version, Data, + #connection_states{current_write=#connection_state{ + epoch = Epoch, + sequence_number = Seq, + compression_state=CompS0, + security_parameters= + #security_parameters{compression_algorithm=CompAlg} + }= WriteState0} = ConnectionStates) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + MacHash = calc_mac_hash(WriteState1, Type, Version, Epoch, Seq, Comp), + {CipherFragment, WriteState} = ssl_record:cipher(dtls_v1:corresponding_tls_version(Version), + Comp, WriteState1, MacHash), + CipherText = encode_tls_cipher_text(Type, Version, Epoch, Seq, CipherFragment), + {CipherText, ConnectionStates#connection_states{current_write = + WriteState#connection_state{sequence_number = Seq +1}}}. + +decode_cipher_text(#ssl_tls{type = Type, version = Version, + epoch = Epoch, + sequence_number = Seq, + fragment = CipherFragment} = CipherText, + #connection_states{current_read = + #connection_state{ + compression_state = CompressionS0, + security_parameters= + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm=CompAlg} + } = ReadState0}= ConnnectionStates0) -> + AAD = calc_aad(Type, Version, Epoch, Seq), + case ssl_record:decipher_aead(dtls_v1:corresponding_tls_version(Version), + CipherFragment, ReadState0, AAD) of + {PlainFragment, ReadState1} -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + ConnnectionStates = ConnnectionStates0#connection_states{ + current_read = ReadState1#connection_state{ + compression_state = CompressionS1}}, + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + #alert{} = Alert -> + Alert + end; + +decode_cipher_text(#ssl_tls{type = Type, version = Version, + epoch = Epoch, + sequence_number = Seq, + fragment = CipherFragment} = CipherText, + #connection_states{current_read = + #connection_state{ + compression_state = CompressionS0, + security_parameters= + #security_parameters{ + compression_algorithm=CompAlg} + } = ReadState0}= ConnnectionStates0) -> + {PlainFragment, Mac, ReadState1} = ssl_record:decipher(dtls_v1:corresponding_tls_version(Version), + CipherFragment, ReadState0, true), + MacHash = calc_mac_hash(ReadState1, Type, Version, Epoch, Seq, PlainFragment), + case ssl_record:is_correct_mac(Mac, MacHash) of + true -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + ConnnectionStates = ConnnectionStates0#connection_states{ + current_read = ReadState1#connection_state{ + compression_state = CompressionS1}}, + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end. + +%%-------------------------------------------------------------------- +-spec encode_change_cipher_spec(dtls_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes a change_cipher_spec-message to send on the ssl socket. +%%-------------------------------------------------------------------- +encode_change_cipher_spec(Version, ConnectionStates) -> + encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). + +%%-------------------------------------------------------------------- +-spec protocol_version(dtls_atom_version() | dtls_version()) -> + dtls_version() | dtls_atom_version(). +%% +%% Description: Creates a protocol version record from a version atom +%% or vice versa. +%%-------------------------------------------------------------------- +protocol_version('dtlsv1.2') -> + {254, 253}; +protocol_version(dtlsv1) -> + {254, 255}; +protocol_version({254, 253}) -> + 'dtlsv1.2'; +protocol_version({254, 255}) -> + dtlsv1. +%%-------------------------------------------------------------------- +-spec lowest_protocol_version(dtls_version(), dtls_version()) -> dtls_version(). +%% +%% Description: Lowes protocol version of two given versions +%%-------------------------------------------------------------------- +lowest_protocol_version(Version = {M, N}, {M, O}) when N > O -> + Version; +lowest_protocol_version({M, _}, Version = {M, _}) -> + Version; +lowest_protocol_version(Version = {M,_}, {N, _}) when M > N -> + Version; +lowest_protocol_version(_,Version) -> + Version. + +%%-------------------------------------------------------------------- +-spec lowest_protocol_version([dtls_version()]) -> dtls_version(). +%% +%% Description: Lowest protocol version present in a list +%%-------------------------------------------------------------------- +lowest_protocol_version([]) -> + lowest_protocol_version(); +lowest_protocol_version(Versions) -> + [Ver | Vers] = Versions, + lowest_list_protocol_version(Ver, Vers). + +%%-------------------------------------------------------------------- +-spec highest_protocol_version([dtls_version()]) -> dtls_version(). +%% +%% Description: Highest protocol version present in a list +%%-------------------------------------------------------------------- +highest_protocol_version([]) -> + highest_protocol_version(); +highest_protocol_version(Versions) -> + [Ver | Vers] = Versions, + highest_list_protocol_version(Ver, Vers). + +%%-------------------------------------------------------------------- +-spec highest_protocol_version(dtls_version(), dtls_version()) -> dtls_version(). +%% +%% Description: Highest protocol version of two given versions +%%-------------------------------------------------------------------- +highest_protocol_version(Version = {M, N}, {M, O}) when N < O -> + Version; +highest_protocol_version({M, _}, + Version = {M, _}) -> + Version; +highest_protocol_version(Version = {M,_}, + {N, _}) when M < N -> + Version; +highest_protocol_version(_,Version) -> + Version. + +%%-------------------------------------------------------------------- +-spec is_higher(V1 :: dtls_version(), V2::dtls_version()) -> boolean(). +%% +%% Description: Is V1 > V2 +%%-------------------------------------------------------------------- +is_higher({M, N}, {M, O}) when N < O -> + true; +is_higher({M, _}, {N, _}) when M < N -> + true; +is_higher(_, _) -> + false. + +%%-------------------------------------------------------------------- +-spec supported_protocol_versions() -> [dtls_version()]. +%% +%% Description: Protocol versions supported +%%-------------------------------------------------------------------- +supported_protocol_versions() -> + Fun = fun(Version) -> + protocol_version(Version) + end, + case application:get_env(ssl, dtls_protocol_version) of + undefined -> + lists:map(Fun, supported_protocol_versions([])); + {ok, []} -> + lists:map(Fun, supported_protocol_versions([])); + {ok, Vsns} when is_list(Vsns) -> + supported_protocol_versions(lists:map(Fun, Vsns)); + {ok, Vsn} -> + supported_protocol_versions([Fun(Vsn)]) + end. + +supported_protocol_versions([]) -> + Vsns = case sufficient_dtlsv1_2_crypto_support() of + true -> + ?ALL_DATAGRAM_SUPPORTED_VERSIONS; + false -> + ?MIN_DATAGRAM_SUPPORTED_VERSIONS + end, + application:set_env(ssl, dtls_protocol_version, Vsns), + Vsns; + +supported_protocol_versions([_|_] = Vsns) -> + case sufficient_dtlsv1_2_crypto_support() of + true -> + Vsns; + false -> + case Vsns -- ['dtlsv1.2'] of + [] -> + ?MIN_SUPPORTED_VERSIONS; + NewVsns -> + NewVsns + end + end. + +%%-------------------------------------------------------------------- +-spec is_acceptable_version(dtls_version(), Supported :: [dtls_version()]) -> boolean(). +%% +%% Description: ssl version 2 is not acceptable security risks are too big. +%% +%%-------------------------------------------------------------------- +is_acceptable_version(Version, Versions) -> + lists:member(Version, Versions). + + +%%-------------------------------------------------------------------- +-spec init_connection_state_seq(dtls_version(), #connection_states{}) -> + #connection_state{}. +%% +%% Description: Copy the read sequence number to the write sequence number +%% This is only valid for DTLS in the first client_hello +%%-------------------------------------------------------------------- +init_connection_state_seq({254, _}, + #connection_states{ + current_read = Read = #connection_state{epoch = 0}, + current_write = Write = #connection_state{epoch = 0}} = CS0) -> + CS0#connection_states{current_write = + Write#connection_state{ + sequence_number = Read#connection_state.sequence_number}}; +init_connection_state_seq(_, CS) -> + CS. + +%%-------------------------------------------------------- +-spec current_connection_state_epoch(#connection_states{}, read | write) -> + integer(). +%% +%% Description: Returns the epoch the connection_state record +%% that is currently defined as the current conection state. +%%-------------------------------------------------------------------- +current_connection_state_epoch(#connection_states{current_read = Current}, + read) -> + Current#connection_state.epoch; +current_connection_state_epoch(#connection_states{current_write = Current}, + write) -> + Current#connection_state.epoch. + +%%-------------------------------------------------------------------- + +-spec connection_state_by_epoch(#connection_states{}, integer(), read | write) -> + #connection_state{}. +%% +%% Description: Returns the instance of the connection_state record +%% that is defined by the Epoch. +%%-------------------------------------------------------------------- +connection_state_by_epoch(#connection_states{current_read = CS}, Epoch, read) + when CS#connection_state.epoch == Epoch -> + CS; +connection_state_by_epoch(#connection_states{pending_read = CS}, Epoch, read) + when CS#connection_state.epoch == Epoch -> + CS; +connection_state_by_epoch(#connection_states{current_write = CS}, Epoch, write) + when CS#connection_state.epoch == Epoch -> + CS; +connection_state_by_epoch(#connection_states{pending_write = CS}, Epoch, write) + when CS#connection_state.epoch == Epoch -> + CS. +%%-------------------------------------------------------------------- +-spec set_connection_state_by_epoch(#connection_states{}, + #connection_state{}, read | write) + -> #connection_states{}. +%% +%% Description: Returns the instance of the connection_state record +%% that is defined by the Epoch. +%%-------------------------------------------------------------------- +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{current_read = CS}, + NewCS = #connection_state{epoch = Epoch}, read) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{current_read = NewCS}; + +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{pending_read = CS}, + NewCS = #connection_state{epoch = Epoch}, read) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{pending_read = NewCS}; + +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{current_write = CS}, + NewCS = #connection_state{epoch = Epoch}, write) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{current_write = NewCS}; + +set_connection_state_by_epoch(ConnectionStates0 = + #connection_states{pending_write = CS}, + NewCS = #connection_state{epoch = Epoch}, write) + when CS#connection_state.epoch == Epoch -> + ConnectionStates0#connection_states{pending_write = NewCS}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- + + +lowest_list_protocol_version(Ver, []) -> + Ver; +lowest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest). + +highest_list_protocol_version(Ver, []) -> + Ver; +highest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest). + +encode_tls_cipher_text(Type, {MajVer, MinVer}, Epoch, Seq, Fragment) -> + Length = erlang:iolist_size(Fragment), + [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Epoch), + ?UINT48(Seq), ?UINT16(Length)>>, Fragment]. + +calc_mac_hash(#connection_state{mac_secret = MacSecret, + security_parameters = #security_parameters{mac_algorithm = MacAlg}}, + Type, Version, Epoch, SeqNo, Fragment) -> + Length = erlang:iolist_size(Fragment), + NewSeq = (Epoch bsl 48) + SeqNo, + mac_hash(Version, MacAlg, MacSecret, NewSeq, Type, + Length, Fragment). + +highest_protocol_version() -> + highest_protocol_version(supported_protocol_versions()). + +lowest_protocol_version() -> + lowest_protocol_version(supported_protocol_versions()). + +sufficient_dtlsv1_2_crypto_support() -> + CryptoSupport = crypto:supports(), + proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)). + +mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> + dtls_v1:mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, + Length, Fragment). + +calc_aad(Type, {MajVer, MinVer}, Epoch, SeqNo) -> + NewSeq = (Epoch bsl 48) + SeqNo, + <<NewSeq:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. diff --git a/lib/ssl/src/dtls_record.hrl b/lib/ssl/src/dtls_record.hrl index e935d84bdf..b9f84cbe7f 100644 --- a/lib/ssl/src/dtls_record.hrl +++ b/lib/ssl/src/dtls_record.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -28,16 +29,15 @@ -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes -%% Used to handle tls_plain_text, tls_compressed and tls_cipher_text +%% Used to handle dtls_plain_text, dtls_compressed and dtls_cipher_text -record(ssl_tls, { type, version, - record_seq, % used in plain_text - epoch, % used in plain_text - message_seq, - fragment_offset, - fragment_length, + epoch, + sequence_number, + offset, + length, fragment }). diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl new file mode 100644 index 0000000000..8c03bda513 --- /dev/null +++ b/lib/ssl/src/dtls_v1.erl @@ -0,0 +1,44 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(dtls_v1). + +-include("ssl_cipher.hrl"). + +-export([suites/1, mac_hash/7, ecc_curves/1, corresponding_tls_version/1]). + +-spec suites(Minor:: 253|255) -> [ssl_cipher:cipher_suite()]. + +suites(Minor) -> + tls_v1:suites(corresponding_minor_tls_version(Minor)). + +mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> + tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, corresponding_tls_version(Version), + Length, Fragment). + +ecc_curves({_Major, Minor}) -> + tls_v1:ecc_curves(corresponding_minor_tls_version(Minor)). + +corresponding_tls_version({254, Minor}) -> + {3, corresponding_minor_tls_version(Minor)}. + +corresponding_minor_tls_version(255) -> + 2; +corresponding_minor_tls_version(253) -> + 3. diff --git a/lib/ssl/src/inet6_tls_dist.erl b/lib/ssl/src/inet6_tls_dist.erl new file mode 100644 index 0000000000..ffd7296f93 --- /dev/null +++ b/lib/ssl/src/inet6_tls_dist.erl @@ -0,0 +1,46 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(inet6_tls_dist). + +-export([childspecs/0, listen/1, accept/1, accept_connection/5, + setup/5, close/1, select/1]). + +childspecs() -> + inet_tls_dist:childspecs(). + +select(Node) -> + inet_tls_dist:gen_select(inet6_tcp, Node). + +listen(Name) -> + inet_tls_dist:gen_listen(inet6_tcp, Name). + +accept(Listen) -> + inet_tls_dist:gen_accept(inet6_tcp, Listen). + +accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + inet_tls_dist:gen_accept_connection(inet6_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime). + +setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> + inet_tls_dist:gen_setup(inet6_tcp, Node, Type, MyNode, LongOrShortNames,SetupTime). + +close(Socket) -> + inet_tls_dist:close(Socket). diff --git a/lib/ssl/src/inet_tls_dist.erl b/lib/ssl/src/inet_tls_dist.erl index 57c859bf24..0da4b3587f 100644 --- a/lib/ssl/src/inet_tls_dist.erl +++ b/lib/ssl/src/inet_tls_dist.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2012. All Rights Reserved. +%% Copyright Ericsson AB 2011-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -23,18 +24,28 @@ -export([childspecs/0, listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). +%% Generalized dist API +-export([gen_listen/2, gen_accept/2, gen_accept_connection/6, + gen_setup/6, gen_select/2]). + -include_lib("kernel/include/net_address.hrl"). -include_lib("kernel/include/dist.hrl"). -include_lib("kernel/include/dist_util.hrl"). childspecs() -> {ok, [{ssl_dist_sup,{ssl_dist_sup, start_link, []}, - permanent, 2000, worker, [ssl_dist_sup]}]}. + permanent, infinity, supervisor, [ssl_dist_sup]}]}. select(Node) -> + gen_select(inet_tcp, Node). + +gen_select(Driver, Node) -> case split_node(atom_to_list(Node), $@, []) of - [_,_Host] -> - true; + [_, Host] -> + case inet:getaddr(Host, Driver:family()) of + {ok, _} -> true; + _ -> false + end; _ -> false end. @@ -45,70 +56,78 @@ is_node_name(_) -> false. listen(Name) -> - ssl_tls_dist_proxy:listen(Name). + gen_listen(inet_tcp, Name). + +gen_listen(Driver, Name) -> + ssl_tls_dist_proxy:listen(Driver, Name). accept(Listen) -> - ssl_tls_dist_proxy:accept(Listen). + gen_accept(inet_tcp, Listen). + +gen_accept(Driver, Listen) -> + ssl_tls_dist_proxy:accept(Driver, Listen). accept_connection(AcceptPid, Socket, MyNode, Allowed, SetupTime) -> + gen_accept_connection(inet_tcp, AcceptPid, Socket, MyNode, Allowed, SetupTime). + +gen_accept_connection(Driver, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> Kernel = self(), - spawn_link(fun() -> do_accept(Kernel, AcceptPid, Socket, + spawn_link(fun() -> do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) end). setup(Node, Type, MyNode, LongOrShortNames,SetupTime) -> + gen_setup(inet_tcp, Node, Type, MyNode, LongOrShortNames,SetupTime). + +gen_setup(Driver, Node, Type, MyNode, LongOrShortNames,SetupTime) -> Kernel = self(), - spawn_opt(fun() -> do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) end, [link, {priority, max}]). + spawn_opt(fun() -> do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) end, [link, {priority, max}]). -do_setup(Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> - [Name, Address] = splitnode(Node, LongOrShortNames), - case inet:getaddr(Address, inet) of +do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> + [Name, Address] = splitnode(Driver, Node, LongOrShortNames), + case inet:getaddr(Address, Driver:family()) of {ok, Ip} -> Timer = dist_util:start_timer(SetupTime), - case erl_epmd:port_please(Name, Ip) of + ErlEpmd = net_kernel:epmd_module(), + case ErlEpmd:port_please(Name, Ip) of {port, TcpPort, Version} -> ?trace("port_please(~p) -> version ~p~n", [Node,Version]), dist_util:reset_timer(Timer), - case ssl_tls_dist_proxy:connect(Ip, TcpPort) of + case ssl_tls_dist_proxy:connect(Driver, Ip, TcpPort) of {ok, Socket} -> HSData = connect_hs_data(Kernel, Node, MyNode, Socket, Timer, Version, Ip, TcpPort, Address, Type), dist_util:handshake_we_started(HSData); - _ -> + Other -> %% Other Node may have closed since %% port_please ! ?trace("other node (~p) " "closed since port_please.~n", [Node]), - ?shutdown(Node) + ?shutdown2(Node, {shutdown, {connect_failed, Other}}) end; - _ -> + Other -> ?trace("port_please (~p) " "failed.~n", [Node]), - ?shutdown(Node) + ?shutdown2(Node, {shutdown, {port_please_failed, Other}}) end; - _Other -> + Other -> ?trace("inet_getaddr(~p) " "failed (~p).~n", [Node,Other]), - ?shutdown(Node) + ?shutdown2(Node, {shutdown, {inet_getaddr_failed, Other}}) end. close(Socket) -> - try - erlang:error(foo) - catch _:_ -> - io:format("close called ~p ~p~n",[Socket, erlang:get_stacktrace()]) - end, gen_tcp:close(Socket), ok. -do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> +do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> process_flag(priority, max), receive {AcceptPid, controller} -> Timer = dist_util:start_timer(SetupTime), - case check_ip(Socket) of + case check_ip(Driver, Socket) of true -> HSData = accept_hs_data(Kernel, MyNode, Socket, Timer, Allowed), dist_util:handshake_other_started(HSData); @@ -122,12 +141,12 @@ do_accept(Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> %% Do only accept new connection attempts from nodes at our %% own LAN, if the check_ip environment parameter is true. %% ------------------------------------------------------------ -check_ip(Socket) -> +check_ip(Driver, Socket) -> case application:get_env(check_ip) of {ok, true} -> case get_ifs(Socket) of {ok, IFs, IP} -> - check_ip(IFs, IP); + check_ip(Driver, IFs, IP); _ -> ?shutdown(no_node) end; @@ -146,37 +165,21 @@ get_ifs(Socket) -> Error end. -check_ip([{OwnIP, _, Netmask}|IFs], PeerIP) -> - case {mask(Netmask, PeerIP), mask(Netmask, OwnIP)} of +check_ip(Driver, [{OwnIP, _, Netmask}|IFs], PeerIP) -> + case {Driver:mask(Netmask, PeerIP), Driver:mask(Netmask, OwnIP)} of {M, M} -> true; _ -> check_ip(IFs, PeerIP) end; -check_ip([], PeerIP) -> +check_ip(_Driver, [], PeerIP) -> {false, PeerIP}. -mask({M1,M2,M3,M4}, {IP1,IP2,IP3,IP4}) -> - {M1 band IP1, - M2 band IP2, - M3 band IP3, - M4 band IP4}; - -mask({M1,M2,M3,M4, M5, M6, M7, M8}, {IP1,IP2,IP3,IP4, IP5, IP6, IP7, IP8}) -> - {M1 band IP1, - M2 band IP2, - M3 band IP3, - M4 band IP4, - M5 band IP5, - M6 band IP6, - M7 band IP7, - M8 band IP8}. - %% If Node is illegal terminate the connection setup!! -splitnode(Node, LongOrShortNames) -> +splitnode(Driver, Node, LongOrShortNames) -> case split_node(atom_to_list(Node), $@, []) of [Name|Tail] when Tail =/= [] -> Host = lists:append(Tail), - check_node(Name, Node, Host, LongOrShortNames); + check_node(Driver, Name, Node, Host, LongOrShortNames); [_] -> error_logger:error_msg("** Nodename ~p illegal, no '@' character **~n", [Node]), @@ -186,15 +189,20 @@ splitnode(Node, LongOrShortNames) -> ?shutdown(Node) end. -check_node(Name, Node, Host, LongOrShortNames) -> +check_node(Driver, Name, Node, Host, LongOrShortNames) -> case split_node(Host, $., []) of [_] when LongOrShortNames == longnames -> - error_logger:error_msg("** System running to use " - "fully qualified " - "hostnames **~n" - "** Hostname ~s is illegal **~n", - [Host]), - ?shutdown(Node); + case Driver:parse_address(Host) of + {ok, _} -> + [Name, Host]; + _ -> + error_logger:error_msg("** System running to use " + "fully qualified " + "hostnames **~n" + "** Hostname ~s is illegal **~n", + [Host]), + ?shutdown(Node) + end; [_, _ | _] when LongOrShortNames == shortnames -> error_logger:error_msg("** System NOT running to use fully qualified " "hostnames **~n" diff --git a/lib/ssl/src/ssl.app.src b/lib/ssl/src/ssl.app.src index 582a60635f..b26efbd88f 100644 --- a/lib/ssl/src/ssl.app.src +++ b/lib/ssl/src/ssl.app.src @@ -3,44 +3,59 @@ {vsn, "%VSN%"}, {modules, [ %% TLS/SSL - tls, tls_connection, tls_handshake, tls_record, + tls_v1, + ssl_v3, + ssl_v2, %% DTLS - dtls_record, - dtls_handshake, dtls_connection, - dtls, - %% Backwards compatibility - ssl, + dtls_handshake, + dtls_record, + dtls_v1, + %% API + ssl, %% Main API + tls, %% TLS specific + dtls, %% DTLS specific + ssl_session_cache_api, %% Both TLS/SSL and DTLS - ssl_app, - ssl_sup, + ssl_config, + ssl_connection, + ssl_handshake, + ssl_record, + ssl_cipher, + ssl_srp_primes, + ssl_alert, + ssl_socket, + ssl_listen_tracker_sup, + %% Erlang Distribution over SSL/TLS inet_tls_dist, + inet6_tls_dist, ssl_tls_dist_proxy, ssl_dist_sup, - ssl_tls1, - ssl_ssl3, - ssl_ssl2, + %% SSL/TLS session handling ssl_session, - ssl_session_cache_api, ssl_session_cache, - ssl_socket, - %%ssl_record, ssl_manager, - %%ssl_handshake, - ssl_connection_sup, - %%ssl_connection, - ssl_cipher, - ssl_srp_primes, ssl_pkix_db, ssl_certificate, - ssl_alert + %% CRL handling + ssl_crl, + ssl_crl_cache, + ssl_crl_cache_api, + ssl_crl_hash_dir, + %% App structure + ssl_app, + ssl_sup, + tls_connection_sup, + dtls_connection_sup ]}, {registered, [ssl_sup, ssl_manager]}, {applications, [crypto, public_key, kernel, stdlib]}, {env, []}, - {mod, {ssl_app, []}}]}. + {mod, {ssl_app, []}}, + {runtime_dependencies, ["stdlib-3.0","public_key-1.2","kernel-3.0", + "erts-7.0","crypto-3.3", "inets-5.10.7"]}]}. diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 9e5bec26f1..11728128c4 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,17 +1,18 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"5.2\\*">>, [{restart_application, ssl}]}, - {<<"5.1\\*">>, [{restart_application, ssl}]}, - {<<"5.0\\*">>, [{restart_application, ssl}]}, - {<<"4\\.*">>, [{restart_application, ssl}]}, - {<<"3\\.*">>, [{restart_application, ssl}]} + {<<"7\\..*">>, [{restart_application, ssl}]}, + {<<"6\\..*">>, [{restart_application, ssl}]}, + {<<"5\\..*">>, [{restart_application, ssl}]}, + {<<"4\\..*">>, [{restart_application, ssl}]}, + {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"5.2\\*">>, [{restart_application, ssl}]}, - {<<"5.1\\*">>, [{restart_application, ssl}]}, - {<<"5.0\\*">>, [{restart_application, ssl}]}, - {<<"4\\.*">>, [{restart_application, ssl}]}, - {<<"3\\.*">>, [{restart_application, ssl}]} - ]}. + {<<"7\\..*">>, [{restart_application, ssl}]}, + {<<"6\\..*">>, [{restart_application, ssl}]}, + {<<"5\\..*">>, [{restart_application, ssl}]}, + {<<"4\\..*">>, [{restart_application, ssl}]}, + {<<"3\\..*">>, [{restart_application, ssl}]} + ] +}. diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 0c1e47311d..d2aeb3258f 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -1,149 +1,374 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. +%% Copyright Ericsson AB 1999-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% %% -%%% Purpose : Backwards compatibility +%%% Purpose : Main API module for SSL see also tls.erl and dtls.erl -module(ssl). +-include("ssl_internal.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%% Application handling +-export([start/0, start/1, stop/0, clear_pem_cache/0]). + +%% Socket handling +-export([connect/3, connect/2, connect/4, + listen/2, transport_accept/1, transport_accept/2, + ssl_accept/1, ssl_accept/2, ssl_accept/3, + controlling_process/2, peername/1, peercert/1, sockname/1, + close/1, close/2, shutdown/2, recv/2, recv/3, send/2, + getopts/2, setopts/2, getstat/1, getstat/2 + ]). +%% SSL/TLS protocol handling +-export([cipher_suites/0, cipher_suites/1, + connection_info/1, versions/0, session_info/1, format_error/1, + renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1, + connection_information/1, connection_information/2]). +%% Misc +-export([handle_options/2]). --export([start/0, start/1, stop/0, transport_accept/1, - transport_accept/2, ssl_accept/1, ssl_accept/2, ssl_accept/3, - cipher_suites/0, cipher_suites/1, suite_definition/1, - close/1, shutdown/2, - connect/3, connect/2, connect/4, connection_info/1, - controlling_process/2, listen/2, peername/1, peercert/1, - recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1, - versions/0, session_info/1, format_error/1, - renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1, negotiated_next_protocol/1]). +-deprecated({negotiated_next_protocol, 1, next_major_release}). +-deprecated({connection_info, 1, next_major_release}). +-include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). -include("ssl_cipher.hrl"). -include("ssl_handshake.hrl"). +-include("ssl_srp.hrl"). -include_lib("public_key/include/public_key.hrl"). -%% Visible in API --export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0, - erl_cipher_suite/0, %% From ssl_cipher.hrl - tls_atom_version/0, %% From ssl_internal.hrl - prf_random/0, sslsocket/0]). - --type sslsocket() :: #sslsocket{}. --type connect_option() :: socket_connect_option() | ssl_option() | transport_option(). --type socket_connect_option() :: gen_tcp:connect_option(). --type listen_option() :: socket_listen_option() | ssl_option() | transport_option(). --type socket_listen_option() :: gen_tcp:listen_option(). - --type ssl_option() :: {verify, verify_type()} | - {verify_fun, {fun(), InitialUserState::term()}} | - {fail_if_no_peer_cert, boolean()} | {depth, integer()} | - {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} | - {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} | - {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} | - {user_lookup_fun, {fun(), InitialUserState::term()}} | - {psk_identity, string()} | - {srp_identity, {string(), string()}} | - {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | - {reuse_session, fun()} | {hibernate_after, integer()|undefined} | - {next_protocols_advertised, list(binary())} | - {client_preferred_next_protocols, binary(), client | server, list(binary())}. - --type verify_type() :: verify_none | verify_peer. --type path() :: string(). --type ciphers() :: [erl_cipher_suite()] | - string(). % (according to old API) --type ssl_imp() :: new | old. - --type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(), - ClosedTag::atom(), ErrTag::atom()}}. --type prf_random() :: client_random | server_random. - %%-------------------------------------------------------------------- -spec start() -> ok | {error, reason()}. -spec start(permanent | transient | temporary) -> ok | {error, reason()}. %% -%% Description: Utility function that starts the ssl, -%% crypto and public_key applications. Default type -%% is temporary. see application(3) +%% Description: Utility function that starts the ssl and applications +%% that it depends on. +%% see application(3) %%-------------------------------------------------------------------- start() -> - tls:start(). + start(temporary). start(Type) -> - tls:start(Type). - + case application:ensure_all_started(ssl, Type) of + {ok, _} -> + ok; + Other -> + Other + end. +%%-------------------------------------------------------------------- +-spec stop() -> ok. +%% +%% Description: Stops the ssl application. +%%-------------------------------------------------------------------- stop() -> - tls:stop(). + application:stop(ssl). -connect(Socket, SslOptions) -> - tls:connect(Socket, SslOptions). +%%-------------------------------------------------------------------- +-spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | + {error, reason()}. +-spec connect(host() | port(), [connect_option()] | inet:port_number(), + timeout() | list()) -> + {ok, #sslsocket{}} | {error, reason()}. +-spec connect(host() | port(), inet:port_number(), list(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. -connect(Socket, SslOptions0, TimeoutOrOpts) -> - tls:connect(Socket, SslOptions0, TimeoutOrOpts). +%% +%% Description: Connect to an ssl server. +%%-------------------------------------------------------------------- +connect(Socket, SslOptions) when is_port(Socket) -> + connect(Socket, SslOptions, infinity). -connect(Host, Port, Options, Timeout) -> - tls:connect(Host, Port, Options, Timeout). +connect(Socket, SslOptions0, Timeout) when is_port(Socket), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0, + {gen_tcp, tcp, tcp_closed, tcp_error}), + EmulatedOptions = ssl_socket:emulated_options(), + {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), + try handle_options(SslOptions0 ++ SocketValues, client) of + {ok, #config{transport_info = CbInfo, ssl = SslOptions, emulated = EmOpts, + connection_cb = ConnectionCb}} -> -listen(Port, Options) -> - tls:listen(Port, Options). + ok = ssl_socket:setopts(Transport, Socket, ssl_socket:internal_inet_values()), + case ssl_socket:peername(Transport, Socket) of + {ok, {Address, Port}} -> + ssl_connection:connect(ConnectionCb, Address, Port, Socket, + {SslOptions, emulated_socket_options(EmOpts, #socket_options{}), undefined}, + self(), CbInfo, Timeout); + {error, Error} -> + {error, Error} + end + catch + _:{error, Reason} -> + {error, Reason} + end; +connect(Host, Port, Options) -> + connect(Host, Port, Options, infinity). + +connect(Host, Port, Options, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + try handle_options(Options, client) of + {ok, Config} -> + do_connect(Host,Port,Config,Timeout) + catch + throw:Error -> + Error + end. + +%%-------------------------------------------------------------------- +-spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}. + +%% +%% Description: Creates an ssl listen socket. +%%-------------------------------------------------------------------- +listen(_Port, []) -> + {error, nooptions}; +listen(Port, Options0) -> + try + {ok, Config} = handle_options(Options0, server), + ConnectionCb = connection_cb(Options0), + #config{transport_info = {Transport, _, _, _}, inet_user = Options, connection_cb = ConnectionCb, + ssl = SslOpts, emulated = EmOpts} = Config, + case Transport:listen(Port, Options) of + {ok, ListenSocket} -> + ok = ssl_socket:setopts(Transport, ListenSocket, ssl_socket:internal_inet_values()), + {ok, Tracker} = ssl_socket:inherit_tracker(ListenSocket, EmOpts, SslOpts), + {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}}; + Err = {error, _} -> + Err + end + catch + Error = {error, _} -> + Error + end. +%%-------------------------------------------------------------------- +-spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} | + {error, reason()}. +-spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | + {error, reason()}. +%% +%% Description: Performs transport accept on an ssl listen socket +%%-------------------------------------------------------------------- transport_accept(ListenSocket) -> - tls:transport_accept(ListenSocket). + transport_accept(ListenSocket, infinity). + +transport_accept(#sslsocket{pid = {ListenSocket, + #config{transport_info = {Transport,_,_, _} =CbInfo, + connection_cb = ConnectionCb, + ssl = SslOpts, + emulated = Tracker}}}, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + case Transport:accept(ListenSocket, Timeout) of + {ok, Socket} -> + {ok, EmOpts} = ssl_socket:get_emulated_opts(Tracker), + {ok, Port} = ssl_socket:port(Transport, Socket), + ConnArgs = [server, "localhost", Port, Socket, + {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), Tracker}, self(), CbInfo], + ConnectionSup = connection_sup(ConnectionCb), + case ConnectionSup:start_child(ConnArgs) of + {ok, Pid} -> + ssl_connection:socket_control(ConnectionCb, Socket, Pid, Transport, Tracker); + {error, Reason} -> + {error, Reason} + end; + {error, Reason} -> + {error, Reason} + end. + +%%-------------------------------------------------------------------- +-spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}. +-spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option() + | transport_option()]) -> + ok | {ok, #sslsocket{}} | {error, reason()}. -transport_accept(ListenSocket, Timeout) -> - tls:transport_accept(ListenSocket, Timeout). - +-spec ssl_accept(#sslsocket{} | port(), [ssl_option()] | [ssl_option()| transport_option()], timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Performs accept on an ssl listen socket. e.i. performs +%% ssl handshake. +%%-------------------------------------------------------------------- ssl_accept(ListenSocket) -> - tls:ssl_accept(ListenSocket, infinity). + ssl_accept(ListenSocket, infinity). -ssl_accept(#sslsocket{} = Socket, Timeout) -> - tls:ssl_accept(Socket, Timeout); - -ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> - tls:ssl_accept(ListenSocket, SslOptions, infinity). +ssl_accept(#sslsocket{} = Socket, Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + ssl_connection:handshake(Socket, Timeout); + +ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> + ssl_accept(ListenSocket, SslOptions, infinity). + +ssl_accept(#sslsocket{} = Socket, [], Timeout) when (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> + ssl_accept(#sslsocket{} = Socket, Timeout); +ssl_accept(#sslsocket{fd = {_, _, _, Tracker}} = Socket, SslOpts0, Timeout) when + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> + try + {ok, EmOpts, InheritedSslOpts} = ssl_socket:get_all_opts(Tracker), + SslOpts = handle_options(SslOpts0, InheritedSslOpts), + ssl_connection:handshake(Socket, {SslOpts, emulated_socket_options(EmOpts, #socket_options{})}, Timeout) + catch + Error = {error, _Reason} -> Error + end; +ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + {Transport,_,_,_} = + proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}), + EmulatedOptions = ssl_socket:emulated_options(), + {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), + ConnetionCb = connection_cb(SslOptions), + try handle_options(SslOptions ++ SocketValues, server) of + {ok, #config{transport_info = CbInfo, ssl = SslOpts, emulated = EmOpts}} -> + ok = ssl_socket:setopts(Transport, Socket, ssl_socket:internal_inet_values()), + {ok, Port} = ssl_socket:port(Transport, Socket), + ssl_connection:ssl_accept(ConnetionCb, Port, Socket, + {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, + self(), CbInfo, Timeout) + catch + Error = {error, _Reason} -> Error + end. -ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> - tls:ssl_accept(Socket, SslOptions, Timeout). +%%-------------------------------------------------------------------- +-spec close(#sslsocket{}) -> term(). +%% +%% Description: Close an ssl connection +%%-------------------------------------------------------------------- +close(#sslsocket{pid = Pid}) when is_pid(Pid) -> + ssl_connection:close(Pid, {close, ?DEFAULT_TIMEOUT}); +close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}) -> + Transport:close(ListenSocket). -close(Socket) -> - tls:close(Socket). +%%-------------------------------------------------------------------- +-spec close(#sslsocket{}, timeout() | {pid(), integer()}) -> term(). +%% +%% Description: Close an ssl connection +%%-------------------------------------------------------------------- +close(#sslsocket{pid = TLSPid}, + {Pid, Timeout} = DownGrade) when is_pid(TLSPid), + is_pid(Pid), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + ssl_connection:close(TLSPid, {close, DownGrade}); +close(#sslsocket{pid = TLSPid}, Timeout) when is_pid(TLSPid), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity) -> + ssl_connection:close(TLSPid, {close, Timeout}); +close(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport,_, _, _}}}}, _) -> + Transport:close(ListenSocket). -send(Socket, Data) -> - tls:send(Socket, Data). +%%-------------------------------------------------------------------- +-spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}. +%% +%% Description: Sends data over the ssl connection +%%-------------------------------------------------------------------- +send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) -> + ssl_connection:send(Pid, Data); +send(#sslsocket{pid = {ListenSocket, #config{transport_info={Transport, _, _, _}}}}, Data) -> + Transport:send(ListenSocket, Data). %% {error,enotconn} +%%-------------------------------------------------------------------- +-spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}. +-spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}. +%% +%% Description: Receives data when active = false +%%-------------------------------------------------------------------- recv(Socket, Length) -> - tls:recv(Socket, Length, infinity). -recv(Socket, Length, Timeout) -> - tls:recv(Socket, Length, Timeout). + recv(Socket, Length, infinity). +recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid), + (is_integer(Timeout) andalso Timeout >= 0) or (Timeout == infinity)-> + ssl_connection:recv(Pid, Length, Timeout); +recv(#sslsocket{pid = {Listen, + #config{transport_info = {Transport, _, _, _}}}}, _,_) when is_port(Listen)-> + Transport:recv(Listen, 0). %% {error,enotconn} -controlling_process(Socket, NewOwner) -> - tls:controlling_process(Socket, NewOwner). - -connection_info(Socket) -> - tls:connection_info(Socket). +%%-------------------------------------------------------------------- +-spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}. +%% +%% Description: Changes process that receives the messages when active = true +%% or once. +%%-------------------------------------------------------------------- +controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> + ssl_connection:new_user(Pid, NewOwner); +controlling_process(#sslsocket{pid = {Listen, + #config{transport_info = {Transport, _, _, _}}}}, + NewOwner) when is_port(Listen), + is_pid(NewOwner) -> + Transport:controlling_process(Listen, NewOwner). -peername(Socket) -> - tls:peername(Socket). +%%-------------------------------------------------------------------- +-spec connection_information(#sslsocket{}) -> {ok, list()} | {error, reason()}. +%% +%% Description: Return SSL information for the connection +%%-------------------------------------------------------------------- +connection_information(#sslsocket{pid = Pid}) when is_pid(Pid) -> + case ssl_connection:connection_information(Pid) of + {ok, Info} -> + {ok, [Item || Item = {_Key, Value} <- Info, Value =/= undefined]}; + Error -> + Error + end; +connection_information(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> + {error, enotconn}. + +%%-------------------------------------------------------------------- +-spec connection_information(#sslsocket{}, [atom()]) -> {ok, list()} | {error, reason()}. +%% +%% Description: Return SSL information for the connection +%%-------------------------------------------------------------------- +connection_information(#sslsocket{} = SSLSocket, Items) -> + case connection_information(SSLSocket) of + {ok, Info} -> + {ok, [Item || Item = {Key, Value} <- Info, lists:member(Key, Items), + Value =/= undefined]}; + Error -> + Error + end. + +%%-------------------------------------------------------------------- +%% Deprecated +-spec connection_info(#sslsocket{}) -> {ok, {tls_record:tls_atom_version(), ssl_cipher:erl_cipher_suite()}} | + {error, reason()}. +%% +%% Description: Returns ssl protocol and cipher used for the connection +%%-------------------------------------------------------------------- +connection_info(#sslsocket{} = SSLSocket) -> + case connection_information(SSLSocket) of + {ok, Result} -> + {ok, {proplists:get_value(protocol, Result), proplists:get_value(cipher_suite, Result)}}; + Error -> + Error + end. + +%%-------------------------------------------------------------------- +-spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. +%% +%% Description: same as inet:peername/1. +%%-------------------------------------------------------------------- +peername(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid)-> + ssl_socket:peername(Transport, Socket); +peername(#sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}}}}) -> + ssl_socket:peername(Transport, ListenSocket). %% Will return {error, enotconn} + +%%-------------------------------------------------------------------- +-spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}. +%% +%% Description: Returns the peercert. +%%-------------------------------------------------------------------- peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> - case tls_connection:peer_certificate(Pid) of + case ssl_connection:peer_certificate(Pid) of {ok, undefined} -> {error, no_peercert}; Result -> @@ -152,71 +377,1023 @@ peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> {error, enotconn}. -suite_definition(S) -> - {KeyExchange, Cipher, Hash, _} = ssl_cipher:suite_definition(S), - {KeyExchange, Cipher, Hash}. - -negotiated_next_protocol(#sslsocket{pid = Pid}) -> - tls_connection:negotiated_next_protocol(Pid). +%%-------------------------------------------------------------------- +-spec negotiated_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. +%% +%% Description: Returns the protocol that has been negotiated. If no +%% protocol has been negotiated will return {error, protocol_not_negotiated} +%%-------------------------------------------------------------------- +negotiated_protocol(#sslsocket{pid = Pid}) -> + ssl_connection:negotiated_protocol(Pid). -%%%-------------------------------------------------------------------- --spec cipher_suites() -> [erl_cipher_suite()]. --spec cipher_suites(erlang | openssl | all ) -> [erl_cipher_suite()] | [string()]. - -%% Description: Returns all supported cipher suites. %%-------------------------------------------------------------------- +-spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. +%% +%% Description: Returns the next protocol that has been negotiated. If no +%% protocol has been negotiated will return {error, next_protocol_not_negotiated} +%%-------------------------------------------------------------------- +negotiated_next_protocol(Socket) -> + case negotiated_protocol(Socket) of + {error, protocol_not_negotiated} -> + {error, next_protocol_not_negotiated}; + Res -> + Res + end. +%%-------------------------------------------------------------------- +-spec cipher_suites() -> [ssl_cipher:erl_cipher_suite()] | [string()]. +%%-------------------------------------------------------------------- cipher_suites() -> cipher_suites(erlang). - +%%-------------------------------------------------------------------- +-spec cipher_suites(erlang | openssl | all) -> [ssl_cipher:erl_cipher_suite()] | + [string()]. +%% Description: Returns all supported cipher suites. +%%-------------------------------------------------------------------- cipher_suites(erlang) -> - Version = tls_record:highest_protocol_version([]), - [suite_definition(S) || S <- ssl_cipher:suites(Version)]; + [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(default)]; cipher_suites(openssl) -> - Version = tls_record:highest_protocol_version([]), - [ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)]; + [ssl_cipher:openssl_suite_name(Suite) || Suite <- available_suites(default)]; cipher_suites(all) -> - Version = tls_record:highest_protocol_version([]), - Supported = ssl_cipher:suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), - [suite_definition(S) || S <- Supported]. + [ssl_cipher:erl_suite_definition(Suite) || Suite <- available_suites(all)]. + +%%-------------------------------------------------------------------- +-spec getopts(#sslsocket{}, [gen_tcp:option_name()]) -> + {ok, [gen_tcp:option()]} | {error, reason()}. +%% +%% Description: Gets options +%%-------------------------------------------------------------------- +getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> + ssl_connection:get_opts(Pid, OptionTags); +getopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, + OptionTags) when is_list(OptionTags) -> + try ssl_socket:getopts(Transport, ListenSocket, OptionTags) of + {ok, _} = Result -> + Result; + {error, InetError} -> + {error, {options, {socket_options, OptionTags, InetError}}} + catch + _:Error -> + {error, {options, {socket_options, OptionTags, Error}}} + end; +getopts(#sslsocket{}, OptionTags) -> + {error, {options, {socket_options, OptionTags}}}. + +%%-------------------------------------------------------------------- +-spec setopts(#sslsocket{}, [gen_tcp:option()]) -> ok | {error, reason()}. +%% +%% Description: Sets options +%%-------------------------------------------------------------------- +setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> + try proplists:expand([{binary, [{mode, binary}]}, + {list, [{mode, list}]}], Options0) of + Options -> + ssl_connection:set_opts(Pid, Options) + catch + _:_ -> + {error, {options, {not_a_proplist, Options0}}} + end; -getopts(Socket, OptionTags) -> - tls:getopts(Socket, OptionTags). +setopts(#sslsocket{pid = {_, #config{transport_info = {Transport,_,_,_}}}} = ListenSocket, Options) when is_list(Options) -> + try ssl_socket:setopts(Transport, ListenSocket, Options) of + ok -> + ok; + {error, InetError} -> + {error, {options, {socket_options, Options, InetError}}} + catch + _:Error -> + {error, {options, {socket_options, Options, Error}}} + end; +setopts(#sslsocket{}, Options) -> + {error, {options,{not_a_proplist, Options}}}. -setopts(Socket, Options) -> - tls:setopts(Socket, Options). +%%--------------------------------------------------------------- +-spec getstat(Socket) -> + {ok, OptionValues} | {error, inet:posix()} when + Socket :: #sslsocket{}, + OptionValues :: [{inet:stat_option(), integer()}]. +%% +%% Description: Get all statistic options for a socket. +%%-------------------------------------------------------------------- +getstat(Socket) -> + getstat(Socket, inet:stats()). -shutdown(Socket, How) -> - tls:shutdown(Socket, How). +%%--------------------------------------------------------------- +-spec getstat(Socket, Options) -> + {ok, OptionValues} | {error, inet:posix()} when + Socket :: #sslsocket{}, + Options :: [inet:stat_option()], + OptionValues :: [{inet:stat_option(), integer()}]. +%% +%% Description: Get one or more statistic options for a socket. +%%-------------------------------------------------------------------- +getstat(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}, Options) when is_port(Listen), is_list(Options) -> + ssl_socket:getstat(Transport, Listen, Options); -sockname(Socket) -> - tls:sockname(Socket). +getstat(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}, Options) when is_pid(Pid), is_list(Options) -> + ssl_socket:getstat(Transport, Socket, Options). +%%--------------------------------------------------------------- +-spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}. +%% +%% Description: Same as gen_tcp:shutdown/2 +%%-------------------------------------------------------------------- +shutdown(#sslsocket{pid = {Listen, #config{transport_info = {Transport,_, _, _}}}}, + How) when is_port(Listen) -> + Transport:shutdown(Listen, How); +shutdown(#sslsocket{pid = Pid}, How) -> + ssl_connection:shutdown(Pid, How). + +%%-------------------------------------------------------------------- +-spec sockname(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. +%% +%% Description: Same as inet:sockname/1 +%%-------------------------------------------------------------------- +sockname(#sslsocket{pid = {Listen, #config{transport_info = {Transport, _, _, _}}}}) when is_port(Listen) -> + ssl_socket:sockname(Transport, Listen); + +sockname(#sslsocket{pid = Pid, fd = {Transport, Socket, _, _}}) when is_pid(Pid) -> + ssl_socket:sockname(Transport, Socket). + +%%--------------------------------------------------------------- +-spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}. +%% +%% Description: Returns list of session info currently [{session_id, session_id(), +%% {cipher_suite, cipher_suite()}] +%%-------------------------------------------------------------------- session_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:session_info(Pid); + ssl_connection:session_info(Pid); session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> {error, enotconn}. +%%--------------------------------------------------------------- +-spec versions() -> [{ssl_app, string()} | {supported, [tls_record:tls_atom_version()]} | + {available, [tls_record:tls_atom_version()]}]. +%% +%% Description: Returns a list of relevant versions. +%%-------------------------------------------------------------------- versions() -> - tls:versions(). + Vsns = tls_record:supported_protocol_versions(), + SupportedVsns = [tls_record:protocol_version(Vsn) || Vsn <- Vsns], + AvailableVsns = ?ALL_AVAILABLE_VERSIONS, + %% TODO Add DTLS versions when supported + [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}]. + -renegotiate(Socket) -> - tls:renegotiate(Socket). +%%--------------------------------------------------------------- +-spec renegotiate(#sslsocket{}) -> ok | {error, reason()}. +%% +%% Description: Initiates a renegotiation. +%%-------------------------------------------------------------------- +renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) -> + ssl_connection:renegotiation(Pid); +renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> + {error, enotconn}. -prf(Socket, Secret, Label, Seed, WantedLength) -> - tls:prf(Socket, Secret, Label, Seed, WantedLength). +%%-------------------------------------------------------------------- +-spec prf(#sslsocket{}, binary() | 'master_secret', binary(), + binary() | prf_random(), non_neg_integer()) -> + {ok, binary()} | {error, reason()}. +%% +%% Description: use a ssl sessions TLS PRF to generate key material +%%-------------------------------------------------------------------- +prf(#sslsocket{pid = Pid}, + Secret, Label, Seed, WantedLength) when is_pid(Pid) -> + ssl_connection:prf(Pid, Secret, Label, Seed, WantedLength); +prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> + {error, enotconn}. +%%-------------------------------------------------------------------- +-spec clear_pem_cache() -> ok. +%% +%% Description: Clear the PEM cache +%%-------------------------------------------------------------------- clear_pem_cache() -> - tls:clear_pem_cache(). + ssl_manager:clear_pem_cache(). + +%%--------------------------------------------------------------- +-spec format_error({error, term()}) -> list(). +%% +%% Description: Creates error string. +%%-------------------------------------------------------------------- +format_error({error, Reason}) -> + format_error(Reason); +format_error(Reason) when is_list(Reason) -> + Reason; +format_error(closed) -> + "TLS connection is closed"; +format_error({tls_alert, Description}) -> + "TLS Alert: " ++ Description; +format_error({options,{FileType, File, Reason}}) when FileType == cacertfile; + FileType == certfile; + FileType == keyfile; + FileType == dhfile -> + Error = file_error_format(Reason), + file_desc(FileType) ++ File ++ ": " ++ Error; +format_error({options, {socket_options, Option, Error}}) -> + lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, format_error(Error)])); +format_error({options, {socket_options, Option}}) -> + lists:flatten(io_lib:format("Invalid socket option: ~p", [Option])); +format_error({options, Options}) -> + lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options])); + +format_error(Error) -> + case inet:format_error(Error) of + "unknown POSIX" ++ _ -> + unexpected_format(Error); + Other -> + Other + end. + +%%%-------------------------------------------------------------- +%%% Internal functions +%%%-------------------------------------------------------------------- + +%% Possible filters out suites not supported by crypto +available_suites(default) -> + Version = tls_record:highest_protocol_version([]), + ssl_cipher:filter_suites(ssl_cipher:suites(Version)); + +available_suites(all) -> + Version = tls_record:highest_protocol_version([]), + ssl_cipher:filter_suites(ssl_cipher:all_suites(Version)). + +do_connect(Address, Port, + #config{transport_info = CbInfo, inet_user = UserOpts, ssl = SslOpts, + emulated = EmOpts, inet_ssl = SocketOpts, connection_cb = ConnetionCb}, + Timeout) -> + {Transport, _, _, _} = CbInfo, + try Transport:connect(Address, Port, SocketOpts, Timeout) of + {ok, Socket} -> + ssl_connection:connect(ConnetionCb, Address, Port, Socket, + {SslOpts, emulated_socket_options(EmOpts, #socket_options{}), undefined}, + self(), CbInfo, Timeout); + {error, Reason} -> + {error, Reason} + catch + exit:{function_clause, _} -> + {error, {options, {cb_info, CbInfo}}}; + exit:badarg -> + {error, {options, {socket_options, UserOpts}}}; + exit:{badarg, _} -> + {error, {options, {socket_options, UserOpts}}} + end. + +%% Handle extra ssl options given to ssl_accept +handle_options(Opts0, #ssl_options{protocol = Protocol, cacerts = CaCerts0, + cacertfile = CaCertFile0} = InheritedSslOpts) -> + RecordCB = record_cb(Protocol), + CaCerts = handle_option(cacerts, Opts0, CaCerts0), + {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, + VerifyClientOnce} = handle_verify_options(Opts0, CaCerts), + CaCertFile = case proplists:get_value(cacertfile, Opts0, CaCertFile0) of + undefined -> + CaCertDefault; + CAFile -> + CAFile + end, + + NewVerifyOpts = InheritedSslOpts#ssl_options{cacerts = CaCerts, + cacertfile = CaCertFile, + verify = Verify, + verify_fun = VerifyFun, + partial_chain = PartialChainHanlder, + fail_if_no_peer_cert = FailIfNoPeerCert, + verify_client_once = VerifyClientOnce}, + SslOpts1 = lists:foldl(fun(Key, PropList) -> + proplists:delete(Key, PropList) + end, Opts0, [cacerts, cacertfile, verify, verify_fun, partial_chain, + fail_if_no_peer_cert, verify_client_once]), + case handle_option(versions, SslOpts1, []) of + [] -> + new_ssl_options(SslOpts1, NewVerifyOpts, RecordCB); + Value -> + Versions = [RecordCB:protocol_version(Vsn) || Vsn <- Value], + new_ssl_options(proplists:delete(versions, SslOpts1), + NewVerifyOpts#ssl_options{versions = Versions}, record_cb(Protocol)) + end; + +%% Handle all options in listen and connect +handle_options(Opts0, Role) -> + Opts = proplists:expand([{binary, [{mode, binary}]}, + {list, [{mode, list}]}], Opts0), + assert_proplist(Opts), + RecordCb = record_cb(Opts), + + ReuseSessionFun = fun(_, _, _, _) -> true end, + CaCerts = handle_option(cacerts, Opts, undefined), + + {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun, PartialChainHanlder, VerifyClientOnce} = + handle_verify_options(Opts, CaCerts), + + CertFile = handle_option(certfile, Opts, <<>>), + RecordCb = record_cb(Opts), + + Versions = case handle_option(versions, Opts, []) of + [] -> + RecordCb:supported_protocol_versions(); + Vsns -> + [RecordCb:protocol_version(Vsn) || Vsn <- Vsns] + end, + + SSLOptions = #ssl_options{ + versions = Versions, + verify = validate_option(verify, Verify), + verify_fun = VerifyFun, + partial_chain = PartialChainHanlder, + fail_if_no_peer_cert = FailIfNoPeerCert, + verify_client_once = VerifyClientOnce, + depth = handle_option(depth, Opts, 1), + cert = handle_option(cert, Opts, undefined), + certfile = CertFile, + key = handle_option(key, Opts, undefined), + keyfile = handle_option(keyfile, Opts, CertFile), + password = handle_option(password, Opts, ""), + cacerts = CaCerts, + cacertfile = handle_option(cacertfile, Opts, CaCertDefault), + dh = handle_option(dh, Opts, undefined), + dhfile = handle_option(dhfile, Opts, undefined), + user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined), + psk_identity = handle_option(psk_identity, Opts, undefined), + srp_identity = handle_option(srp_identity, Opts, undefined), + ciphers = handle_cipher_option(proplists:get_value(ciphers, Opts, []), + RecordCb:highest_protocol_version(Versions)), + signature_algs = handle_hashsigns_option(proplists:get_value(signature_algs, Opts, + default_option_role(server, + tls_v1:default_signature_algs(Versions), Role)), + RecordCb:highest_protocol_version(Versions)), + %% Server side option + reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), + reuse_sessions = handle_option(reuse_sessions, Opts, true), + secure_renegotiate = handle_option(secure_renegotiate, Opts, false), + client_renegotiation = handle_option(client_renegotiation, Opts, + default_option_role(server, true, Role), + server, Role), + renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), + hibernate_after = handle_option(hibernate_after, Opts, infinity), + erl_dist = handle_option(erl_dist, Opts, false), + alpn_advertised_protocols = + handle_option(alpn_advertised_protocols, Opts, undefined), + alpn_preferred_protocols = + handle_option(alpn_preferred_protocols, Opts, undefined), + next_protocols_advertised = + handle_option(next_protocols_advertised, Opts, undefined), + next_protocol_selector = + make_next_protocol_selector( + handle_option(client_preferred_next_protocols, Opts, undefined)), + log_alert = handle_option(log_alert, Opts, true), + server_name_indication = handle_option(server_name_indication, Opts, undefined), + sni_hosts = handle_option(sni_hosts, Opts, []), + sni_fun = handle_option(sni_fun, Opts, undefined), + honor_cipher_order = handle_option(honor_cipher_order, Opts, + default_option_role(server, false, Role), + server, Role), + protocol = proplists:get_value(protocol, Opts, tls), + padding_check = proplists:get_value(padding_check, Opts, true), + beast_mitigation = handle_option(beast_mitigation, Opts, one_n_minus_one), + fallback = handle_option(fallback, Opts, + proplists:get_value(fallback, Opts, + default_option_role(client, + false, Role)), + client, Role), + crl_check = handle_option(crl_check, Opts, false), + crl_cache = handle_option(crl_cache, Opts, {ssl_crl_cache, {internal, []}}), + v2_hello_compatible = handle_option(v2_hello_compatible, Opts, false) + }, + + CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), + SslOptions = [protocol, versions, verify, verify_fun, partial_chain, + fail_if_no_peer_cert, verify_client_once, + depth, cert, certfile, key, keyfile, + password, cacerts, cacertfile, dh, dhfile, + user_lookup_fun, psk_identity, srp_identity, ciphers, + reuse_session, reuse_sessions, ssl_imp, client_renegotiation, + cb_info, renegotiate_at, secure_renegotiate, hibernate_after, + erl_dist, alpn_advertised_protocols, sni_hosts, sni_fun, + alpn_preferred_protocols, next_protocols_advertised, + client_preferred_next_protocols, log_alert, + server_name_indication, honor_cipher_order, padding_check, crl_check, crl_cache, + fallback, signature_algs, beast_mitigation, v2_hello_compatible], + + SockOpts = lists:foldl(fun(Key, PropList) -> + proplists:delete(Key, PropList) + end, Opts, SslOptions), + + {Sock, Emulated} = emulated_options(SockOpts), + ConnetionCb = connection_cb(Opts), + + {ok, #config{ssl = SSLOptions, emulated = Emulated, inet_ssl = Sock, + inet_user = SockOpts, transport_info = CbInfo, connection_cb = ConnetionCb + }}. + + + +handle_option(OptionName, Opts, Default, Role, Role) -> + handle_option(OptionName, Opts, Default); +handle_option(_, _, undefined = Value, _, _) -> + Value. + +handle_option(sni_fun, Opts, Default) -> + OptFun = validate_option(sni_fun, + proplists:get_value(sni_fun, Opts, Default)), + OptHosts = proplists:get_value(sni_hosts, Opts, undefined), + case {OptFun, OptHosts} of + {Default, _} -> + Default; + {_, undefined} -> + OptFun; + _ -> + throw({error, {conflict_options, [sni_fun, sni_hosts]}}) + end; +handle_option(OptionName, Opts, Default) -> + validate_option(OptionName, + proplists:get_value(OptionName, Opts, Default)). + +validate_option(versions, Versions) -> + validate_versions(Versions, Versions); +validate_option(verify, Value) + when Value == verify_none; Value == verify_peer -> + Value; +validate_option(verify_fun, undefined) -> + undefined; +%% Backwards compatibility +validate_option(verify_fun, Fun) when is_function(Fun) -> + {fun(_,{bad_cert, _} = Reason, OldFun) -> + case OldFun([Reason]) of + true -> + {valid, OldFun}; + false -> + {fail, Reason} + end; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, Fun}; +validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) -> + Value; +validate_option(partial_chain, Value) when is_function(Value) -> + Value; +validate_option(fail_if_no_peer_cert, Value) when is_boolean(Value) -> + Value; +validate_option(verify_client_once, Value) when is_boolean(Value) -> + Value; +validate_option(depth, Value) when is_integer(Value), + Value >= 0, Value =< 255-> + Value; +validate_option(cert, Value) when Value == undefined; + is_binary(Value) -> + Value; +validate_option(certfile, undefined = Value) -> + Value; +validate_option(certfile, Value) when is_binary(Value) -> + Value; +validate_option(certfile, Value) when is_list(Value) -> + binary_filename(Value); + +validate_option(key, undefined) -> + undefined; +validate_option(key, {KeyType, Value}) when is_binary(Value), + KeyType == rsa; %% Backwards compatibility + KeyType == dsa; %% Backwards compatibility + KeyType == 'RSAPrivateKey'; + KeyType == 'DSAPrivateKey'; + KeyType == 'ECPrivateKey'; + KeyType == 'PrivateKeyInfo' -> + {KeyType, Value}; + +validate_option(keyfile, undefined) -> + <<>>; +validate_option(keyfile, Value) when is_binary(Value) -> + Value; +validate_option(keyfile, Value) when is_list(Value), Value =/= "" -> + binary_filename(Value); +validate_option(password, Value) when is_list(Value) -> + Value; + +validate_option(cacerts, Value) when Value == undefined; + is_list(Value) -> + Value; +%% certfile must be present in some cases otherwhise it can be set +%% to the empty string. +validate_option(cacertfile, undefined) -> + <<>>; +validate_option(cacertfile, Value) when is_binary(Value) -> + Value; +validate_option(cacertfile, Value) when is_list(Value), Value =/= ""-> + binary_filename(Value); +validate_option(dh, Value) when Value == undefined; + is_binary(Value) -> + Value; +validate_option(dhfile, undefined = Value) -> + Value; +validate_option(dhfile, Value) when is_binary(Value) -> + Value; +validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> + binary_filename(Value); +validate_option(psk_identity, undefined) -> + undefined; +validate_option(psk_identity, Identity) + when is_list(Identity), Identity =/= "", length(Identity) =< 65535 -> + binary_filename(Identity); +validate_option(user_lookup_fun, undefined) -> + undefined; +validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) -> + Value; +validate_option(srp_identity, undefined) -> + undefined; +validate_option(srp_identity, {Username, Password}) + when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 -> + {unicode:characters_to_binary(Username), + unicode:characters_to_binary(Password)}; + +validate_option(reuse_session, Value) when is_function(Value) -> + Value; +validate_option(reuse_sessions, Value) when is_boolean(Value) -> + Value; + +validate_option(secure_renegotiate, Value) when is_boolean(Value) -> + Value; +validate_option(client_renegotiation, Value) when is_boolean(Value) -> + Value; +validate_option(renegotiate_at, Value) when is_integer(Value) -> + erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT); + +validate_option(hibernate_after, undefined) -> %% Backwards compatibility + infinity; +validate_option(hibernate_after, infinity) -> + infinity; +validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 -> + Value; + +validate_option(erl_dist,Value) when is_boolean(Value) -> + Value; +validate_option(Opt, Value) + when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols, + is_list(Value) -> + case tls_record:highest_protocol_version([]) of + {3,0} -> + throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); + _ -> + validate_binary_list(Opt, Value), + Value + end; +validate_option(Opt, Value) + when Opt =:= alpn_advertised_protocols orelse Opt =:= alpn_preferred_protocols, + Value =:= undefined -> + undefined; +validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value) + when is_list(PreferredProtocols) -> + case tls_record:highest_protocol_version([]) of + {3,0} -> + throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); + _ -> + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + {Precedence, PreferredProtocols, ?NO_PROTOCOL} + end; +validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value) + when is_list(PreferredProtocols), is_binary(Default), + byte_size(Default) > 0, byte_size(Default) < 256 -> + case tls_record:highest_protocol_version([]) of + {3,0} -> + throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); + _ -> + validate_binary_list(client_preferred_next_protocols, PreferredProtocols), + validate_npn_ordering(Precedence), + Value + end; + +validate_option(client_preferred_next_protocols, undefined) -> + undefined; +validate_option(log_alert, Value) when is_boolean(Value) -> + Value; +validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) -> + case tls_record:highest_protocol_version([]) of + {3,0} -> + throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); + _ -> + validate_binary_list(next_protocols_advertised, Value), + Value + end; + +validate_option(next_protocols_advertised, undefined) -> + undefined; +validate_option(server_name_indication, Value) when is_list(Value) -> + Value; +validate_option(server_name_indication, disable) -> + disable; +validate_option(server_name_indication, undefined) -> + undefined; +validate_option(sni_hosts, []) -> + []; +validate_option(sni_hosts, [{Hostname, SSLOptions} | Tail]) when is_list(Hostname) -> + RecursiveSNIOptions = proplists:get_value(sni_hosts, SSLOptions, undefined), + case RecursiveSNIOptions of + undefined -> + [{Hostname, validate_options(SSLOptions)} | validate_option(sni_hosts, Tail)]; + _ -> + throw({error, {options, {sni_hosts, RecursiveSNIOptions}}}) + end; +validate_option(sni_fun, undefined) -> + undefined; +validate_option(sni_fun, Fun) when is_function(Fun) -> + Fun; +validate_option(honor_cipher_order, Value) when is_boolean(Value) -> + Value; +validate_option(padding_check, Value) when is_boolean(Value) -> + Value; +validate_option(fallback, Value) when is_boolean(Value) -> + Value; +validate_option(crl_check, Value) when is_boolean(Value) -> + Value; +validate_option(crl_check, Value) when (Value == best_effort) or (Value == peer) -> + Value; +validate_option(crl_cache, {Cb, {_Handle, Options}} = Value) when is_atom(Cb) and is_list(Options) -> + Value; +validate_option(beast_mitigation, Value) when Value == one_n_minus_one orelse + Value == zero_n orelse + Value == disabled -> + Value; +validate_option(v2_hello_compatible, Value) when is_boolean(Value) -> + Value; +validate_option(Opt, Value) -> + throw({error, {options, {Opt, Value}}}). + +handle_hashsigns_option(Value, {Major, Minor} = Version) when is_list(Value) + andalso Major >= 3 andalso Minor >= 3-> + case tls_v1:signature_algs(Version, Value) of + [] -> + throw({error, {options, no_supported_algorithms, {signature_algs, Value}}}); + _ -> + Value + end; +handle_hashsigns_option(_, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3-> + handle_hashsigns_option(tls_v1:default_signature_algs(Version), Version); +handle_hashsigns_option(_, _Version) -> + undefined. + +validate_options([]) -> + []; +validate_options([{Opt, Value} | Tail]) -> + [{Opt, validate_option(Opt, Value)} | validate_options(Tail)]. + +validate_npn_ordering(client) -> + ok; +validate_npn_ordering(server) -> + ok; +validate_npn_ordering(Value) -> + throw({error, {options, {client_preferred_next_protocols, {invalid_precedence, Value}}}}). + +validate_binary_list(Opt, List) -> + lists:foreach( + fun(Bin) when is_binary(Bin), + byte_size(Bin) > 0, + byte_size(Bin) < 256 -> + ok; + (Bin) -> + throw({error, {options, {Opt, {invalid_protocol, Bin}}}}) + end, List). + +validate_versions([], Versions) -> + Versions; +validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; + Version == 'tlsv1.1'; + Version == tlsv1; + Version == sslv3 -> + validate_versions(Rest, Versions); +validate_versions([Ver| _], Versions) -> + throw({error, {options, {Ver, {versions, Versions}}}}). + +validate_inet_option(mode, Value) + when Value =/= list, Value =/= binary -> + throw({error, {options, {mode,Value}}}); +validate_inet_option(packet, Value) + when not (is_atom(Value) orelse is_integer(Value)) -> + throw({error, {options, {packet,Value}}}); +validate_inet_option(packet_size, Value) + when not is_integer(Value) -> + throw({error, {options, {packet_size,Value}}}); +validate_inet_option(header, Value) + when not is_integer(Value) -> + throw({error, {options, {header,Value}}}); +validate_inet_option(active, Value) + when Value =/= true, Value =/= false, Value =/= once -> + throw({error, {options, {active,Value}}}); +validate_inet_option(_, _) -> + ok. + +%% The option cacerts overrides cacertsfile +ca_cert_default(_,_, [_|_]) -> + undefined; +ca_cert_default(verify_none, _, _) -> + undefined; +ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) -> + undefined; +%% Server that wants to verify_peer and has no verify_fun must have +%% some trusted certs. +ca_cert_default(verify_peer, undefined, _) -> + "". +emulated_options(Opts) -> + emulated_options(Opts, ssl_socket:internal_inet_values(), ssl_socket:default_inet_values()). + +emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(mode, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]); +emulated_options([{header, Value} = Opt | Opts], Inet, Emulated) -> + validate_inet_option(header, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(header, Emulated)]); +emulated_options([{active, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(active, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(active, Emulated)]); +emulated_options([{packet, Value} = Opt |Opts], Inet, Emulated) -> + validate_inet_option(packet, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(packet, Emulated)]); +emulated_options([{packet_size, Value} = Opt | Opts], Inet, Emulated) -> + validate_inet_option(packet_size, Value), + emulated_options(Opts, Inet, [Opt | proplists:delete(packet_size, Emulated)]); +emulated_options([Opt|Opts], Inet, Emulated) -> + emulated_options(Opts, [Opt|Inet], Emulated); +emulated_options([], Inet,Emulated) -> + {Inet, Emulated}. + +handle_cipher_option(Value, Version) when is_list(Value) -> + try binary_cipher_suites(Version, Value) of + Suites -> + Suites + catch + exit:_ -> + throw({error, {options, {ciphers, Value}}}); + error:_-> + throw({error, {options, {ciphers, Value}}}) + end. + +binary_cipher_suites(Version, []) -> + %% Defaults to all supported suites that does + %% not require explicit configuration + ssl_cipher:filter_suites(ssl_cipher:suites(Version)); +binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> + Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0], + binary_cipher_suites(Version, Ciphers); + +binary_cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> + All = ssl_cipher:all_suites(Version), + case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, All)] of + [] -> + %% Defaults to all supported suites that does + %% not require explicit configuration + ssl_cipher:filter_suites(ssl_cipher:suites(Version)); + Ciphers -> + Ciphers + end; +binary_cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> + %% Format: ["RC4-SHA","RC4-MD5"] + Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0], + binary_cipher_suites(Version, Ciphers); +binary_cipher_suites(Version, Ciphers0) -> + %% Format: "RC4-SHA:RC4-MD5" + Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")], + binary_cipher_suites(Version, Ciphers). + +unexpected_format(Error) -> + lists:flatten(io_lib:format("Unexpected error: ~p", [Error])). + +file_error_format({error, Error})-> + case file:format_error(Error) of + "unknown POSIX error" -> + "decoding error"; + Str -> + Str + end; +file_error_format(_) -> + "decoding error". + +file_desc(cacertfile) -> + "Invalid CA certificate file "; +file_desc(certfile) -> + "Invalid certificate file "; +file_desc(keyfile) -> + "Invalid key file "; +file_desc(dhfile) -> + "Invalid DH params file ". + +detect(_Pred, []) -> + undefined; +detect(Pred, [H|T]) -> + case Pred(H) of + true -> + H; + _ -> + detect(Pred, T) + end. + +make_next_protocol_selector(undefined) -> + undefined; +make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) -> + fun(AdvertisedProtocols) -> + case detect(fun(PreferredProtocol) -> + lists:member(PreferredProtocol, AdvertisedProtocols) + end, AllProtocols) of + undefined -> + DefaultProtocol; + PreferredProtocol -> + PreferredProtocol + end + end; + +make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) -> + fun(AdvertisedProtocols) -> + case detect(fun(PreferredProtocol) -> + lists:member(PreferredProtocol, AllProtocols) + end, + AdvertisedProtocols) of + undefined -> + DefaultProtocol; + PreferredProtocol -> + PreferredProtocol + end + end. + +connection_cb(tls) -> + tls_connection; +connection_cb(dtls) -> + dtls_connection; +connection_cb(Opts) -> + connection_cb(proplists:get_value(protocol, Opts, tls)). + +record_cb(tls) -> + tls_record; +record_cb(dtls) -> + dtls_record; +record_cb(Opts) -> + record_cb(proplists:get_value(protocol, Opts, tls)). + +connection_sup(tls_connection) -> + tls_connection_sup; +connection_sup(dtls_connection) -> + dtls_connection_sup. + +binary_filename(FileName) -> + Enc = file:native_name_encoding(), + unicode:characters_to_binary(FileName, unicode, Enc). + +assert_proplist([]) -> + true; +assert_proplist([{Key,_} | Rest]) when is_atom(Key) -> + assert_proplist(Rest); +%% Handle exceptions +assert_proplist([{raw,_,_,_} | Rest]) -> + assert_proplist(Rest); +assert_proplist([inet | Rest]) -> + assert_proplist(Rest); +assert_proplist([inet6 | Rest]) -> + assert_proplist(Rest); +assert_proplist([Value | _]) -> + throw({option_not_a_key_value_tuple, Value}). + +emulated_socket_options(InetValues, #socket_options{ + mode = Mode, + header = Header, + active = Active, + packet = Packet, + packet_size = Size}) -> + #socket_options{ + mode = proplists:get_value(mode, InetValues, Mode), + header = proplists:get_value(header, InetValues, Header), + active = proplists:get_value(active, InetValues, Active), + packet = proplists:get_value(packet, InetValues, Packet), + packet_size = proplists:get_value(packet_size, InetValues, Size) + }. + +new_ssl_options([], #ssl_options{} = Opts, _) -> + Opts; +new_ssl_options([{verify_client_once, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{verify_client_once = + validate_option(verify_client_once, Value)}, RecordCB); +new_ssl_options([{depth, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{depth = validate_option(depth, Value)}, RecordCB); +new_ssl_options([{cert, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{cert = validate_option(cert, Value)}, RecordCB); +new_ssl_options([{certfile, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{certfile = validate_option(certfile, Value)}, RecordCB); +new_ssl_options([{key, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{key = validate_option(key, Value)}, RecordCB); +new_ssl_options([{keyfile, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{keyfile = validate_option(keyfile, Value)}, RecordCB); +new_ssl_options([{password, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{password = validate_option(password, Value)}, RecordCB); +new_ssl_options([{dh, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{dh = validate_option(dh, Value)}, RecordCB); +new_ssl_options([{dhfile, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{dhfile = validate_option(dhfile, Value)}, RecordCB); +new_ssl_options([{user_lookup_fun, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{user_lookup_fun = validate_option(user_lookup_fun, Value)}, RecordCB); +new_ssl_options([{psk_identity, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{psk_identity = validate_option(psk_identity, Value)}, RecordCB); +new_ssl_options([{srp_identity, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{srp_identity = validate_option(srp_identity, Value)}, RecordCB); +new_ssl_options([{ciphers, Value} | Rest], #ssl_options{versions = Versions} = Opts, RecordCB) -> + Ciphers = handle_cipher_option(Value, RecordCB:highest_protocol_version(Versions)), + new_ssl_options(Rest, + Opts#ssl_options{ciphers = Ciphers}, RecordCB); +new_ssl_options([{reuse_session, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{reuse_session = validate_option(reuse_session, Value)}, RecordCB); +new_ssl_options([{reuse_sessions, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{reuse_sessions = validate_option(reuse_sessions, Value)}, RecordCB); +new_ssl_options([{ssl_imp, _Value} | Rest], #ssl_options{} = Opts, RecordCB) -> %% Not used backwards compatibility + new_ssl_options(Rest, Opts, RecordCB); +new_ssl_options([{renegotiate_at, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{ renegotiate_at = validate_option(renegotiate_at, Value)}, RecordCB); +new_ssl_options([{secure_renegotiate, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{secure_renegotiate = validate_option(secure_renegotiate, Value)}, RecordCB); +new_ssl_options([{client_renegotiation, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{client_renegotiation = validate_option(client_renegotiation, Value)}, RecordCB); +new_ssl_options([{hibernate_after, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{hibernate_after = validate_option(hibernate_after, Value)}, RecordCB); +new_ssl_options([{alpn_advertised_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{alpn_advertised_protocols = validate_option(alpn_advertised_protocols, Value)}, RecordCB); +new_ssl_options([{alpn_preferred_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{alpn_preferred_protocols = validate_option(alpn_preferred_protocols, Value)}, RecordCB); +new_ssl_options([{next_protocols_advertised, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{next_protocols_advertised = validate_option(next_protocols_advertised, Value)}, RecordCB); +new_ssl_options([{client_preferred_next_protocols, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{next_protocol_selector = + make_next_protocol_selector(validate_option(client_preferred_next_protocols, Value))}, RecordCB); +new_ssl_options([{log_alert, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{log_alert = validate_option(log_alert, Value)}, RecordCB); +new_ssl_options([{server_name_indication, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{server_name_indication = validate_option(server_name_indication, Value)}, RecordCB); +new_ssl_options([{honor_cipher_order, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, Opts#ssl_options{honor_cipher_order = validate_option(honor_cipher_order, Value)}, RecordCB); +new_ssl_options([{signature_algs, Value} | Rest], #ssl_options{} = Opts, RecordCB) -> + new_ssl_options(Rest, + Opts#ssl_options{signature_algs = + handle_hashsigns_option(Value, + RecordCB:highest_protocol_version())}, + RecordCB); + +new_ssl_options([{Key, Value} | _Rest], #ssl_options{}, _) -> + throw({error, {options, {Key, Value}}}). + + +handle_verify_options(Opts, CaCerts) -> + DefaultVerifyNoneFun = + {fun(_,{bad_cert, _}, UserState) -> + {valid, UserState}; + (_,{extension, #'Extension'{critical = true}}, UserState) -> + %% This extension is marked as critical, so + %% certificate verification should fail if we don't + %% understand the extension. However, this is + %% `verify_none', so let's accept it anyway. + {valid, UserState}; + (_,{extension, _}, UserState) -> + {unknown, UserState}; + (_, valid, UserState) -> + {valid, UserState}; + (_, valid_peer, UserState) -> + {valid, UserState} + end, []}, + VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun), + + UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false), + UserVerifyFun = handle_option(verify_fun, Opts, undefined), + + PartialChainHanlder = handle_option(partial_chain, Opts, + fun(_) -> unknown_ca end), -format_error(Error) -> - tls:format_error(Error). + VerifyClientOnce = handle_option(verify_client_once, Opts, false), -random_bytes(N) -> - tls:random_bytes(N). + %% Handle 0, 1, 2 for backwards compatibility + case proplists:get_value(verify, Opts, verify_none) of + 0 -> + {verify_none, false, + ca_cert_default(verify_none, VerifyNoneFun, CaCerts), + VerifyNoneFun, PartialChainHanlder, VerifyClientOnce}; + 1 -> + {verify_peer, false, + ca_cert_default(verify_peer, UserVerifyFun, CaCerts), + UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; + 2 -> + {verify_peer, true, + ca_cert_default(verify_peer, UserVerifyFun, CaCerts), + UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; + verify_none -> + {verify_none, false, + ca_cert_default(verify_none, VerifyNoneFun, CaCerts), + VerifyNoneFun, PartialChainHanlder, VerifyClientOnce}; + verify_peer -> + {verify_peer, UserFailIfNoPeerCert, + ca_cert_default(verify_peer, UserVerifyFun, CaCerts), + UserVerifyFun, PartialChainHanlder, VerifyClientOnce}; + Value -> + throw({error, {options, {verify, Value}}}) + end. +default_option_role(Role, Value, Role) -> + Value; +default_option_role(_,_,_) -> + undefined. diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl index 1810043dfb..db71b16d80 100644 --- a/lib/ssl/src/ssl_alert.erl +++ b/lib/ssl/src/ssl_alert.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,12 +30,32 @@ -include("ssl_alert.hrl"). -include("ssl_record.hrl"). +-include("ssl_internal.hrl"). --export([alert_txt/1, reason_code/2]). +-export([encode/3, decode/1, alert_txt/1, reason_code/2]). %%==================================================================== %% Internal application API %%==================================================================== + +%%-------------------------------------------------------------------- +-spec encode(#alert{}, ssl_record:ssl_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes an alert +%%-------------------------------------------------------------------- +encode(#alert{} = Alert, Version, ConnectionStates) -> + ssl_record:encode_alert_record(Alert, Version, ConnectionStates). + +%%-------------------------------------------------------------------- +-spec decode(binary()) -> [#alert{}] | #alert{}. +%% +%% Description: Decode alert(s), will return a singel own alert if peer +%% sends garbage or too many warning alerts. +%%-------------------------------------------------------------------- +decode(Bin) -> + decode(Bin, [], 0). + %%-------------------------------------------------------------------- -spec reason_code(#alert{}, client | server) -> closed | {essl, string()}. %% @@ -52,14 +73,34 @@ reason_code(#alert{description = Description}, _) -> %% %% Description: Returns the error string for given alert. %%-------------------------------------------------------------------- - -alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}}) -> +alert_txt(#alert{level = Level, description = Description, where = {Mod,Line}, reason = undefined}) -> Mod ++ ":" ++ integer_to_list(Line) ++ ":" ++ - level_txt(Level) ++" "++ description_txt(Description). + level_txt(Level) ++" "++ description_txt(Description); +alert_txt(#alert{reason = Reason} = Alert) -> + BaseTxt = alert_txt(Alert#alert{reason = undefined}), + FormatDepth = 9, % Some limit on printed representation of an error + ReasonTxt = lists:flatten(io_lib:format("~P", [Reason, FormatDepth])), + BaseTxt ++ " - " ++ ReasonTxt. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- + +%% It is very unlikely that an correct implementation will send more than one alert at the time +%% So it there is more than 10 warning alerts we consider it an error +decode(<<?BYTE(Level), ?BYTE(_), _/binary>>, _, N) when Level == ?WARNING, N > ?MAX_ALERTS -> + ?ALERT_REC(?FATAL, ?DECODE_ERROR, too_many_remote_alerts); +decode(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc, N) when Level == ?WARNING -> + Alert = ?ALERT_REC(Level, Description), + decode(Rest, [Alert | Acc], N + 1); +decode(<<?BYTE(Level), ?BYTE(Description), _Rest/binary>>, Acc, _) when Level == ?FATAL-> + Alert = ?ALERT_REC(Level, Description), + lists:reverse([Alert | Acc]); %% No need to decode rest fatal alert will end the connection +decode(<<?BYTE(_Level), _/binary>>, _, _) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER, failed_to_decode_remote_alert); +decode(<<>>, Acc, _) -> + lists:reverse(Acc, []). + level_txt(?WARNING) -> "Warning:"; level_txt(?FATAL) -> @@ -113,5 +154,21 @@ description_txt(?USER_CANCELED) -> "user canceled"; description_txt(?NO_RENEGOTIATION) -> "no renegotiation"; +description_txt(?UNSUPPORTED_EXTENSION) -> + "unsupported extension"; +description_txt(?CERTIFICATE_UNOBTAINABLE) -> + "certificate unobtainable"; +description_txt(?UNRECOGNISED_NAME) -> + "unrecognised name"; +description_txt(?BAD_CERTIFICATE_STATUS_RESPONSE) -> + "bad certificate status response"; +description_txt(?BAD_CERTIFICATE_HASH_VALUE) -> + "bad certificate hash value"; description_txt(?UNKNOWN_PSK_IDENTITY) -> - "unknown psk identity". + "unknown psk identity"; +description_txt(?INAPPROPRIATE_FALLBACK) -> + "inappropriate fallback"; +description_txt(?NO_APPLICATION_PROTOCOL) -> + "no application protocol"; +description_txt(Enum) -> + lists:flatten(io_lib:format("unsupported/unknown alert: ~p", [Enum])). diff --git a/lib/ssl/src/ssl_alert.hrl b/lib/ssl/src/ssl_alert.hrl index 2a8a91aefa..38facb964f 100644 --- a/lib/ssl/src/ssl_alert.hrl +++ b/lib/ssl/src/ssl_alert.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -48,7 +49,7 @@ %% unsupported_certificate(43), %% certificate_revoked(44), %% certificate_expired(45), - %% certificate_unknown(46), +%% certificate_unknown(46), %% illegal_parameter(47), %% unknown_ca(48), %% access_denied(49), @@ -58,9 +59,19 @@ %% protocol_version(70), %% insufficient_security(71), %% internal_error(80), +%% inappropriate_fallback(86), %% user_canceled(90), %% no_renegotiation(100), +%% RFC 4366 +%% unsupported_extension(110), +%% certificate_unobtainable(111), +%% unrecognized_name(112), +%% bad_certificate_status_response(113), +%% bad_certificate_hash_value(114), +%% RFC 4366 %% unknown_psk_identity(115), +%% RFC 7301 +%% no_application_protocol(120), %% (255) %% } AlertDescription; @@ -86,16 +97,27 @@ -define(PROTOCOL_VERSION, 70). -define(INSUFFICIENT_SECURITY, 71). -define(INTERNAL_ERROR, 80). +-define(INAPPROPRIATE_FALLBACK, 86). -define(USER_CANCELED, 90). -define(NO_RENEGOTIATION, 100). +-define(UNSUPPORTED_EXTENSION, 110). +-define(CERTIFICATE_UNOBTAINABLE, 111). +-define(UNRECOGNISED_NAME, 112). +-define(BAD_CERTIFICATE_STATUS_RESPONSE, 113). +-define(BAD_CERTIFICATE_HASH_VALUE, 114). -define(UNKNOWN_PSK_IDENTITY, 115). +-define(NO_APPLICATION_PROTOCOL, 120). -define(ALERT_REC(Level,Desc), #alert{level=Level,description=Desc,where={?FILE, ?LINE}}). +-define(ALERT_REC(Level,Desc,Reason), #alert{level=Level,description=Desc,where={?FILE, ?LINE},reason=Reason}). + +-define(MAX_ALERTS, 10). %% Alert -record(alert, { level, description, - where = {?FILE, ?LINE} + where = {?FILE, ?LINE}, + reason }). -endif. % -ifdef(ssl_alert). diff --git a/lib/ssl/src/ssl_api.hrl b/lib/ssl/src/ssl_api.hrl new file mode 100644 index 0000000000..2bd51cf91e --- /dev/null +++ b/lib/ssl/src/ssl_api.hrl @@ -0,0 +1,68 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-ifndef(ssl_api). +-define(ssl_api, true). + +-include("ssl_cipher.hrl"). + +%% Visible in API +-export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0, + prf_random/0, sslsocket/0]). + + +%% Looks like it does for backwards compatibility reasons +-record(sslsocket, {fd = nil, pid = nil}). + + +-type sslsocket() :: #sslsocket{}. +-type connect_option() :: socket_connect_option() | ssl_option() | transport_option(). +-type socket_connect_option() :: gen_tcp:connect_option(). +-type listen_option() :: socket_listen_option() | ssl_option() | transport_option(). +-type socket_listen_option() :: gen_tcp:listen_option(). + +-type ssl_option() :: {versions, ssl_record:ssl_atom_version()} | + {verify, verify_type()} | + {verify_fun, {fun(), InitialUserState::term()}} | + {fail_if_no_peer_cert, boolean()} | {depth, integer()} | + {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} | + {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} | + {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} | + {user_lookup_fun, {fun(), InitialUserState::term()}} | + {psk_identity, string()} | + {srp_identity, {string(), string()}} | + {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | + {reuse_session, fun()} | {hibernate_after, integer()|undefined} | + {alpn_advertised_protocols, [binary()]} | + {alpn_preferred_protocols, [binary()]} | + {next_protocols_advertised, list(binary())} | + {client_preferred_next_protocols, binary(), client | server, list(binary())}. + +-type verify_type() :: verify_none | verify_peer. +-type path() :: string(). +-type ciphers() :: [ssl_cipher:erl_cipher_suite()] | + string(). % (according to old API) +-type ssl_imp() :: new | old. + +-type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(), + ClosedTag::atom(), ErrTag::atom()}}. +-type prf_random() :: client_random | server_random. + +-endif. % -ifdef(ssl_api). diff --git a/lib/ssl/src/ssl_app.erl b/lib/ssl/src/ssl_app.erl index 0c475a6d01..62e8765d4a 100644 --- a/lib/ssl/src/ssl_app.erl +++ b/lib/ssl/src/ssl_app.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2011. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% diff --git a/lib/ssl/src/ssl_certificate.erl b/lib/ssl/src/ssl_certificate.erl index b186a1015a..3ec3f50e05 100644 --- a/lib/ssl/src/ssl_certificate.erl +++ b/lib/ssl/src/ssl_certificate.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016 All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -30,10 +31,11 @@ -include("ssl_internal.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([trusted_cert_and_path/3, +-export([trusted_cert_and_path/4, certificate_chain/3, file_to_certificats/2, - validate_extension/3, + file_to_crls/2, + validate/3, is_valid_extkey_usage/2, is_valid_key_usage/2, select_extension/2, @@ -46,62 +48,56 @@ %%==================================================================== %%-------------------------------------------------------------------- --spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref()) -> +-spec trusted_cert_and_path([der_cert()], db_handle(), certdb_ref(), fun()) -> {der_cert() | unknown_ca, [der_cert()]}. %% %% Description: Extracts the root cert (if not presents tries to %% look it up, if not found {bad_cert, unknown_ca} will be added verification %% errors. Returns {RootCert, Path, VerifyErrors} %%-------------------------------------------------------------------- -trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef) -> - Path = [Cert | _] = lists:reverse(CertChain), - OtpCert = public_key:pkix_decode_cert(Cert, otp), +trusted_cert_and_path(CertChain, CertDbHandle, CertDbRef, PartialChainHandler) -> + Path = [BinCert | _] = lists:reverse(CertChain), + OtpCert = public_key:pkix_decode_cert(BinCert, otp), SignedAndIssuerID = case public_key:pkix_is_self_signed(OtpCert) of true -> {ok, IssuerId} = public_key:pkix_issuer_id(OtpCert, self), {self, IssuerId}; false -> - case public_key:pkix_issuer_id(OtpCert, other) of - {ok, IssuerId} -> - {other, IssuerId}; - {error, issuer_not_found} -> - case find_issuer(OtpCert, CertDbHandle) of - {ok, IssuerId} -> - {other, IssuerId}; - Other -> - Other - end - end + other_issuer(OtpCert, BinCert, CertDbHandle) end, case SignedAndIssuerID of {error, issuer_not_found} -> %% The root CA was not sent and can not be found. - {unknown_ca, Path}; + handle_incomplete_chain(Path, PartialChainHandler); {self, _} when length(Path) == 1 -> {selfsigned_peer, Path}; {_ ,{SerialNr, Issuer}} -> case ssl_manager:lookup_trusted_cert(CertDbHandle, CertDbRef, SerialNr, Issuer) of - {ok, {BinCert,_}} -> - {BinCert, Path}; + {ok, Trusted} -> + %% Trusted must be selfsigned or it is an incomplete chain + handle_path(Trusted, Path, PartialChainHandler); _ -> %% Root CA could not be verified - {unknown_ca, Path} + handle_incomplete_chain(Path, PartialChainHandler) end end. %%-------------------------------------------------------------------- --spec certificate_chain(undefined | binary(), db_handle(), certdb_ref()) -> - {error, no_cert} | {ok, [der_cert()]}. +-spec certificate_chain(undefined | binary() | #'OTPCertificate'{} , db_handle(), certdb_ref()) -> + {error, no_cert} | {ok, #'OTPCertificate'{} | undefined, [der_cert()]}. %% %% Description: Return the certificate chain to send to peer. %%-------------------------------------------------------------------- certificate_chain(undefined, _, _) -> {error, no_cert}; -certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> +certificate_chain(OwnCert, CertDbHandle, CertsDbRef) when is_binary(OwnCert) -> ErlCert = public_key:pkix_decode_cert(OwnCert, otp), - certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert]). + certificate_chain(ErlCert, OwnCert, CertDbHandle, CertsDbRef, [OwnCert]); +certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> + DerCert = public_key:pkix_encode('OTPCertificate', OwnCert, otp), + certificate_chain(OwnCert, DerCert, CertDbHandle, CertsDbRef, [DerCert]). %%-------------------------------------------------------------------- -spec file_to_certificats(binary(), term()) -> [der_cert()]. %% @@ -110,29 +106,39 @@ certificate_chain(OwnCert, CertDbHandle, CertsDbRef) -> file_to_certificats(File, DbHandle) -> {ok, List} = ssl_manager:cache_pem_file(File, DbHandle), [Bin || {'Certificate', Bin, not_encrypted} <- List]. + %%-------------------------------------------------------------------- --spec validate_extension(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid, - term()) -> {valid, term()} | - {fail, tuple()} | - {unknown, term()}. +-spec file_to_crls(binary(), term()) -> [der_cert()]. +%% +%% Description: Return list of DER encoded certificates. +%%-------------------------------------------------------------------- +file_to_crls(File, DbHandle) -> + {ok, List} = ssl_manager:cache_pem_file(File, DbHandle), + [Bin || {'CertificateList', Bin, not_encrypted} <- List]. + +%%-------------------------------------------------------------------- +-spec validate(term(), {extension, #'Extension'{}} | {bad_cert, atom()} | valid, + term()) -> {valid, term()} | + {fail, tuple()} | + {unknown, term()}. %% %% Description: Validates ssl/tls specific extensions %%-------------------------------------------------------------------- -validate_extension(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage', - extnValue = KeyUse}}, Role) -> +validate(_,{extension, #'Extension'{extnID = ?'id-ce-extKeyUsage', + extnValue = KeyUse}}, {Role, _,_, _, _}) -> case is_valid_extkey_usage(KeyUse, Role) of true -> {valid, Role}; false -> {fail, {bad_cert, invalid_ext_key_usage}} end; -validate_extension(_, {bad_cert, _} = Reason, _) -> - {fail, Reason}; -validate_extension(_, {extension, _}, Role) -> +validate(_, {extension, _}, Role) -> {unknown, Role}; -validate_extension(_, valid, Role) -> +validate(_, {bad_cert, _} = Reason, _) -> + {fail, Reason}; +validate(_, valid, Role) -> {valid, Role}; -validate_extension(_, valid_peer, Role) -> +validate(_, valid_peer, Role) -> {valid, Role}. %%-------------------------------------------------------------------- @@ -181,7 +187,7 @@ public_key_type(?'id-ecPublicKey') -> %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -certificate_chain(OtpCert, _Cert, CertDbHandle, CertsDbRef, Chain) -> +certificate_chain(OtpCert, BinCert, CertDbHandle, CertsDbRef, Chain) -> IssuerAndSelfSigned = case public_key:pkix_is_self_signed(OtpCert) of true -> @@ -194,7 +200,7 @@ certificate_chain(OtpCert, _Cert, CertDbHandle, CertsDbRef, Chain) -> {_, true = SelfSigned} -> certificate_chain(CertDbHandle, CertsDbRef, Chain, ignore, ignore, SelfSigned); {{error, issuer_not_found}, SelfSigned} -> - case find_issuer(OtpCert, CertDbHandle) of + case find_issuer(OtpCert, BinCert, CertDbHandle) of {ok, {SerialNr, Issuer}} -> certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned); @@ -203,14 +209,14 @@ certificate_chain(OtpCert, _Cert, CertDbHandle, CertsDbRef, Chain) -> %% certificate. The verification of the %% cert chain will fail if guess is %% incorrect. - {ok, lists:reverse(Chain)} + {ok, undefined, lists:reverse(Chain)} end; {{ok, {SerialNr, Issuer}}, SelfSigned} -> certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, SelfSigned) end. -certificate_chain(_,_, Chain, _SerialNr, _Issuer, true) -> - {ok, lists:reverse(Chain)}; +certificate_chain(_, _, [RootCert | _] = Chain, _, _, true) -> + {ok, RootCert, lists:reverse(Chain)}; certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned) -> case ssl_manager:lookup_trusted_cert(CertDbHandle, CertsDbRef, @@ -222,23 +228,27 @@ certificate_chain(CertDbHandle, CertsDbRef, Chain, SerialNr, Issuer, _SelfSigned _ -> %% The trusted cert may be obmitted from the chain as the %% counter part needs to have it anyway to be able to - %% verify it. This will be the normal case for servers - %% that does not verify the clients and hence have not - %% specified the cacertfile. - {ok, lists:reverse(Chain)} + %% verify it. + {ok, undefined, lists:reverse(Chain)} end. -find_issuer(OtpCert, CertDbHandle) -> - IsIssuerFun = fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> - case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of - true -> - throw(public_key:pkix_issuer_id(ErlCertCandidate, self)); - false -> - Acc - end; - (_, Acc) -> - Acc - end, +find_issuer(OtpCert, BinCert, CertDbHandle) -> + IsIssuerFun = + fun({_Key, {_Der, #'OTPCertificate'{} = ErlCertCandidate}}, Acc) -> + case public_key:pkix_is_issuer(OtpCert, ErlCertCandidate) of + true -> + case verify_cert_signer(BinCert, ErlCertCandidate#'OTPCertificate'.tbsCertificate) of + true -> + throw(public_key:pkix_issuer_id(ErlCertCandidate, self)); + false -> + Acc + end; + false -> + Acc + end; + (_, Acc) -> + Acc + end, try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, CertDbHandle) of issuer_not_found -> @@ -254,3 +264,57 @@ is_valid_extkey_usage(KeyUse, client) -> is_valid_extkey_usage(KeyUse, server) -> %% Server wants to verify client is_valid_key_usage(KeyUse, ?'id-kp-clientAuth'). + +verify_cert_signer(BinCert, SignerTBSCert) -> + PublicKey = public_key(SignerTBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo), + public_key:pkix_verify(BinCert, PublicKey). + +public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-ecPublicKey', + parameters = Params}, + subjectPublicKey = Point}) -> + {Point, Params}; +public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'rsaEncryption'}, + subjectPublicKey = Key}) -> + Key; +public_key(#'OTPSubjectPublicKeyInfo'{algorithm = #'PublicKeyAlgorithm'{algorithm = ?'id-dsa', + parameters = {params, Params}}, + subjectPublicKey = Key}) -> + {Key, Params}. + +other_issuer(OtpCert, BinCert, CertDbHandle) -> + case public_key:pkix_issuer_id(OtpCert, other) of + {ok, IssuerId} -> + {other, IssuerId}; + {error, issuer_not_found} -> + case find_issuer(OtpCert, BinCert, CertDbHandle) of + {ok, IssuerId} -> + {other, IssuerId}; + Other -> + Other + end + end. + +handle_path({BinCert, OTPCert}, Path, PartialChainHandler) -> + case public_key:pkix_is_self_signed(OTPCert) of + true -> + {BinCert, lists:delete(BinCert, Path)}; + false -> + handle_incomplete_chain(Path, PartialChainHandler) + end. + +handle_incomplete_chain(Chain, Fun) -> + case catch Fun(Chain) of + {trusted_ca, DerCert} -> + new_trusteded_chain(DerCert, Chain); + unknown_ca = Error -> + {Error, Chain}; + _ -> + {unknown_ca, Chain} + end. + +new_trusteded_chain(DerCert, [DerCert | Chain]) -> + {DerCert, Chain}; +new_trusteded_chain(DerCert, [_ | Rest]) -> + new_trusteded_chain(DerCert, Rest); +new_trusteded_chain(_, []) -> + unknown_ca. diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index ec5d793d65..e935c033c7 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,18 +1,19 @@ -%% +% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% http://www.apache.org/licenses/LICENSE-2.0 %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -32,16 +33,49 @@ -include("ssl_alert.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([security_parameters/3, suite_definition/1, - decipher/5, cipher/5, - suite/1, suites/1, anonymous_suites/0, psk_suites/1, srp_suites/0, - openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, - hash_algorithm/1, sign_algorithm/1]). +-export([security_parameters/2, security_parameters/3, suite_definition/1, + erl_suite_definition/1, + cipher_init/3, decipher/6, cipher/5, decipher_aead/6, cipher_aead/6, + suite/1, suites/1, all_suites/1, + ec_keyed_suites/0, anonymous_suites/1, psk_suites/1, srp_suites/0, + rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, + hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, + random_bytes/1]). + +-export_type([cipher_suite/0, + erl_cipher_suite/0, openssl_cipher_suite/0, + hash/0, key_algo/0, sign_algo/0]). + +-type cipher() :: null |rc4_128 | idea_cbc | des40_cbc | des_cbc | '3des_ede_cbc' + | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305. +-type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512. +-type sign_algo() :: rsa | dsa | ecdsa. +-type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | + psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. +-type erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2 + %% TLS 1.2, internally PRE TLS 1.2 will use default_prf + | {key_algo(), cipher(), hash(), hash() | default_prf}. + + +-type cipher_suite() :: binary(). +-type cipher_enum() :: integer(). +-type openssl_cipher_suite() :: string(). + -compile(inline). %%-------------------------------------------------------------------- --spec security_parameters(tls_version(), cipher_suite(), #security_parameters{}) -> +-spec security_parameters(cipher_suite(), #security_parameters{}) -> + #security_parameters{}. +%% Only security_parameters/2 should call security_parameters/3 with undefined as +%% first argument. +%%-------------------------------------------------------------------- + +security_parameters(?TLS_NULL_WITH_NULL_NULL = CipherSuite, SecParams) -> + security_parameters(undefined, CipherSuite, SecParams). + +%%-------------------------------------------------------------------- +-spec security_parameters(ssl_record:ssl_version() | undefined, cipher_suite(), #security_parameters{}) -> #security_parameters{}. %% %% Description: Returns a security parameters record where the @@ -62,20 +96,32 @@ security_parameters(Version, CipherSuite, SecParams) -> hash_size = hash_size(Hash)}. %%-------------------------------------------------------------------- --spec cipher(cipher_enum(), #cipher_state{}, binary(), binary(), tls_version()) -> +-spec cipher_init(cipher_enum(), binary(), binary()) -> #cipher_state{}. +%% +%% Description: Initializes the #cipher_state according to BCA +%%------------------------------------------------------------------- +cipher_init(?RC4, IV, Key) -> + State = crypto:stream_init(rc4, Key), + #cipher_state{iv = IV, key = Key, state = State}; +cipher_init(?AES_GCM, IV, Key) -> + <<Nonce:64>> = random_bytes(8), + #cipher_state{iv = IV, key = Key, nonce = Nonce}; +cipher_init(_BCA, IV, Key) -> + #cipher_state{iv = IV, key = Key}. + +%%-------------------------------------------------------------------- +-spec cipher(cipher_enum(), #cipher_state{}, binary(), iodata(), ssl_record:ssl_version()) -> {binary(), #cipher_state{}}. %% %% Description: Encrypts the data and the MAC using chipher described %% by cipher_enum() and updating the cipher state +%% Used for "MAC then Cipher" suites where first an HMAC of the +%% data is calculated and the data plus the HMAC is ecncrypted. %%------------------------------------------------------------------- cipher(?NULL, CipherState, <<>>, Fragment, _Version) -> GenStreamCipherList = [Fragment, <<>>], {GenStreamCipherList, CipherState}; -cipher(?RC4, CipherState, Mac, Fragment, _Version) -> - State0 = case CipherState#cipher_state.state of - undefined -> crypto:stream_init(rc4, CipherState#cipher_state.key); - S -> S - end, +cipher(?RC4, CipherState = #cipher_state{state = State0}, Mac, Fragment, _Version) -> GenStreamCipherList = [Fragment, Mac], {State1, T} = crypto:stream_encrypt(State0, GenStreamCipherList), {T, CipherState#cipher_state{state = State1}}; @@ -87,13 +133,40 @@ cipher(?'3DES', CipherState, Mac, Fragment, Version) -> block_cipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) -> crypto:block_encrypt(des3_cbc, [K1, K2, K3], IV, T) end, block_size(des_cbc), CipherState, Mac, Fragment, Version); -cipher(?AES, CipherState, Mac, Fragment, Version) -> +cipher(?AES_CBC, CipherState, Mac, Fragment, Version) -> block_cipher(fun(Key, IV, T) when byte_size(Key) =:= 16 -> crypto:block_encrypt(aes_cbc128, Key, IV, T); (Key, IV, T) when byte_size(Key) =:= 32 -> crypto:block_encrypt(aes_cbc256, Key, IV, T) end, block_size(aes_128_cbc), CipherState, Mac, Fragment, Version). +%%-------------------------------------------------------------------- +-spec cipher_aead(cipher_enum(), #cipher_state{}, integer(), binary(), iodata(), ssl_record:ssl_version()) -> + {binary(), #cipher_state{}}. +%% +%% Description: Encrypts the data and protects associated data (AAD) using chipher +%% described by cipher_enum() and updating the cipher state +%% Use for suites that use authenticated encryption with associated data (AEAD) +%%------------------------------------------------------------------- +cipher_aead(?AES_GCM, CipherState, SeqNo, AAD, Fragment, Version) -> + aead_cipher(aes_gcm, CipherState, SeqNo, AAD, Fragment, Version); +cipher_aead(?CHACHA20_POLY1305, CipherState, SeqNo, AAD, Fragment, Version) -> + aead_cipher(chacha20_poly1305, CipherState, SeqNo, AAD, Fragment, Version). + +aead_cipher(chacha20_poly1305, #cipher_state{key=Key} = CipherState, SeqNo, AAD0, Fragment, _Version) -> + CipherLen = erlang:iolist_size(Fragment), + AAD = <<AAD0/binary, ?UINT16(CipherLen)>>, + Nonce = <<SeqNo:64/integer>>, + {Content, CipherTag} = crypto:block_encrypt(chacha20_poly1305, Key, Nonce, {AAD, Fragment}), + {<<Content/binary, CipherTag/binary>>, CipherState}; +aead_cipher(Type, #cipher_state{key=Key, iv = IV0, nonce = Nonce} = CipherState, _SeqNo, AAD0, Fragment, _Version) -> + CipherLen = erlang:iolist_size(Fragment), + AAD = <<AAD0/binary, ?UINT16(CipherLen)>>, + <<Salt:4/bytes, _/binary>> = IV0, + IV = <<Salt/binary, Nonce:64/integer>>, + {Content, CipherTag} = crypto:block_encrypt(Type, Key, IV, {AAD, Fragment}), + {<<Nonce:64/integer, Content/binary, CipherTag/binary>>, CipherState#cipher_state{nonce = Nonce + 1}}. + build_cipher_block(BlockSz, Mac, Fragment) -> TotSz = byte_size(Mac) + erlang:iolist_size(Fragment) + 1, {PaddingLength, Padding} = get_padding(TotSz, BlockSz), @@ -117,19 +190,18 @@ block_cipher(Fun, BlockSz, #cipher_state{key=Key, iv=IV} = CS0, {T, CS0#cipher_state{iv=NextIV}}. %%-------------------------------------------------------------------- --spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(), tls_version()) -> +-spec decipher(cipher_enum(), integer(), #cipher_state{}, binary(), + ssl_record:ssl_version(), boolean()) -> {binary(), binary(), #cipher_state{}} | #alert{}. %% %% Description: Decrypts the data and the MAC using cipher described %% by cipher_enum() and updating the cipher state. +%% Used for "MAC then Cipher" suites where first the data is decrypted +%% and the an HMAC of the decrypted data is checked %%------------------------------------------------------------------- -decipher(?NULL, _HashSz, CipherState, Fragment, _) -> +decipher(?NULL, _HashSz, CipherState, Fragment, _, _) -> {Fragment, <<>>, CipherState}; -decipher(?RC4, HashSz, CipherState, Fragment, _) -> - State0 = case CipherState#cipher_state.state of - undefined -> crypto:stream_init(rc4, CipherState#cipher_state.key); - S -> S - end, +decipher(?RC4, HashSz, CipherState = #cipher_state{state = State0}, Fragment, _, _) -> try crypto:stream_decrypt(State0, Fragment) of {State, Text} -> GSC = generic_stream_cipher_from_bin(Text, HashSz), @@ -142,26 +214,39 @@ decipher(?RC4, HashSz, CipherState, Fragment, _) -> %% alerts may permit certain attacks against CBC mode as used in %% TLS [CBCATT]. It is preferable to uniformly use the %% bad_record_mac alert to hide the specific type of the error." - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end; -decipher(?DES, HashSz, CipherState, Fragment, Version) -> +decipher(?DES, HashSz, CipherState, Fragment, Version, PaddingCheck) -> block_decipher(fun(Key, IV, T) -> crypto:block_decrypt(des_cbc, Key, IV, T) - end, CipherState, HashSz, Fragment, Version); -decipher(?'3DES', HashSz, CipherState, Fragment, Version) -> + end, CipherState, HashSz, Fragment, Version, PaddingCheck); +decipher(?'3DES', HashSz, CipherState, Fragment, Version, PaddingCheck) -> block_decipher(fun(<<K1:8/binary, K2:8/binary, K3:8/binary>>, IV, T) -> crypto:block_decrypt(des3_cbc, [K1, K2, K3], IV, T) - end, CipherState, HashSz, Fragment, Version); -decipher(?AES, HashSz, CipherState, Fragment, Version) -> + end, CipherState, HashSz, Fragment, Version, PaddingCheck); +decipher(?AES_CBC, HashSz, CipherState, Fragment, Version, PaddingCheck) -> block_decipher(fun(Key, IV, T) when byte_size(Key) =:= 16 -> crypto:block_decrypt(aes_cbc128, Key, IV, T); (Key, IV, T) when byte_size(Key) =:= 32 -> crypto:block_decrypt(aes_cbc256, Key, IV, T) - end, CipherState, HashSz, Fragment, Version). + end, CipherState, HashSz, Fragment, Version, PaddingCheck). + +%%-------------------------------------------------------------------- +-spec decipher_aead(cipher_enum(), #cipher_state{}, integer(), binary(), binary(), ssl_record:ssl_version()) -> + {binary(), binary(), #cipher_state{}} | #alert{}. +%% +%% Description: Decrypts the data and checks the associated data (AAD) MAC using +%% cipher described by cipher_enum() and updating the cipher state. +%% Use for suites that use authenticated encryption with associated data (AEAD) +%%------------------------------------------------------------------- +decipher_aead(?AES_GCM, CipherState, SeqNo, AAD, Fragment, Version) -> + aead_decipher(aes_gcm, CipherState, SeqNo, AAD, Fragment, Version); +decipher_aead(?CHACHA20_POLY1305, CipherState, SeqNo, AAD, Fragment, Version) -> + aead_decipher(chacha20_poly1305, CipherState, SeqNo, AAD, Fragment, Version). block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, - HashSz, Fragment, Version) -> + HashSz, Fragment, Version, PaddingCheck) -> try Text = Fun(Key, IV, Fragment), NextIV = next_iv(Fragment, IV), @@ -169,7 +254,7 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, Content = GBC#generic_block_cipher.content, Mac = GBC#generic_block_cipher.mac, CipherState1 = CipherState0#cipher_state{iv=GBC#generic_block_cipher.next_iv}, - case is_correct_padding(GBC, Version) of + case is_correct_padding(GBC, Version, PaddingCheck) of true -> {Content, Mac, CipherState1}; false -> @@ -187,25 +272,71 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0, %% alerts may permit certain attacks against CBC mode as used in %% TLS [CBCATT]. It is preferable to uniformly use the %% bad_record_mac alert to hide the specific type of the error." - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) end. + +aead_ciphertext_to_state(chacha20_poly1305, SeqNo, _IV, AAD0, Fragment, _Version) -> + CipherLen = size(Fragment) - 16, + <<CipherText:CipherLen/bytes, CipherTag:16/bytes>> = Fragment, + AAD = <<AAD0/binary, ?UINT16(CipherLen)>>, + Nonce = <<SeqNo:64/integer>>, + {Nonce, AAD, CipherText, CipherTag}; +aead_ciphertext_to_state(_, _SeqNo, <<Salt:4/bytes, _/binary>>, AAD0, Fragment, _Version) -> + CipherLen = size(Fragment) - 24, + <<ExplicitNonce:8/bytes, CipherText:CipherLen/bytes, CipherTag:16/bytes>> = Fragment, + AAD = <<AAD0/binary, ?UINT16(CipherLen)>>, + Nonce = <<Salt/binary, ExplicitNonce/binary>>, + {Nonce, AAD, CipherText, CipherTag}. + +aead_decipher(Type, #cipher_state{key = Key, iv = IV} = CipherState, + SeqNo, AAD0, Fragment, Version) -> + try + {Nonce, AAD, CipherText, CipherTag} = aead_ciphertext_to_state(Type, SeqNo, IV, AAD0, Fragment, Version), + case crypto:block_decrypt(Type, Key, Nonce, {AAD, CipherText, CipherTag}) of + Content when is_binary(Content) -> + {Content, CipherState}; + _ -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) + end + catch + _:_ -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC, decryption_failed) + end. + %%-------------------------------------------------------------------- --spec suites(tls_version()) -> [cipher_suite()]. +-spec suites(ssl_record:ssl_version()) -> [cipher_suite()]. %% %% Description: Returns a list of supported cipher suites. %%-------------------------------------------------------------------- suites({3, 0}) -> - ssl_ssl3:suites(); + ssl_v3:suites(); suites({3, N}) -> - ssl_tls1:suites(N). - + tls_v1:suites(N). + +all_suites(Version) -> + suites(Version) + ++ anonymous_suites(Version) + ++ psk_suites(Version) + ++ srp_suites() + ++ rc4_suites(Version) + ++ des_suites(Version). %%-------------------------------------------------------------------- --spec anonymous_suites() -> [cipher_suite()]. +-spec anonymous_suites(ssl_record:ssl_version() | integer()) -> [cipher_suite()]. %% %% Description: Returns a list of the anonymous cipher suites, only supported %% if explicitly set by user. Intended only for testing. %%-------------------------------------------------------------------- -anonymous_suites() -> + +anonymous_suites({3, N}) -> + anonymous_suites(N); + +anonymous_suites(N) + when N >= 3 -> + [?TLS_DH_anon_WITH_AES_128_GCM_SHA256, + ?TLS_DH_anon_WITH_AES_256_GCM_SHA384 + ] ++ anonymous_suites(0); + +anonymous_suites(_) -> [?TLS_DH_anon_WITH_RC4_128_MD5, ?TLS_DH_anon_WITH_DES_CBC_SHA, ?TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, @@ -219,7 +350,7 @@ anonymous_suites() -> ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA]. %%-------------------------------------------------------------------- --spec psk_suites(tls_version() | integer()) -> [cipher_suite()]. +-spec psk_suites(ssl_record:ssl_version() | integer()) -> [cipher_suite()]. %% %% Description: Returns a list of the PSK cipher suites, only supported %% if explicitly set by user. @@ -229,13 +360,20 @@ psk_suites({3, N}) -> psk_suites(N) when N >= 3 -> - psk_suites(0) ++ - [?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_PSK_WITH_AES_256_CBC_SHA384, - ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, - ?TLS_PSK_WITH_AES_128_CBC_SHA256]; + [ + ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, + ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, + ?TLS_PSK_WITH_AES_256_GCM_SHA384, + ?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384, + ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, + ?TLS_PSK_WITH_AES_256_CBC_SHA384, + ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256, + ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, + ?TLS_PSK_WITH_AES_128_CBC_SHA256 + ] ++ psk_suites(0); psk_suites(_) -> [?TLS_DHE_PSK_WITH_AES_256_CBC_SHA, @@ -267,9 +405,37 @@ srp_suites() -> ?TLS_SRP_SHA_WITH_AES_256_CBC_SHA, ?TLS_SRP_SHA_RSA_WITH_AES_256_CBC_SHA, ?TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA]. +%%-------------------------------------------------------------------- +-spec rc4_suites(Version::ssl_record:ssl_version()) -> [cipher_suite()]. +%% +%% Description: Returns a list of the RSA|(ECDH/RSA)| (ECDH/ECDSA) +%% with RC4 cipher suites, only supported if explicitly set by user. +%% Are not considered secure any more. Other RC4 suites already +%% belonged to the user configured only category. +%%-------------------------------------------------------------------- +rc4_suites({3, 0}) -> + [?TLS_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_MD5]; +rc4_suites({3, N}) when N =< 3 -> + [?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, + ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_SHA, + ?TLS_RSA_WITH_RC4_128_MD5, + ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, + ?TLS_ECDH_RSA_WITH_RC4_128_SHA]. +%%-------------------------------------------------------------------- +-spec des_suites(Version::ssl_record:ssl_version()) -> [cipher_suite()]. +%% +%% Description: Returns a list of the cipher suites +%% with DES cipher, only supported if explicitly set by user. +%% Are not considered secure any more. +%%-------------------------------------------------------------------- +des_suites(_)-> + [?TLS_DHE_RSA_WITH_DES_CBC_SHA, + ?TLS_RSA_WITH_DES_CBC_SHA]. %%-------------------------------------------------------------------- --spec suite_definition(cipher_suite()) -> int_cipher_suite(). +-spec suite_definition(cipher_suite()) -> erl_cipher_suite(). %% %% Description: Return erlang cipher suite definition. %% Note: Currently not supported suites are commented away. @@ -387,6 +553,19 @@ suite_definition(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA) -> %%% TLS 1.2 PSK Cipher Suites RFC 5487 +suite_definition(?TLS_PSK_WITH_AES_128_GCM_SHA256) -> + {psk, aes_128_gcm, null, sha256}; +suite_definition(?TLS_PSK_WITH_AES_256_GCM_SHA384) -> + {psk, aes_256_gcm, null, sha384}; +suite_definition(?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256) -> + {dhe_psk, aes_128_gcm, null, sha256}; +suite_definition(?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384) -> + {dhe_psk, aes_256_gcm, null, sha384}; +suite_definition(?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256) -> + {rsa_psk, aes_128_gcm, null, sha256}; +suite_definition(?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384) -> + {rsa_psk, aes_256_gcm, null, sha384}; + suite_definition(?TLS_PSK_WITH_AES_128_CBC_SHA256) -> {psk, aes_128_cbc, sha256, default_prf}; suite_definition(?TLS_PSK_WITH_AES_256_CBC_SHA384) -> @@ -506,7 +685,73 @@ suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384) -> suite_definition(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) -> {ecdh_rsa, aes_128_cbc, sha256, sha256}; suite_definition(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) -> - {ecdh_rsa, aes_256_cbc, sha384, sha384}. + {ecdh_rsa, aes_256_cbc, sha384, sha384}; + +%% RFC 5288 AES-GCM Cipher Suites +suite_definition(?TLS_RSA_WITH_AES_128_GCM_SHA256) -> + {rsa, aes_128_gcm, null, sha256}; +suite_definition(?TLS_RSA_WITH_AES_256_GCM_SHA384) -> + {rsa, aes_256_gcm, null, sha384}; +suite_definition(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) -> + {dhe_rsa, aes_128_gcm, null, sha256}; +suite_definition(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) -> + {dhe_rsa, aes_256_gcm, null, sha384}; +suite_definition(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) -> + {dh_rsa, aes_128_gcm, null, sha256}; +suite_definition(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) -> + {dh_rsa, aes_256_gcm, null, sha384}; +suite_definition(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) -> + {dhe_dss, aes_128_gcm, null, sha256}; +suite_definition(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) -> + {dhe_dss, aes_256_gcm, null, sha384}; +suite_definition(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) -> + {dh_dss, aes_128_gcm, null, sha256}; +suite_definition(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) -> + {dh_dss, aes_256_gcm, null, sha384}; +suite_definition(?TLS_DH_anon_WITH_AES_128_GCM_SHA256) -> + {dh_anon, aes_128_gcm, null, sha256}; +suite_definition(?TLS_DH_anon_WITH_AES_256_GCM_SHA384) -> + {dh_anon, aes_256_gcm, null, sha384}; + +%% RFC 5289 ECC AES-GCM Cipher Suites +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) -> + {ecdhe_ecdsa, aes_128_gcm, null, sha256}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) -> + {ecdhe_ecdsa, aes_256_gcm, null, sha384}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) -> + {ecdh_ecdsa, aes_128_gcm, null, sha256}; +suite_definition(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) -> + {ecdh_ecdsa, aes_256_gcm, null, sha384}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) -> + {ecdhe_rsa, aes_128_gcm, null, sha256}; +suite_definition(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) -> + {ecdhe_rsa, aes_256_gcm, null, sha384}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) -> + {ecdh_rsa, aes_128_gcm, null, sha256}; +suite_definition(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) -> + {ecdh_rsa, aes_256_gcm, null, sha384}; + +%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites +suite_definition(?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256) -> + {ecdhe_rsa, chacha20_poly1305, null, sha256}; +suite_definition(?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256) -> + {ecdhe_ecdsa, chacha20_poly1305, null, sha256}; +suite_definition(?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256) -> + {dhe_rsa, chacha20_poly1305, null, sha256}. + +%%-------------------------------------------------------------------- +-spec erl_suite_definition(cipher_suite()) -> erl_cipher_suite(). +%% +%% Description: Return erlang cipher suite definition. Filters last value +%% for now (compatibility reasons). +%%-------------------------------------------------------------------- +erl_suite_definition(S) -> + case suite_definition(S) of + {KeyExchange, Cipher, Hash, default_prf} -> + {KeyExchange, Cipher, Hash}; + Suite -> + Suite + end. %%-------------------------------------------------------------------- -spec suite(erl_cipher_suite()) -> cipher_suite(). @@ -610,6 +855,19 @@ suite({rsa_psk, aes_256_cbc,sha}) -> %%% TLS 1.2 PSK Cipher Suites RFC 5487 +suite({psk, aes_128_gcm, null, sha256}) -> + ?TLS_PSK_WITH_AES_128_GCM_SHA256; +suite({psk, aes_256_gcm, null, sha384}) -> + ?TLS_PSK_WITH_AES_256_GCM_SHA384; +suite({dhe_psk, aes_128_gcm, null, sha256}) -> + ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256; +suite({dhe_psk, aes_256_gcm, null, sha384}) -> + ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384; +suite({rsa_psk, aes_128_gcm, null, sha256}) -> + ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256; +suite({rsa_psk, aes_256_gcm, null, sha384}) -> + ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384; + suite({psk, aes_128_cbc, sha256}) -> ?TLS_PSK_WITH_AES_128_CBC_SHA256; suite({psk, aes_256_cbc, sha384}) -> @@ -714,22 +972,75 @@ suite({ecdh_anon, aes_256_cbc, sha}) -> ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA; %%% RFC 5289 EC TLS suites -suite({ecdhe_ecdsa, aes_128_cbc, sha256}) -> +suite({ecdhe_ecdsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_ecdsa, aes_256_cbc, sha384}) -> +suite({ecdhe_ecdsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_ecdsa, aes_128_cbc, sha256}) -> +suite({ecdh_ecdsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_ecdsa, aes_256_cbc, sha384}) -> +suite({ecdh_ecdsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdhe_rsa, aes_128_cbc, sha256}) -> +suite({ecdhe_rsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_rsa, aes_256_cbc, sha384}) -> +suite({ecdhe_rsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_rsa, aes_128_cbc, sha256}) -> +suite({ecdh_rsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_rsa, aes_256_cbc, sha384}) -> - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384. +suite({ecdh_rsa, aes_256_cbc, sha384, sha384}) -> + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384; + +%% RFC 5288 AES-GCM Cipher Suites +suite({rsa, aes_128_gcm, null, sha256}) -> + ?TLS_RSA_WITH_AES_128_GCM_SHA256; +suite({rsa, aes_256_gcm, null, sha384}) -> + ?TLS_RSA_WITH_AES_256_GCM_SHA384; +suite({dhe_rsa, aes_128_gcm, null, sha256}) -> + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256; +suite({dhe_rsa, aes_256_gcm, null, sha384}) -> + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384; +suite({dh_rsa, aes_128_gcm, null, sha256}) -> + ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256; +suite({dh_rsa, aes_256_gcm, null, sha384}) -> + ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384; +suite({dhe_dss, aes_128_gcm, null, sha256}) -> + ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256; +suite({dhe_dss, aes_256_gcm, null, sha384}) -> + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384; +suite({dh_dss, aes_128_gcm, null, sha256}) -> + ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256; +suite({dh_dss, aes_256_gcm, null, sha384}) -> + ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384; +suite({dh_anon, aes_128_gcm, null, sha256}) -> + ?TLS_DH_anon_WITH_AES_128_GCM_SHA256; +suite({dh_anon, aes_256_gcm, null, sha384}) -> + ?TLS_DH_anon_WITH_AES_256_GCM_SHA384; + +%% RFC 5289 ECC AES-GCM Cipher Suites +suite({ecdhe_ecdsa, aes_128_gcm, null, sha256}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; +suite({ecdhe_ecdsa, aes_256_gcm, null, sha384}) -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; +suite({ecdh_ecdsa, aes_128_gcm, null, sha256}) -> + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256; +suite({ecdh_ecdsa, aes_256_gcm, null, sha384}) -> + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384; +suite({ecdhe_rsa, aes_128_gcm, null, sha256}) -> + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256; +suite({ecdhe_rsa, aes_256_gcm, null, sha384}) -> + ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; +suite({ecdh_rsa, aes_128_gcm, null, sha256}) -> + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256; +suite({ecdh_rsa, aes_256_gcm, null, sha384}) -> + ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384; + + +%% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites +suite({ecdhe_rsa, chacha20_poly1305, null, sha256}) -> + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256; +suite({ecdhe_ecdsa, chacha20_poly1305, null, sha256}) -> + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256; +suite({dhe_rsa, chacha20_poly1305, null, sha256}) -> + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256. %%-------------------------------------------------------------------- -spec openssl_suite(openssl_cipher_suite()) -> cipher_suite(). @@ -844,7 +1155,47 @@ openssl_suite("ECDHE-RSA-AES256-SHA384") -> openssl_suite("ECDH-RSA-AES128-SHA256") -> ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; openssl_suite("ECDH-RSA-AES256-SHA384") -> - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384. + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384; + +%% RFC 5288 AES-GCM Cipher Suites +openssl_suite("AES128-GCM-SHA256") -> + ?TLS_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("AES256-GCM-SHA384") -> + ?TLS_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("DHE-RSA-AES128-GCM-SHA256") -> + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("DHE-RSA-AES256-GCM-SHA384") -> + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("DH-RSA-AES128-GCM-SHA256") -> + ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("DH-RSA-AES256-GCM-SHA384") -> + ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("DHE-DSS-AES128-GCM-SHA256") -> + ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256; +openssl_suite("DHE-DSS-AES256-GCM-SHA384") -> + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384; +openssl_suite("DH-DSS-AES128-GCM-SHA256") -> + ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256; +openssl_suite("DH-DSS-AES256-GCM-SHA384") -> + ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384; + +%% RFC 5289 ECC AES-GCM Cipher Suites +openssl_suite("ECDHE-ECDSA-AES128-GCM-SHA256") -> + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDHE-ECDSA-AES256-GCM-SHA384") -> + ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; +openssl_suite("ECDH-ECDSA-AES128-GCM-SHA256") -> + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDH-ECDSA-AES256-GCM-SHA384") -> + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384; +openssl_suite("ECDHE-RSA-AES128-GCM-SHA256") -> + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDHE-RSA-AES256-GCM-SHA384") -> + ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; +openssl_suite("ECDH-RSA-AES128-GCM-SHA256") -> + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256; +openssl_suite("ECDH-RSA-AES256-GCM-SHA384") -> + ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384. %%-------------------------------------------------------------------- -spec openssl_suite_name(cipher_suite()) -> openssl_cipher_suite(). @@ -981,6 +1332,46 @@ openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256) -> openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384) -> "ECDH-RSA-AES256-SHA384"; +%% RFC 5288 AES-GCM Cipher Suites +openssl_suite_name(?TLS_RSA_WITH_AES_128_GCM_SHA256) -> + "AES128-GCM-SHA256"; +openssl_suite_name(?TLS_RSA_WITH_AES_256_GCM_SHA384) -> + "AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) -> + "DHE-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384) -> + "DHE-RSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DH_RSA_WITH_AES_128_GCM_SHA256) -> + "DH-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DH_RSA_WITH_AES_256_GCM_SHA384) -> + "DH-RSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256) -> + "DHE-DSS-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384) -> + "DHE-DSS-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_DH_DSS_WITH_AES_128_GCM_SHA256) -> + "DH-DSS-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_DH_DSS_WITH_AES_256_GCM_SHA384) -> + "DH-DSS-AES256-GCM-SHA384"; + +%% RFC 5289 ECC AES-GCM Cipher Suites +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256) -> + "ECDHE-ECDSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384) -> + "ECDHE-ECDSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256) -> + "ECDH-ECDSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384) -> + "ECDH-ECDSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256) -> + "ECDHE-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) -> + "ECDHE-RSA-AES256-GCM-SHA384"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256) -> + "ECDH-RSA-AES128-GCM-SHA256"; +openssl_suite_name(?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384) -> + "ECDH-RSA-AES256-GCM-SHA384"; + %% No oppenssl name openssl_suite_name(Cipher) -> suite_definition(Cipher). @@ -988,7 +1379,8 @@ openssl_suite_name(Cipher) -> %%-------------------------------------------------------------------- -spec filter(undefined | binary(), [cipher_suite()]) -> [cipher_suite()]. %% -%% Description: . +%% Description: Select the cipher suites that can be used together with the +%% supplied certificate. (Server side functionality) %%------------------------------------------------------------------- filter(undefined, Ciphers) -> Ciphers; @@ -1009,6 +1401,7 @@ filter(DerCert, Ciphers) -> filter_keyuse(OtpCert, (Ciphers -- rsa_keyed_suites()) -- dsa_signed_suites(), [], ecdhe_ecdsa_suites()) end, + case public_key:pkix_sign_types(SigAlg#'SignatureAlgorithm'.algorithm) of {_, rsa} -> Ciphers1 -- ecdsa_signed_suites(); @@ -1021,20 +1414,16 @@ filter(DerCert, Ciphers) -> %%-------------------------------------------------------------------- -spec filter_suites([cipher_suite()]) -> [cipher_suite()]. %% -%% Description: filter suites for algorithms +%% Description: Filter suites for algorithms supported by crypto. %%------------------------------------------------------------------- -filter_suites(Suites = [{_,_,_}|_]) -> +filter_suites(Suites = [Value|_]) when is_tuple(Value) -> Algos = crypto:supports(), + Hashs = proplists:get_value(hashs, Algos), lists:filter(fun({KeyExchange, Cipher, Hash}) -> is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso - is_acceptable_hash(Hash, proplists:get_value(hashs, Algos)) - end, Suites); - -filter_suites(Suites = [{_,_,_,_}|_]) -> - Algos = crypto:supports(), - Hashs = proplists:get_value(hashs, Algos), - lists:filter(fun({KeyExchange, Cipher, Hash, Prf}) -> + is_acceptable_hash(Hash, proplists:get_value(hashs, Algos)); + ({KeyExchange, Cipher, Hash, Prf}) -> is_acceptable_keyexchange(KeyExchange, proplists:get_value(public_keys, Algos)) andalso is_acceptable_cipher(Cipher, proplists:get_value(ciphers, Algos)) andalso is_acceptable_hash(Hash, Hashs) andalso @@ -1062,6 +1451,13 @@ is_acceptable_keyexchange(KeyExchange, Algos) is_acceptable_keyexchange(_, _) -> true. +is_acceptable_cipher(Cipher, Algos) + when Cipher == aes_128_gcm; + Cipher == aes_256_gcm -> + proplists:get_bool(aes_gcm, Algos); +is_acceptable_cipher(Cipher, Algos) + when Cipher == chacha20_poly1305 -> + proplists:get_bool(Cipher, Algos); is_acceptable_cipher(_, _) -> true. @@ -1075,6 +1471,19 @@ is_acceptable_prf(default_prf, _) -> is_acceptable_prf(Prf, Algos) -> proplists:get_bool(Prf, Algos). +is_fallback(CipherSuites)-> + lists:member(?TLS_FALLBACK_SCSV, CipherSuites). + + +%%-------------------------------------------------------------------- +-spec random_bytes(integer()) -> binary(). + +%% +%% Description: Generates cryptographically secure random sequence +%%-------------------------------------------------------------------- +random_bytes(N) -> + crypto:strong_rand_bytes(N). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -1089,7 +1498,12 @@ bulk_cipher_algorithm('3des_ede_cbc') -> ?'3DES'; bulk_cipher_algorithm(Cipher) when Cipher == aes_128_cbc; Cipher == aes_256_cbc -> - ?AES. + ?AES_CBC; +bulk_cipher_algorithm(Cipher) when Cipher == aes_128_gcm; + Cipher == aes_256_gcm -> + ?AES_GCM; +bulk_cipher_algorithm(chacha20_poly1305) -> + ?CHACHA20_POLY1305. type(Cipher) when Cipher == null; Cipher == rc4_128 -> @@ -1099,7 +1513,11 @@ type(Cipher) when Cipher == des_cbc; Cipher == '3des_ede_cbc'; Cipher == aes_128_cbc; Cipher == aes_256_cbc -> - ?BLOCK. + ?BLOCK; +type(Cipher) when Cipher == aes_128_gcm; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> + ?AEAD. key_material(null) -> 0; @@ -1112,6 +1530,12 @@ key_material('3des_ede_cbc') -> key_material(aes_128_cbc) -> 16; key_material(aes_256_cbc) -> + 32; +key_material(aes_128_gcm) -> + 16; +key_material(aes_256_gcm) -> + 32; +key_material(chacha20_poly1305) -> 32. expanded_key_material(null) -> @@ -1123,7 +1547,10 @@ expanded_key_material(Cipher) when Cipher == des_cbc -> expanded_key_material('3des_ede_cbc') -> 24; expanded_key_material(Cipher) when Cipher == aes_128_cbc; - Cipher == aes_256_cbc -> + Cipher == aes_256_cbc; + Cipher == aes_128_gcm; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> unknown. @@ -1132,16 +1559,25 @@ effective_key_bits(null) -> effective_key_bits(des_cbc) -> 56; effective_key_bits(Cipher) when Cipher == rc4_128; - Cipher == aes_128_cbc -> + Cipher == aes_128_cbc; + Cipher == aes_128_gcm -> 128; effective_key_bits('3des_ede_cbc') -> 168; -effective_key_bits(aes_256_cbc) -> +effective_key_bits(Cipher) when Cipher == aes_256_cbc; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> 256. iv_size(Cipher) when Cipher == null; - Cipher == rc4_128 -> + Cipher == rc4_128; + Cipher == chacha20_poly1305-> 0; + +iv_size(Cipher) when Cipher == aes_128_gcm; + Cipher == aes_256_gcm -> + 4; + iv_size(Cipher) -> block_size(Cipher). @@ -1150,7 +1586,10 @@ block_size(Cipher) when Cipher == des_cbc; 8; block_size(Cipher) when Cipher == aes_128_cbc; - Cipher == aes_256_cbc -> + Cipher == aes_256_cbc; + Cipher == aes_128_gcm; + Cipher == aes_256_gcm; + Cipher == chacha20_poly1305 -> 16. prf_algorithm(default_prf, {3, N}) when N >= 3 -> @@ -1173,7 +1612,9 @@ hash_algorithm(?SHA) -> sha; hash_algorithm(?SHA224) -> sha224; hash_algorithm(?SHA256) -> sha256; hash_algorithm(?SHA384) -> sha384; -hash_algorithm(?SHA512) -> sha512. +hash_algorithm(?SHA512) -> sha512; +hash_algorithm(Other) when is_integer(Other) andalso ((Other >= 7) and (Other =< 223)) -> unassigned; +hash_algorithm(Other) when is_integer(Other) andalso ((Other >= 224) and (Other =< 255)) -> Other. sign_algorithm(anon) -> ?ANON; sign_algorithm(rsa) -> ?RSA; @@ -1182,7 +1623,9 @@ sign_algorithm(ecdsa) -> ?ECDSA; sign_algorithm(?ANON) -> anon; sign_algorithm(?RSA) -> rsa; sign_algorithm(?DSA) -> dsa; -sign_algorithm(?ECDSA) -> ecdsa. +sign_algorithm(?ECDSA) -> ecdsa; +sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 4) and (Other =< 223)) -> unassigned; +sign_algorithm(Other) when is_integer(Other) andalso ((Other >= 224) and (Other =< 255)) -> Other. hash_size(null) -> 0; @@ -1191,15 +1634,15 @@ hash_size(md5) -> hash_size(sha) -> 20; %% Uncomment when adding cipher suite that needs it -%% hash_size(sha224) -> -%% 28; +%hash_size(sha224) -> +% 28; hash_size(sha256) -> 32; hash_size(sha384) -> 48. %% Uncomment when adding cipher suite that needs it -%% hash_size(sha512) -> -%% 64. +%hash_size(sha512) -> +% 64. %% RFC 5246: 6.2.3.2. CBC Block Cipher %% @@ -1255,19 +1698,21 @@ generic_stream_cipher_from_bin(T, HashSz) -> #generic_stream_cipher{content=Content, mac=Mac}. -%% For interoperability reasons we do not check the padding content in -%% SSL 3.0 and TLS 1.0 as it is not strictly required and breaks -%% interopability with for instance Google. is_correct_padding(#generic_block_cipher{padding_length = Len, - padding = Padding}, {3, N}) - when N == 0; N == 1 -> - Len == byte_size(Padding); -%% Padding must be check in TLS 1.1 and after + padding = Padding}, {3, 0}, _) -> + Len == byte_size(Padding); %% Only length check is done in SSL 3.0 spec +%% For interoperability reasons it is possible to disable +%% the padding check when using TLS 1.0, as it is not strictly required +%% in the spec (only recommended), howerver this makes TLS 1.0 vunrable to the Poodle attack +%% so by default this clause will not match +is_correct_padding(GenBlockCipher, {3, 1}, false) -> + is_correct_padding(GenBlockCipher, {3, 0}, false); +%% Padding must be checked in TLS 1.1 and after is_correct_padding(#generic_block_cipher{padding_length = Len, - padding = Padding}, _) -> + padding = Padding}, _, _) -> Len == byte_size(Padding) andalso list_to_binary(lists:duplicate(Len, Len)) == Padding. - + get_padding(Length, BlockSize) -> get_padding_aux(BlockSize, Length rem BlockSize). @@ -1279,7 +1724,7 @@ get_padding_aux(BlockSize, PadLength) -> random_iv(IV) -> IVSz = byte_size(IV), - ssl:random_bytes(IVSz). + random_bytes(IVSz). next_iv(Bin, IV) -> BinSz = byte_size(Bin), @@ -1291,7 +1736,7 @@ next_iv(Bin, IV) -> rsa_signed_suites() -> dhe_rsa_suites() ++ rsa_suites() ++ psk_rsa_suites() ++ srp_rsa_suites() ++ - ecdh_rsa_suites(). + ecdh_rsa_suites() ++ ecdhe_rsa_suites(). rsa_keyed_suites() -> dhe_rsa_suites() ++ rsa_suites() ++ @@ -1304,10 +1749,16 @@ dhe_rsa_suites() -> ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA]. + ?TLS_DHE_RSA_WITH_DES_CBC_SHA, + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 + ]. psk_rsa_suites() -> - [?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, + [?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, + ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, + ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384, ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256, ?TLS_RSA_PSK_WITH_AES_256_CBC_SHA, ?TLS_RSA_PSK_WITH_AES_128_CBC_SHA, @@ -1327,7 +1778,9 @@ rsa_suites() -> ?TLS_RSA_WITH_AES_128_CBC_SHA, ?TLS_RSA_WITH_RC4_128_SHA, ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_RSA_WITH_DES_CBC_SHA]. + ?TLS_RSA_WITH_DES_CBC_SHA, + ?TLS_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_RSA_WITH_AES_256_GCM_SHA384]. ecdh_rsa_suites() -> [?TLS_ECDH_RSA_WITH_NULL_SHA, @@ -1336,7 +1789,9 @@ ecdh_rsa_suites() -> ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384]. + ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384]. ecdhe_rsa_suites() -> [?TLS_ECDHE_RSA_WITH_NULL_SHA, @@ -1345,7 +1800,10 @@ ecdhe_rsa_suites() -> ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384]. + ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256]. dsa_signed_suites() -> dhe_dss_suites() ++ srp_dss_suites(). @@ -1356,7 +1814,9 @@ dhe_dss_suites() -> ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA]. + ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, + ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384]. srp_dss_suites() -> [?TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA, @@ -1380,7 +1840,9 @@ ecdh_ecdsa_suites() -> ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384]. + ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384]. ecdhe_ecdsa_suites() -> [?TLS_ECDHE_ECDSA_WITH_NULL_SHA, @@ -1389,7 +1851,10 @@ ecdhe_ecdsa_suites() -> ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, - ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384]. + ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256]. filter_keyuse(OtpCert, Ciphers, Suites, SignSuites) -> TBSCert = OtpCert#'OTPCertificate'.tbsCertificate, diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl index c7c71ee1a7..8e8f3d9c67 100644 --- a/lib/ssl/src/ssl_cipher.hrl +++ b/lib/ssl/src/ssl_cipher.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -26,15 +27,6 @@ -ifndef(ssl_cipher). -define(ssl_cipher, true). --type cipher() :: null |rc4_128 | idea_cbc | des40_cbc | des_cbc | '3des_ede_cbc' - | aes_128_cbc | aes_256_cbc. --type hash() :: null | sha | md5 | ssh224 | sha256 | sha384 | sha512. --type erl_cipher_suite() :: {key_algo(), cipher(), hash()}. --type int_cipher_suite() :: {key_algo(), cipher(), hash(), hash() | default_prf}. --type cipher_suite() :: binary(). --type cipher_enum() :: integer(). --type openssl_cipher_suite() :: string(). - %%% SSL cipher protocol %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -define(CHANGE_CIPHER_SPEC_PROTO, 1). % _PROTO to not clash with % SSL record protocol @@ -55,7 +47,8 @@ -record(cipher_state, { iv, key, - state + state, + nonce }). %%% TLS_NULL_WITH_NULL_NULL is specified and is the initial state of a @@ -364,6 +357,10 @@ %% hello extension data as they should. -define(TLS_EMPTY_RENEGOTIATION_INFO_SCSV, <<?BYTE(16#00), ?BYTE(16#FF)>>). +%% TLS Fallback Signaling Cipher Suite Value (SCSV) for Preventing Protocol +%% Downgrade Attacks +-define(TLS_FALLBACK_SCSV, <<?BYTE(16#56), ?BYTE(16#00)>>). + %%% PSK Cipher Suites RFC 4279 %% TLS_PSK_WITH_RC4_128_SHA = { 0x00, 0x8A }; @@ -404,6 +401,24 @@ %%% TLS 1.2 PSK Cipher Suites RFC 5487 +%% TLS_PSK_WITH_AES_128_GCM_SHA256 = {0x00,0xA8}; +-define(TLS_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A8)>>). + +%% TLS_PSK_WITH_AES_256_GCM_SHA384 = {0x00,0xA9}; +-define(TLS_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A9)>>). + +%% TLS_DHE_PSK_WITH_AES_128_GCM_SHA256 = {0x00,0xAA}; +-define(TLS_DHE_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#AA)>>). + +%% TLS_DHE_PSK_WITH_AES_256_GCM_SHA384 = {0x00,0xAB}; +-define(TLS_DHE_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#AB)>>). + +%% TLS_RSA_PSK_WITH_AES_128_GCM_SHA256 = {0x00,0xAC}; +-define(TLS_RSA_PSK_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#AC)>>). + +%% TLS_RSA_PSK_WITH_AES_256_GCM_SHA384 = {0x00,0xAD}; +-define(TLS_RSA_PSK_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#AD)>>). + %% TLS_PSK_WITH_AES_128_CBC_SHA256 = {0x00,0xAE}; -define(TLS_PSK_WITH_AES_128_CBC_SHA256, <<?BYTE(16#00), ?BYTE(16#AE)>>). @@ -469,4 +484,79 @@ %% TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA = { 0xC0,0x22 }; -define(TLS_SRP_SHA_DSS_WITH_AES_256_CBC_SHA, <<?BYTE(16#C0), ?BYTE(16#22)>>). +%%% AES-GCM Cipher Suites RFC 5288 + +%% TLS_RSA_WITH_AES_128_GCM_SHA256 = {0x00,0x9C} +-define(TLS_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#9C)>>). + +%% TLS_RSA_WITH_AES_256_GCM_SHA384 = {0x00,0x9D} +-define(TLS_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#9D)>>). + +%% TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = {0x00,0x9E} +-define(TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#9E)>>). + +%% TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = {0x00,0x9F} +-define(TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#9F)>>). + +%% TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = {0x00,0xA0} +-define(TLS_DH_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A0)>>). + +%% TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = {0x00,0xA1} +-define(TLS_DH_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A1)>>). + +%% TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = {0x00,0xA2} +-define(TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A2)>>). + +%% TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = {0x00,0xA3} +-define(TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A3)>>). + +%% TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = {0x00,0xA4} +-define(TLS_DH_DSS_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A4)>>). + +%% TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = {0x00,0xA5} +-define(TLS_DH_DSS_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A5)>>). + +%% TLS_DH_anon_WITH_AES_128_GCM_SHA256 = {0x00,0xA6} +-define(TLS_DH_anon_WITH_AES_128_GCM_SHA256, <<?BYTE(16#00), ?BYTE(16#A6)>>). + +%% TLS_DH_anon_WITH_AES_256_GCM_SHA384 = {0x00,0xA7} +-define(TLS_DH_anon_WITH_AES_256_GCM_SHA384, <<?BYTE(16#00), ?BYTE(16#A7)>>). + +%%% ECC AES-GCM Cipher Suites RFC 5289 + +%% TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x2B}; +-define(TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#2B)>>). + +%% TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x2C}; +-define(TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#2C)>>). + +%% TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x2D}; +-define(TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#2D)>>). + +%% TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x2E}; +-define(TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#2E)>>). + +%% TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x2F}; +-define(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#2F)>>). + +%% TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x30}; +-define(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#30)>>). + +%% TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = {0xC0,0x31}; +-define(TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, <<?BYTE(16#C0), ?BYTE(16#31)>>). + +%% TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = {0xC0,0x32}; +-define(TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, <<?BYTE(16#C0), ?BYTE(16#32)>>). + +%%% Chacha20/Poly1305 Suites draft-agl-tls-chacha20poly1305-04 + +%% TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x13} +-define(TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#13)>>). + +%% TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x14} +-define(TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#14)>>). + +%% TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 = {0xcc, 0x15} +-define(TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, <<?BYTE(16#CC), ?BYTE(16#15)>>). + -endif. % -ifdef(ssl_cipher). diff --git a/lib/ssl/src/ssl_config.erl b/lib/ssl/src/ssl_config.erl new file mode 100644 index 0000000000..0652d029c3 --- /dev/null +++ b/lib/ssl/src/ssl_config.erl @@ -0,0 +1,160 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_config). + +-include("ssl_internal.hrl"). +-include("ssl_connection.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-export([init/2]). + +init(SslOpts, Role) -> + + init_manager_name(SslOpts#ssl_options.erl_dist), + + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CRLDbHandle, OwnCert} + = init_certificates(SslOpts, Role), + PrivateKey = + init_private_key(PemCacheHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, + SslOpts#ssl_options.password, Role), + DHParams = init_diffie_hellman(PemCacheHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), + {ok, CertDbRef, CertDbHandle, FileRefHandle, CacheHandle, CRLDbHandle, OwnCert, PrivateKey, DHParams}. + +init_manager_name(false) -> + put(ssl_manager, ssl_manager:manager_name(normal)); +init_manager_name(true) -> + put(ssl_manager, ssl_manager:manager_name(dist)). + +init_certificates(#ssl_options{cacerts = CaCerts, + cacertfile = CACertFile, + certfile = CertFile, + cert = Cert, + crl_cache = CRLCache + }, Role) -> + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CRLDbInfo} = + try + Certs = case CaCerts of + undefined -> + CACertFile; + _ -> + {der, CaCerts} + end, + {ok, _, _, _, _, _, _} = ssl_manager:connection_init(Certs, Role, CRLCache) + catch + _:Reason -> + file_error(CACertFile, {cacertfile, Reason}) + end, + init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, + CacheHandle, CRLDbInfo, CertFile, Role). + +init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, + CRLDbInfo, <<>>, _) -> + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CRLDbInfo, undefined}; + +init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, + CacheHandle, CRLDbInfo, CertFile, client) -> + try + %% Ignoring potential proxy-certificates see: + %% http://dev.globus.org/wiki/Security/ProxyFileFormat + [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CRLDbInfo, OwnCert} + catch _Error:_Reason -> + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CRLDbInfo, undefined} + end; + +init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, + PemCacheHandle, CacheRef, CRLDbInfo, CertFile, server) -> + try + [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CRLDbInfo, OwnCert} + catch + _:Reason -> + file_error(CertFile, {certfile, Reason}) + end; +init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CRLDbInfo, _, _) -> + {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CRLDbInfo, Cert}. + +init_private_key(_, undefined, <<>>, _Password, _Client) -> + undefined; +init_private_key(DbHandle, undefined, KeyFile, Password, _) -> + try + {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle), + [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List, + PKey =:= 'RSAPrivateKey' orelse + PKey =:= 'DSAPrivateKey' orelse + PKey =:= 'ECPrivateKey' orelse + PKey =:= 'PrivateKeyInfo' + ], + private_key(public_key:pem_entry_decode(PemEntry, Password)) + catch + _:Reason -> + file_error(KeyFile, {keyfile, Reason}) + end; + +init_private_key(_,{Asn1Type, PrivateKey},_,_,_) -> + private_key(init_private_key(Asn1Type, PrivateKey)). + +init_private_key(Asn1Type, PrivateKey) -> + public_key:der_decode(Asn1Type, PrivateKey). + +private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'}, + privateKey = Key}) -> + public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key)); + +private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = + #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, + privateKey = Key}) -> + public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); + +private_key(Key) -> + Key. + +-spec(file_error(_,_) -> no_return()). +file_error(File, Throw) -> + case Throw of + {Opt,{badmatch, {error, {badmatch, Error}}}} -> + throw({options, {Opt, binary_to_list(File), Error}}); + _ -> + throw(Throw) + end. + +init_diffie_hellman(_,Params, _,_) when is_binary(Params)-> + public_key:der_decode('DHParameter', Params); +init_diffie_hellman(_,_,_, client) -> + undefined; +init_diffie_hellman(_,_,undefined, _) -> + ?DEFAULT_DIFFIE_HELLMAN_PARAMS; +init_diffie_hellman(DbHandle,_, DHParamFile, server) -> + try + {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle), + case [Entry || Entry = {'DHParameter', _ , _} <- List] of + [Entry] -> + public_key:pem_entry_decode(Entry); + [] -> + ?DEFAULT_DIFFIE_HELLMAN_PARAMS + end + catch + _:Reason -> + file_error(DHParamFile, {dhfile, Reason}) + end. diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl new file mode 100644 index 0000000000..53282998d0 --- /dev/null +++ b/lib/ssl/src/ssl_connection.erl @@ -0,0 +1,2059 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Common handling of a TLS/SSL/DTLS connection, see also +%% tls_connection.erl and dtls_connection.erl +%%---------------------------------------------------------------------- + +-module(ssl_connection). + +-include("ssl_api.hrl"). +-include("ssl_connection.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%% Setup +-export([connect/8, ssl_accept/7, handshake/2, handshake/3, + socket_control/4, socket_control/5, start_or_recv_cancel_timer/2]). + +%% User Events +-export([send/2, recv/3, close/2, shutdown/2, + new_user/2, get_opts/2, set_opts/2, session_info/1, + peer_certificate/1, renegotiation/1, negotiated_protocol/1, prf/5, + connection_information/1 + ]). + +%% General gen_statem state functions with extra callback argument +%% to determine if it is an SSL/TLS or DTLS gen_statem machine +-export([init/4, hello/4, abbreviated/4, certify/4, cipher/4, connection/4, downgrade/4]). + +%% gen_statem callbacks +-export([terminate/3, format_status/2]). + +%% +-export([handle_info/3, handle_call/5, handle_session/7, ssl_config/3, + prepare_connection/2, hibernate_after/3]). + + +%%==================================================================== +%% Internal application API +%%==================================================================== +%%-------------------------------------------------------------------- +-spec connect(tls_connection | dtls_connection, + host(), inet:port_number(), port(), + {#ssl_options{}, #socket_options{}, + %% Tracker only needed on server side + undefined}, + pid(), tuple(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Connect to an ssl server. +%%-------------------------------------------------------------------- +connect(Connection, Host, Port, Socket, Options, User, CbInfo, Timeout) -> + try Connection:start_fsm(client, Host, Port, Socket, Options, User, CbInfo, + Timeout) + catch + exit:{noproc, _} -> + {error, ssl_not_started} + end. +%%-------------------------------------------------------------------- +-spec ssl_accept(tls_connection | dtls_connection, + inet:port_number(), port(), + {#ssl_options{}, #socket_options{}, undefined | pid()}, + pid(), tuple(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Performs accept on an ssl listen socket. e.i. performs +%% ssl handshake. +%%-------------------------------------------------------------------- +ssl_accept(Connection, Port, Socket, Opts, User, CbInfo, Timeout) -> + try Connection:start_fsm(server, "localhost", Port, Socket, Opts, User, + CbInfo, Timeout) + catch + exit:{noproc, _} -> + {error, ssl_not_started} + end. + +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}. +%% +%% Description: Starts ssl handshake. +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = Pid}, Timeout) -> + case call(Pid, {start, Timeout}) of + connected -> + ok; + Error -> + Error + end. + +%%-------------------------------------------------------------------- +-spec handshake(#sslsocket{}, {#ssl_options{},#socket_options{}}, + timeout()) -> ok | {error, reason()}. +%% +%% Description: Starts ssl handshake with some new options +%%-------------------------------------------------------------------- +handshake(#sslsocket{pid = Pid}, SslOptions, Timeout) -> + case call(Pid, {start, SslOptions, Timeout}) of + connected -> + ok; + Error -> + Error + end. + +%-------------------------------------------------------------------- +-spec socket_control(tls_connection | dtls_connection, port(), pid(), atom()) -> + {ok, #sslsocket{}} | {error, reason()}. +%% +%% Description: Set the ssl process to own the accept socket +%%-------------------------------------------------------------------- +socket_control(Connection, Socket, Pid, Transport) -> + socket_control(Connection, Socket, Pid, Transport, undefined). + +%-------------------------------------------------------------------- +-spec socket_control(tls_connection | dtls_connection, port(), pid(), atom(), pid()| undefined) -> + {ok, #sslsocket{}} | {error, reason()}. +%%-------------------------------------------------------------------- +socket_control(Connection, Socket, Pid, Transport, ListenTracker) -> + case Transport:controlling_process(Socket, Pid) of + ok -> + {ok, ssl_socket:socket(Pid, Transport, Socket, Connection, ListenTracker)}; + {error, Reason} -> + {error, Reason} + end. + +%%-------------------------------------------------------------------- +-spec send(pid(), iodata()) -> ok | {error, reason()}. +%% +%% Description: Sends data over the ssl connection +%%-------------------------------------------------------------------- +send(Pid, Data) -> + call(Pid, {application_data, + %% iolist_to_binary should really + %% be called iodata_to_binary() + erlang:iolist_to_binary(Data)}). + +%%-------------------------------------------------------------------- +-spec recv(pid(), integer(), timeout()) -> + {ok, binary() | list()} | {error, reason()}. +%% +%% Description: Receives data when active = false +%%-------------------------------------------------------------------- +recv(Pid, Length, Timeout) -> + call(Pid, {recv, Length, Timeout}). + +%%-------------------------------------------------------------------- +-spec connection_information(pid()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Get the SNI hostname +%%-------------------------------------------------------------------- +connection_information(Pid) when is_pid(Pid) -> + call(Pid, connection_information). + +%%-------------------------------------------------------------------- +-spec close(pid(), {close, Timeout::integer() | + {NewController::pid(), Timeout::integer()}}) -> + ok | {ok, port()} | {error, reason()}. +%% +%% Description: Close an ssl connection +%%-------------------------------------------------------------------- +close(ConnectionPid, How) -> + case call(ConnectionPid, How) of + {error, closed} -> + ok; + Other -> + Other + end. +%%-------------------------------------------------------------------- +-spec shutdown(pid(), atom()) -> ok | {error, reason()}. +%% +%% Description: Same as gen_tcp:shutdown/2 +%%-------------------------------------------------------------------- +shutdown(ConnectionPid, How) -> + call(ConnectionPid, {shutdown, How}). + +%%-------------------------------------------------------------------- +-spec new_user(pid(), pid()) -> ok | {error, reason()}. +%% +%% Description: Changes process that receives the messages when active = true +%% or once. +%%-------------------------------------------------------------------- +new_user(ConnectionPid, User) -> + call(ConnectionPid, {new_user, User}). + +%%-------------------------------------------------------------------- +-spec negotiated_protocol(pid()) -> {ok, binary()} | {error, reason()}. +%% +%% Description: Returns the negotiated protocol +%%-------------------------------------------------------------------- +negotiated_protocol(ConnectionPid) -> + call(ConnectionPid, negotiated_protocol). + +%%-------------------------------------------------------------------- +-spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Same as inet:getopts/2 +%%-------------------------------------------------------------------- +get_opts(ConnectionPid, OptTags) -> + call(ConnectionPid, {get_opts, OptTags}). +%%-------------------------------------------------------------------- +-spec set_opts(pid(), list()) -> ok | {error, reason()}. +%% +%% Description: Same as inet:setopts/2 +%%-------------------------------------------------------------------- +set_opts(ConnectionPid, Options) -> + call(ConnectionPid, {set_opts, Options}). + +%%-------------------------------------------------------------------- +-spec session_info(pid()) -> {ok, list()} | {error, reason()}. +%% +%% Description: Returns info about the ssl session +%%-------------------------------------------------------------------- +session_info(ConnectionPid) -> + call(ConnectionPid, session_info). + +%%-------------------------------------------------------------------- +-spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. +%% +%% Description: Returns the peer cert +%%-------------------------------------------------------------------- +peer_certificate(ConnectionPid) -> + call(ConnectionPid, peer_certificate). + +%%-------------------------------------------------------------------- +-spec renegotiation(pid()) -> ok | {error, reason()}. +%% +%% Description: Starts a renegotiation of the ssl session. +%%-------------------------------------------------------------------- +renegotiation(ConnectionPid) -> + call(ConnectionPid, renegotiate). + +%%-------------------------------------------------------------------- +-spec prf(pid(), binary() | 'master_secret', binary(), + binary() | ssl:prf_random(), non_neg_integer()) -> + {ok, binary()} | {error, reason()} | {'EXIT', term()}. +%% +%% Description: use a ssl sessions TLS PRF to generate key material +%%-------------------------------------------------------------------- +prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> + call(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). + +%%-------------------------------------------------------------------- +-spec handle_session(#server_hello{}, ssl_record:ssl_version(), + binary(), #connection_states{}, _,_, #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +handle_session(#server_hello{cipher_suite = CipherSuite, + compression_method = Compression}, + Version, NewId, ConnectionStates, ProtoExt, Protocol0, + #state{session = #session{session_id = OldId}, + negotiated_version = ReqVersion, + negotiated_protocol = CurrentProtocol} = State0) -> + {KeyAlgorithm, _, _, _} = + ssl_cipher:suite_definition(CipherSuite), + + PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), + + {ExpectNPN, Protocol} = case Protocol0 of + undefined -> {false, CurrentProtocol}; + _ -> {ProtoExt =:= npn, Protocol0} + end, + + State = State0#state{key_algorithm = KeyAlgorithm, + negotiated_version = Version, + connection_states = ConnectionStates, + premaster_secret = PremasterSecret, + expecting_next_protocol_negotiation = ExpectNPN, + negotiated_protocol = Protocol}, + + case ssl_session:is_new(OldId, NewId) of + true -> + handle_new_session(NewId, CipherSuite, Compression, + State#state{connection_states = ConnectionStates}); + false -> + handle_resumed_session(NewId, + State#state{connection_states = ConnectionStates}) + end. + +%%-------------------------------------------------------------------- +-spec ssl_config(#ssl_options{}, client | server, #state{}) -> #state{}. +%%-------------------------------------------------------------------- +ssl_config(Opts, Role, State) -> + {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbInfo, + OwnCert, Key, DHParams} = + ssl_config:init(Opts, Role), + Handshake = ssl_handshake:init_handshake_history(), + TimeStamp = erlang:monotonic_time(), + Session = State#state.session, + State#state{tls_handshake_history = Handshake, + session = Session#session{own_certificate = OwnCert, + time_stamp = TimeStamp}, + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbInfo, + session_cache = CacheHandle, + private_key = Key, + diffie_hellman_params = DHParams, + ssl_options = Opts}. + +%%==================================================================== +%% gen_statem state functions +%%==================================================================== +%%-------------------------------------------------------------------- +-spec init(gen_statem:event_type(), + {start, timeout()} | {start, {list(), list()}, timeout()}| term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- + +init({call, From}, {start, Timeout}, State0, Connection) -> + Timer = start_or_recv_cancel_timer(Timeout, From), + {Record, State} = Connection:next_record(State0#state{start_or_recv_from = From, + timer = Timer}), + Connection:next_event(hello, Record, State); +init({call, From}, {start, {Opts, EmOpts}, Timeout}, + #state{role = Role} = State0, Connection) -> + try + State = ssl_config(Opts, Role, State0), + init({call, From}, {start, Timeout}, + State#state{ssl_options = Opts, socket_options = EmOpts}, Connection) + catch throw:Error -> + {stop_and_reply, normal, {reply, From, {error, Error}}} + end; +init({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, init, State, Connection); +init(_Type, _Event, _State, _Connection) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #server_hello{} | term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, hello, State, Connection); +hello(internal, {common_client_hello, Type, ServerHelloExt}, State, Connection) -> + do_server_hello(Type, ServerHelloExt, State, Connection); +hello(info, Msg, State, _) -> + handle_info(Msg, hello, State); +hello(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, hello, State, Connection). + +%%-------------------------------------------------------------------- +-spec abbreviated(gen_statem:event_type(), + #hello_request{} | #finished{} | term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +abbreviated({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, abbreviated, State, Connection); + +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{role = server, + negotiated_version = Version, + expecting_finished = true, + tls_handshake_history = Handshake, + session = #session{master_secret = MasterSecret}, + connection_states = ConnectionStates0} = + State0, Connection) -> + case ssl_handshake:verify_connection(Version, Finished, client, + get_current_prf(ConnectionStates0, write), + MasterSecret, Handshake) of + verified -> + ConnectionStates = + ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0), + {Record, State} = prepare_connection(State0#state{connection_states = ConnectionStates, + expecting_finished = false}, Connection), + Connection:next_event(connection, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, abbreviated, State0) + end; + +abbreviated(internal, #finished{verify_data = Data} = Finished, + #state{role = client, tls_handshake_history = Handshake0, + session = #session{master_secret = MasterSecret}, + negotiated_version = Version, + connection_states = ConnectionStates0} = State0, Connection) -> + case ssl_handshake:verify_connection(Version, Finished, server, + get_pending_prf(ConnectionStates0, write), + MasterSecret, Handshake0) of + verified -> + ConnectionStates1 = + ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0), + State1 = + finalize_handshake(State0#state{connection_states = ConnectionStates1}, + abbreviated, Connection), + {Record, State} = prepare_connection(State1#state{expecting_finished = false}, Connection), + Connection:next_event(connection, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, abbreviated, State0) + end; + +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation +abbreviated(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{role = server, expecting_next_protocol_negotiation = true} = State0, + Connection) -> + {Record, State} = + Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), + Connection:next_event(abbreviated, Record, + State#state{expecting_next_protocol_negotiation = false}); +abbreviated(internal, + #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = + State0, Connection) -> + ConnectionStates1 = + ssl_record:activate_pending_connection_state(ConnectionStates0, read), + {Record, State} = Connection:next_record(State0#state{connection_states = + ConnectionStates1}), + Connection:next_event(abbreviated, Record, State#state{expecting_finished = true}); +abbreviated(info, Msg, State, _) -> + handle_info(Msg, abbreviated, State); +abbreviated(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, abbreviated, State, Connection). + +%%-------------------------------------------------------------------- +-spec certify(gen_statem:event_type(), + #hello_request{} | #certificate{} | #server_key_exchange{} | + #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +certify({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, certify, State, Connection); +certify(info, Msg, State, _) -> + handle_info(Msg, certify, State); +certify(internal, #certificate{asn1_certificates = []}, + #state{role = server, negotiated_version = Version, + ssl_options = #ssl_options{verify = verify_peer, + fail_if_no_peer_cert = true}} = + State, Connection) -> + Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), + Connection:handle_own_alert(Alert, Version, certify, State); + +certify(internal, #certificate{asn1_certificates = []}, + #state{role = server, + ssl_options = #ssl_options{verify = verify_peer, + fail_if_no_peer_cert = false}} = + State0, Connection) -> + {Record, State} = + Connection:next_record(State0#state{client_certificate_requested = false}), + Connection:next_event(certify, Record, State); + +certify(internal, #certificate{}, + #state{role = server, + negotiated_version = Version, + ssl_options = #ssl_options{verify = verify_none}} = + State, Connection) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE, unrequested_certificate), + Connection:handle_own_alert(Alert, Version, certify, State); + +certify(internal, #certificate{} = Cert, + #state{negotiated_version = Version, + role = Role, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + crl_db = CRLDbInfo, + ssl_options = Opts} = State, Connection) -> + case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, + Opts#ssl_options.depth, + Opts#ssl_options.verify, + Opts#ssl_options.verify_fun, + Opts#ssl_options.partial_chain, + Opts#ssl_options.crl_check, + CRLDbInfo, + Role) of + {PeerCert, PublicKeyInfo} -> + handle_peer_cert(Role, PeerCert, PublicKeyInfo, + State#state{client_certificate_requested = false}, Connection); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State) + end; + +certify(internal, #server_key_exchange{exchange_keys = Keys}, + #state{role = client, negotiated_version = Version, + key_algorithm = Alg, + public_key_info = PubKeyInfo, + connection_states = ConnectionStates} = State, Connection) + when Alg == dhe_dss; Alg == dhe_rsa; + Alg == ecdhe_rsa; Alg == ecdhe_ecdsa; + Alg == dh_anon; Alg == ecdh_anon; + Alg == psk; Alg == dhe_psk; Alg == rsa_psk; + Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> + + Params = ssl_handshake:decode_server_key(Keys, Alg, Version), + + %% Use negotiated value if TLS-1.2 otherwhise return default + HashSign = negotiated_hashsign(Params#server_key_params.hashsign, Alg, PubKeyInfo, Version), + + case is_anonymous(Alg) of + true -> + calculate_secret(Params#server_key_params.params, + State#state{hashsign_algorithm = HashSign}, Connection); + false -> + case ssl_handshake:verify_server_key(Params, HashSign, + ConnectionStates, Version, PubKeyInfo) of + true -> + calculate_secret(Params#server_key_params.params, + State#state{hashsign_algorithm = HashSign}, + Connection); + false -> + Connection:handle_own_alert(?ALERT_REC(?FATAL, ?DECRYPT_ERROR), + Version, certify, State) + end + end; + +certify(internal, #certificate_request{hashsign_algorithms = HashSigns}, + #state{session = #session{own_certificate = Cert}, + key_algorithm = KeyExAlg, + ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, + negotiated_version = Version} = State0, Connection) -> + + case ssl_handshake:select_hashsign(HashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of + #alert {} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0); + NegotiatedHashSign -> + {Record, State} = Connection:next_record(State0#state{client_certificate_requested = true}), + Connection:next_event(certify, Record, + State#state{cert_hashsign_algorithm = NegotiatedHashSign}) + end; + +%% PSK and RSA_PSK might bypass the Server-Key-Exchange +certify(internal, #server_hello_done{}, + #state{session = #session{master_secret = undefined}, + negotiated_version = Version, + psk_identity = PSKIdentity, + ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, + premaster_secret = undefined, + role = client, + key_algorithm = Alg} = State0, Connection) + when Alg == psk -> + case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup) of + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0); + PremasterSecret -> + State = master_secret(PremasterSecret, + State0#state{premaster_secret = PremasterSecret}), + client_certify_and_key_exchange(State, Connection) + end; + +certify(internal, #server_hello_done{}, + #state{session = #session{master_secret = undefined}, + ssl_options = #ssl_options{user_lookup_fun = PSKLookup}, + negotiated_version = {Major, Minor} = Version, + psk_identity = PSKIdentity, + premaster_secret = undefined, + role = client, + key_algorithm = Alg} = State0, Connection) + when Alg == rsa_psk -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, + case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, + RSAPremasterSecret) of + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0); + PremasterSecret -> + State = master_secret(PremasterSecret, + State0#state{premaster_secret = RSAPremasterSecret}), + client_certify_and_key_exchange(State, Connection) + end; + +%% Master secret was determined with help of server-key exchange msg +certify(internal, #server_hello_done{}, + #state{session = #session{master_secret = MasterSecret} = Session, + connection_states = ConnectionStates0, + negotiated_version = Version, + premaster_secret = undefined, + role = client} = State0, Connection) -> + case ssl_handshake:master_secret(record_cb(Connection), Version, Session, + ConnectionStates0, client) of + {MasterSecret, ConnectionStates} -> + State = State0#state{connection_states = ConnectionStates}, + client_certify_and_key_exchange(State, Connection); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0) + end; + +%% Master secret is calculated from premaster_secret +certify(internal, #server_hello_done{}, + #state{session = Session0, + connection_states = ConnectionStates0, + negotiated_version = Version, + premaster_secret = PremasterSecret, + role = client} = State0, Connection) -> + case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret, + ConnectionStates0, client) of + {MasterSecret, ConnectionStates} -> + Session = Session0#session{master_secret = MasterSecret}, + State = State0#state{connection_states = ConnectionStates, + session = Session}, + client_certify_and_key_exchange(State, Connection); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0) + end; + +certify(internal = Type, #client_key_exchange{} = Msg, + #state{role = server, + client_certificate_requested = true, + ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State, + Connection) -> + %% We expect a certificate here + handle_common_event(Type, Msg, certify, State, Connection); + +certify(internal, #client_key_exchange{exchange_keys = Keys}, + State = #state{key_algorithm = KeyAlg, negotiated_version = Version}, Connection) -> + try + certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), + State, Connection) + catch + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State) + end; + +certify(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, certify, State, Connection). + +%%-------------------------------------------------------------------- +-spec cipher(gen_statem:event_type(), + #hello_request{} | #certificate_verify{} | #finished{} | term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +cipher({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, cipher, State, Connection); + +cipher(info, Msg, State, _) -> + handle_info(Msg, cipher, State); + +cipher(internal, #certificate_verify{signature = Signature, + hashsign_algorithm = CertHashSign}, + #state{role = server, + key_algorithm = KexAlg, + public_key_info = PublicKeyInfo, + negotiated_version = Version, + session = #session{master_secret = MasterSecret}, + tls_handshake_history = Handshake + } = State0, Connection) -> + + %% Use negotiated value if TLS-1.2 otherwhise return default + HashSign = negotiated_hashsign(CertHashSign, KexAlg, PublicKeyInfo, Version), + case ssl_handshake:certificate_verify(Signature, PublicKeyInfo, + Version, HashSign, MasterSecret, Handshake) of + valid -> + {Record, State} = Connection:next_record(State0), + Connection:next_event(cipher, Record, + State#state{cert_hashsign_algorithm = HashSign}); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, cipher, State0) + end; + +%% client must send a next protocol message if we are expecting it +cipher(internal, #finished{}, + #state{role = server, expecting_next_protocol_negotiation = true, + negotiated_protocol = undefined, negotiated_version = Version} = State0, + Connection) -> + Connection:handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); + +cipher(internal, #finished{verify_data = Data} = Finished, + #state{negotiated_version = Version, + host = Host, + port = Port, + role = Role, + expecting_finished = true, + session = #session{master_secret = MasterSecret} + = Session0, + connection_states = ConnectionStates0, + tls_handshake_history = Handshake0} = State, Connection) -> + case ssl_handshake:verify_connection(Version, Finished, + opposite_role(Role), + get_current_prf(ConnectionStates0, read), + MasterSecret, Handshake0) of + verified -> + Session = register_session(Role, Host, Port, Session0), + cipher_role(Role, Data, Session, + State#state{expecting_finished = false}, Connection); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, cipher, State) + end; + +%% only allowed to send next_protocol message after change cipher spec +%% & before finished message and it is not allowed during renegotiation +cipher(internal, #next_protocol{selected_protocol = SelectedProtocol}, + #state{role = server, expecting_next_protocol_negotiation = true, + expecting_finished = true} = State0, Connection) -> + {Record, State} = + Connection:next_record(State0#state{negotiated_protocol = SelectedProtocol}), + Connection:next_event(cipher, Record, + State#state{expecting_next_protocol_negotiation = false}); +cipher(internal, #change_cipher_spec{type = <<1>>}, #state{connection_states = ConnectionStates0} = + State0, Connection) -> + ConnectionStates1 = + ssl_record:activate_pending_connection_state(ConnectionStates0, read), + {Record, State} = Connection:next_record(State0#state{connection_states = + ConnectionStates1}), + Connection:next_event(cipher, Record, State#state{expecting_finished = true}); +cipher(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, cipher, State, Connection). + +%%-------------------------------------------------------------------- +-spec connection(gen_statem:event_type(), term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +connection({call, From}, {application_data, Data}, + #state{protocol_cb = Connection} = State, Connection) -> + %% We should look into having a worker process to do this to + %% parallize send and receive decoding and not block the receiver + %% if sending is overloading the socket. + try + Connection:write_application_data(Data, From, State) + catch throw:Error -> + hibernate_after(connection, State, [{reply, From, Error}]) + end; +connection({call, RecvFrom}, {recv, N, Timeout}, + #state{protocol_cb = Connection, socket_options = + #socket_options{active = false}} = State0, Connection) -> + Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), + Connection:passive_receive(State0#state{bytes_to_read = N, + start_or_recv_from = RecvFrom, + timer = Timer}, connection); +connection({call, From}, renegotiate, #state{protocol_cb = Connection} = State, + Connection) -> + Connection:renegotiate(State#state{renegotiation = {true, From}}, []); +connection({call, From}, peer_certificate, + #state{session = #session{peer_certificate = Cert}} = State, _) -> + hibernate_after(connection, State, [{reply, From, {ok, Cert}}]); +connection({call, From}, connection_information, State, _) -> + Info = connection_info(State), + hibernate_after(connection, State, [{reply, From, {ok, Info}}]); +connection({call, From}, session_info, #state{session = #session{session_id = Id, + cipher_suite = Suite}} = State, _) -> + SessionInfo = [{session_id, Id}, + {cipher_suite, ssl_cipher:erl_suite_definition(Suite)}], + hibernate_after(connection, State, [{reply, From, SessionInfo}]); +connection({call, From}, negotiated_protocol, + #state{negotiated_protocol = undefined} = State, _) -> + hibernate_after(connection, State, [{reply, From, {error, protocol_not_negotiated}}]); +connection({call, From}, negotiated_protocol, + #state{negotiated_protocol = SelectedProtocol} = State, _) -> + hibernate_after(connection, State, + [{reply, From, {ok, SelectedProtocol}}]); +connection({call, From}, Msg, State, Connection) -> + handle_call(Msg, From, connection, State, Connection); +connection(info, Msg, State, _) -> + handle_info(Msg, connection, State); +connection(internal, {recv, _}, State, Connection) -> + Connection:passive_receive(State, connection); +connection(Type, Msg, State, Connection) -> + handle_common_event(Type, Msg, connection, State, Connection). + +%%-------------------------------------------------------------------- +-spec downgrade(gen_statem:event_type(), term(), + #state{}, tls_connection | dtls_connection) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +downgrade(internal, #alert{description = ?CLOSE_NOTIFY}, + #state{transport_cb = Transport, socket = Socket, + downgrade = {Pid, From}} = State, _) -> + ssl_socket:setopts(Transport, Socket, [{active, false}, {packet, 0}, {mode, binary}]), + Transport:controlling_process(Socket, Pid), + gen_statem:reply(From, {ok, Socket}), + {stop, normal, State}; +downgrade(timeout, downgrade, #state{downgrade = {_, From}} = State, _) -> + gen_statem:reply(From, {error, timeout}), + {stop, normal, State}; +downgrade(Type, Event, State, Connection) -> + handle_common_event(Type, Event, downgrade, State, Connection). + +%%-------------------------------------------------------------------- +%% Event handling functions called by state functions to handle +%% common or unexpected events for the state. +%%-------------------------------------------------------------------- +handle_common_event(internal, {handshake, {#hello_request{} = Handshake, _}}, connection = StateName, + #state{role = client} = State, _) -> + %% Should not be included in handshake history + {next_state, StateName, State#state{renegotiation = {true, peer}}, [{next_event, internal, Handshake}]}; +handle_common_event(internal, {handshake, {#hello_request{}, _}}, StateName, #state{role = client}, _) + when StateName =/= connection -> + {keep_state_and_data}; +handle_common_event(internal, {handshake, {Handshake, Raw}}, StateName, + #state{tls_handshake_history = Hs0} = State0, Connection) -> + %% This function handles client SNI hello extension when Handshake is + %% a client_hello, which needs to be determined by the connection callback. + %% In other cases this is a noop + State = Connection:handle_sni_extension(Handshake, State0), + HsHist = ssl_handshake:update_handshake_history(Hs0, Raw), + {next_state, StateName, State#state{tls_handshake_history = HsHist}, + [{next_event, internal, Handshake}]}; +handle_common_event(internal, {tls_record, TLSRecord}, StateName, State, Connection) -> + Connection:handle_common_event(internal, TLSRecord, StateName, State); +handle_common_event(timeout, hibernate, _, _, _) -> + {keep_state_and_data, [hibernate]}; +handle_common_event(internal, {application_data, Data}, StateName, State0, Connection) -> + case Connection:read_application_data(Data, State0) of + {stop, Reason, State} -> + {stop, Reason, State}; + {Record, State} -> + Connection:next_event(StateName, Record, State) + end; +handle_common_event(internal, #change_cipher_spec{type = <<1>>}, StateName, + #state{negotiated_version = Version} = State, Connection) -> + Connection:handle_own_alert(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE), Version, + StateName, State); +handle_common_event(internal, _, _, _, _) -> + {keep_state_and_data, [postpone]}; +handle_common_event(_Type, Msg, StateName, #state{negotiated_version = Version} = State, + Connection) -> + Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), + Connection:handle_own_alert(Alert, Version, {StateName, Msg}, State). + +handle_call({application_data, _Data}, _, _, _, _) -> + %% In renegotiation priorities handshake, send data when handshake is finished + {keep_state_and_data, [postpone]}; +handle_call({close, {Pid, Timeout}}, From, StateName, State0, Connection) when is_pid(Pid) -> + %% terminate will send close alert to peer + State = State0#state{downgrade = {Pid, From}}, + Connection:terminate(downgrade, StateName, State), + %% User downgrades connection + %% When downgrading an TLS connection to a transport connection + %% we must recive the close alert from the peer before releasing the + %% transport socket. + {next_state, downgrade, State, [{timeout, Timeout, downgrade}]}; +handle_call({close, _} = Close, From, StateName, State, Connection) -> + %% Run terminate before returning so that the reuseaddr + %% inet-option + Result = Connection:terminate(Close, StateName, State), + {stop_and_reply, {shutdown, normal}, + {reply, From, Result}, State}; +handle_call({shutdown, How0}, From, _, + #state{transport_cb = Transport, + negotiated_version = Version, + connection_states = ConnectionStates, + socket = Socket}, _) -> + case How0 of + How when How == write; How == both -> + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + {BinMsg, _} = + ssl_alert:encode(Alert, Version, ConnectionStates), + Transport:send(Socket, BinMsg); + _ -> + ok + end, + + case Transport:shutdown(Socket, How0) of + ok -> + {keep_state_and_data, [{reply, From, ok}]}; + Error -> + gen_statem:reply(From, {error, Error}), + {stop, normal} + end; +handle_call({recv, _N, _Timeout}, From, _, + #state{socket_options = + #socket_options{active = Active}}, _) when Active =/= false -> + {keep_state_and_data, [{reply, From, {error, einval}}]}; +handle_call({recv, N, Timeout}, RecvFrom, StateName, State, _) -> + %% Doing renegotiate wait with handling request until renegotiate is + %% finished. + Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), + {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom, + timer = Timer}, + [{next_event, internal, {recv, RecvFrom}}]}; +handle_call({new_user, User}, From, StateName, + State =#state{user_application = {OldMon, _}}, _) -> + NewMon = erlang:monitor(process, User), + erlang:demonitor(OldMon, [flush]), + {next_state, StateName, State#state{user_application = {NewMon,User}}, + [{reply, From, ok}]}; +handle_call({get_opts, OptTags}, From, _, + #state{socket = Socket, + transport_cb = Transport, + socket_options = SockOpts}, _) -> + OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []), + {keep_state_and_data, [{reply, From, OptsReply}]}; +handle_call({set_opts, Opts0}, From, StateName, + #state{socket_options = Opts1, + socket = Socket, + transport_cb = Transport} = State0, _) -> + {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []), + State = State0#state{socket_options = Opts}, + handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); + +handle_call(renegotiate, From, StateName, _, _) when StateName =/= connection -> + {keep_state_and_data, [{reply, From, {error, already_renegotiating}}]}; +handle_call({prf, Secret, Label, Seed, WantedLength}, From, _, + #state{connection_states = ConnectionStates, + negotiated_version = Version}, _) -> + ConnectionState = + ssl_record:current_connection_state(ConnectionStates, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{master_secret = MasterSecret, + client_random = ClientRandom, + server_random = ServerRandom, + prf_algorithm = PRFAlgorithm} = SecParams, + Reply = try + SecretToUse = case Secret of + _ when is_binary(Secret) -> Secret; + master_secret -> MasterSecret + end, + SeedToUse = lists:reverse( + lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; + (client_random, Acc) -> [ClientRandom|Acc]; + (server_random, Acc) -> [ServerRandom|Acc] + end, [], Seed)), + ssl_handshake:prf(Version, PRFAlgorithm, SecretToUse, Label, SeedToUse, WantedLength) + catch + exit:_ -> {error, badarg}; + error:Reason -> {error, Reason} + end, + {keep_state_and_data, [{reply, From, Reply}]}; +handle_call(_,_,_,_,_) -> + {keep_state_and_data, [postpone]}. + +handle_info({ErrorTag, Socket, econnaborted}, StateName, + #state{socket = Socket, transport_cb = Transport, + start_or_recv_from = StartFrom, role = Role, + protocol_cb = Connection, + error_tag = ErrorTag, + tracker = Tracker} = State) when StateName =/= connection -> + Connection:alert_user(Transport, Tracker,Socket, + StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), + {stop, normal, State}; + +handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, + protocol_cb = Connection, + error_tag = ErrorTag} = State) -> + Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), + error_logger:info_report(Report), + Connection:handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), + {stop, normal, State}; + +handle_info({'DOWN', MonitorRef, _, _, _}, _, + State = #state{user_application={MonitorRef,_Pid}}) -> + {stop, normal, State}; + +%%% So that terminate will be run when supervisor issues shutdown +handle_info({'EXIT', _Sup, shutdown}, _StateName, State) -> + {stop, shutdown, State}; +handle_info({'EXIT', Socket, normal}, _StateName, #state{socket = Socket} = State) -> + %% Handle as transport close" + {stop, {shutdown, transport_closed}, State}; + +handle_info(allow_renegotiate, StateName, State) -> + {next_state, StateName, State#state{allow_renegotiate = true}}; + +handle_info({cancel_start_or_recv, StartFrom}, StateName, + #state{renegotiation = {false, first}} = State) when StateName =/= connection -> + {stop_and_reply, {shutdown, user_timeout}, + {reply, StartFrom, {error, timeout}}, State#state{timer = undefined}}; + +handle_info({cancel_start_or_recv, RecvFrom}, StateName, + #state{start_or_recv_from = RecvFrom} = State) when RecvFrom =/= undefined -> + {next_state, StateName, State#state{start_or_recv_from = undefined, + bytes_to_read = undefined, + timer = undefined}, [{reply, RecvFrom, {error, timeout}}]}; + +handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> + {next_state, StateName, State#state{timer = undefined}}; + +handle_info(Msg, StateName, #state{socket = Socket, error_tag = Tag} = State) -> + Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [{Msg, Tag, Socket}]), + error_logger:info_report(Report), + {next_state, StateName, State}. + +%%-------------------------------------------------------------------- +%% gen_statem callbacks +%%-------------------------------------------------------------------- +terminate(_, _, #state{terminated = true}) -> + %% Happens when user closes the connection using ssl:close/1 + %% we want to guarantee that Transport:close has been called + %% when ssl:close/1 returns. + ok; + +terminate({shutdown, transport_closed} = Reason, + _StateName, #state{protocol_cb = Connection, + socket = Socket, transport_cb = Transport} = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined); +terminate({shutdown, own_alert}, _StateName, #state{%%send_queue = SendQueue, + protocol_cb = Connection, + socket = Socket, + transport_cb = Transport} = State) -> + handle_trusted_certs_db(State), + case application:get_env(ssl, alert_timeout) of + {ok, Timeout} when is_integer(Timeout) -> + Connection:close({timeout, Timeout}, Socket, Transport, undefined, undefined); + _ -> + Connection:close({timeout, ?DEFAULT_TIMEOUT}, Socket, Transport, undefined, undefined) + end; +terminate(Reason, connection, #state{negotiated_version = Version, + protocol_cb = Connection, + connection_states = ConnectionStates0, + ssl_options = #ssl_options{padding_check = Check}, + transport_cb = Transport, socket = Socket + } = State) -> + handle_trusted_certs_db(State), + {BinAlert, ConnectionStates} = terminate_alert(Reason, Version, ConnectionStates0), + Transport:send(Socket, BinAlert), + Connection:close(Reason, Socket, Transport, ConnectionStates, Check); + +terminate(Reason, _StateName, #state{transport_cb = Transport, protocol_cb = Connection, + socket = Socket + } = State) -> + handle_trusted_certs_db(State), + Connection:close(Reason, Socket, Transport, undefined, undefined). + +format_status(normal, [_, StateName, State]) -> + [{data, [{"State", {StateName, State}}]}]; +format_status(terminate, [_, StateName, State]) -> + SslOptions = (State#state.ssl_options), + NewOptions = SslOptions#ssl_options{password = ?SECRET_PRINTOUT, + cert = ?SECRET_PRINTOUT, + cacerts = ?SECRET_PRINTOUT, + key = ?SECRET_PRINTOUT, + dh = ?SECRET_PRINTOUT, + psk_identity = ?SECRET_PRINTOUT, + srp_identity = ?SECRET_PRINTOUT}, + [{data, [{"State", {StateName, State#state{connection_states = ?SECRET_PRINTOUT, + protocol_buffers = ?SECRET_PRINTOUT, + user_data_buffer = ?SECRET_PRINTOUT, + tls_handshake_history = ?SECRET_PRINTOUT, + session = ?SECRET_PRINTOUT, + private_key = ?SECRET_PRINTOUT, + diffie_hellman_params = ?SECRET_PRINTOUT, + diffie_hellman_keys = ?SECRET_PRINTOUT, + srp_params = ?SECRET_PRINTOUT, + srp_keys = ?SECRET_PRINTOUT, + premaster_secret = ?SECRET_PRINTOUT, + ssl_options = NewOptions, + flight_buffer = ?SECRET_PRINTOUT} + }}]}]. +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +connection_info(#state{sni_hostname = SNIHostname, + session = #session{cipher_suite = CipherSuite}, + negotiated_version = Version, ssl_options = Opts}) -> + [{protocol, tls_record:protocol_version(Version)}, + {cipher_suite, ssl_cipher:erl_suite_definition(CipherSuite)}, + {sni_hostname, SNIHostname}] ++ ssl_options_list(Opts). + +do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = + ServerHelloExt, + #state{negotiated_version = Version, + session = #session{session_id = SessId}, + connection_states = ConnectionStates0} + = State0, Connection) when is_atom(Type) -> + + ServerHello = + ssl_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt), + State = server_hello(ServerHello, + State0#state{expecting_next_protocol_negotiation = + NextProtocols =/= undefined}, Connection), + case Type of + new -> + new_server_hello(ServerHello, State, Connection); + resumed -> + resumed_server_hello(State, Connection) + end. + +new_server_hello(#server_hello{cipher_suite = CipherSuite, + compression_method = Compression, + session_id = SessionId}, + #state{session = Session0, + negotiated_version = Version} = State0, Connection) -> + try server_certify_and_key_exchange(State0, Connection) of + #state{} = State1 -> + State2 = server_hello_done(State1, Connection), + Session = + Session0#session{session_id = SessionId, + cipher_suite = CipherSuite, + compression_method = Compression}, + {Record, State} = Connection:next_record(State2#state{session = Session}), + Connection:next_event(certify, Record, State) + catch + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, hello, State0) + end. + +resumed_server_hello(#state{session = Session, + connection_states = ConnectionStates0, + negotiated_version = Version} = State0, Connection) -> + + case ssl_handshake:master_secret(record_cb(Connection), Version, Session, + ConnectionStates0, server) of + {_, ConnectionStates1} -> + State1 = State0#state{connection_states = ConnectionStates1, + session = Session}, + State2 = + finalize_handshake(State1, abbreviated, Connection), + {Record, State} = Connection:next_record(State2), + Connection:next_event(abbreviated, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, hello, State0) + end. + +server_hello(ServerHello, State0, Connection) -> + CipherSuite = ServerHello#server_hello.cipher_suite, + {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), + State = Connection:queue_handshake(ServerHello, State0), + State#state{key_algorithm = KeyAlgorithm}. + +server_hello_done(State, Connection) -> + HelloDone = ssl_handshake:server_hello_done(), + Connection:send_handshake(HelloDone, State). + +handle_peer_cert(Role, PeerCert, PublicKeyInfo, + #state{session = #session{cipher_suite = CipherSuite} = Session} = State0, + Connection) -> + State1 = State0#state{session = + Session#session{peer_certificate = PeerCert}, + public_key_info = PublicKeyInfo}, + {KeyAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite), + State2 = handle_peer_cert_key(Role, PeerCert, PublicKeyInfo, KeyAlg, State1), + + {Record, State} = Connection:next_record(State2), + Connection:next_event(certify, Record, State). + +handle_peer_cert_key(client, _, + {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, + PublicKeyParams}, + KeyAlg, State) when KeyAlg == ecdh_rsa; + KeyAlg == ecdh_ecdsa -> + ECDHKey = public_key:generate_key(PublicKeyParams), + PremasterSecret = ssl_handshake:premaster_secret(PublicKey, ECDHKey), + master_secret(PremasterSecret, State#state{diffie_hellman_keys = ECDHKey}); + +%% We do currently not support cipher suites that use fixed DH. +%% If we want to implement that the following clause can be used +%% to extract DH parameters form cert. +%% handle_peer_cert_key(client, _PeerCert, {?dhpublicnumber, PublicKey, PublicKeyParams}, +%% {_,SignAlg}, +%% #state{diffie_hellman_keys = {_, MyPrivatKey}} = State) when +%% SignAlg == dh_rsa; +%% SignAlg == dh_dss -> +%% dh_master_secret(PublicKeyParams, PublicKey, MyPrivatKey, State); +handle_peer_cert_key(_, _, _, _, State) -> + State. + +certify_client(#state{client_certificate_requested = true, role = client, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + session = #session{own_certificate = OwnCert}} + = State, Connection) -> + Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), + Connection:queue_handshake(Certificate, State); + +certify_client(#state{client_certificate_requested = false} = State, _) -> + State. + +verify_client_cert(#state{client_certificate_requested = true, role = client, + negotiated_version = Version, + private_key = PrivateKey, + session = #session{master_secret = MasterSecret, + own_certificate = OwnCert}, + cert_hashsign_algorithm = HashSign, + tls_handshake_history = Handshake0} = State, Connection) -> + + case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret, + Version, HashSign, PrivateKey, Handshake0) of + #certificate_verify{} = Verified -> + Connection:queue_handshake(Verified, State); + ignore -> + State; + #alert{} = Alert -> + throw(Alert) + end; +verify_client_cert(#state{client_certificate_requested = false} = State, _) -> + State. + +client_certify_and_key_exchange(#state{negotiated_version = Version} = + State0, Connection) -> + try do_client_certify_and_key_exchange(State0, Connection) of + State1 = #state{} -> + State2 = finalize_handshake(State1, certify, Connection), + State3 = State2#state{ + %% Reinitialize + client_certificate_requested = false}, + {Record, State} = Connection:next_record(State3), + Connection:next_event(cipher, Record, State) + catch + throw:#alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0) + end. + +do_client_certify_and_key_exchange(State0, Connection) -> + State1 = certify_client(State0, Connection), + State2 = key_exchange(State1, Connection), + verify_client_cert(State2, Connection). + +server_certify_and_key_exchange(State0, Connection) -> + State1 = certify_server(State0, Connection), + State2 = key_exchange(State1, Connection), + request_client_cert(State2, Connection). + +certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, + #state{private_key = Key} = State, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(EncPMS, Key), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + +certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, + #state{diffie_hellman_params = #'DHParameter'{} = Params, + diffie_hellman_keys = {_, ServerDhPrivateKey}} = State, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientPublicDhKey, ServerDhPrivateKey, Params), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + +certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, + #state{diffie_hellman_keys = ECDHKey} = State, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(#'ECPoint'{point = ClientPublicEcDhPoint}, ECDHKey), + calculate_master_secret(PremasterSecret, State, Connection, certify, cipher); + +certify_client_key_exchange(#client_psk_identity{} = ClientKey, + #state{ssl_options = + #ssl_options{user_lookup_fun = PSKLookup}} = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); + +certify_client_key_exchange(#client_dhe_psk_identity{} = ClientKey, + #state{diffie_hellman_params = #'DHParameter'{} = Params, + diffie_hellman_keys = {_, ServerDhPrivateKey}, + ssl_options = + #ssl_options{user_lookup_fun = PSKLookup}} = State0, + Connection) -> + PremasterSecret = + ssl_handshake:premaster_secret(ClientKey, ServerDhPrivateKey, Params, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); +certify_client_key_exchange(#client_rsa_psk_identity{} = ClientKey, + #state{private_key = Key, + ssl_options = + #ssl_options{user_lookup_fun = PSKLookup}} = State0, + Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, PSKLookup), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher); + +certify_client_key_exchange(#client_srp_public{} = ClientKey, + #state{srp_params = Params, + srp_keys = Key + } = State0, Connection) -> + PremasterSecret = ssl_handshake:premaster_secret(ClientKey, Key, Params), + calculate_master_secret(PremasterSecret, State0, Connection, certify, cipher). + +certify_server(#state{key_algorithm = Algo} = State, _) when Algo == dh_anon; + Algo == ecdh_anon; + Algo == psk; + Algo == dhe_psk; + Algo == srp_anon -> + State; + +certify_server(#state{cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + session = #session{own_certificate = OwnCert}} = State, Connection) -> + case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of + Cert = #certificate{} -> + Connection:queue_handshake(Cert, State); + Alert = #alert{} -> + throw(Alert) + end. + +key_exchange(#state{role = server, key_algorithm = rsa} = State,_) -> + State; +key_exchange(#state{role = server, key_algorithm = Algo, + hashsign_algorithm = HashSignAlgo, + diffie_hellman_params = #'DHParameter'{} = Params, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State0, Connection) + when Algo == dhe_dss; + Algo == dhe_rsa; + Algo == dh_anon -> + DHKeys = public_key:generate_key(Params), + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), + State#state{diffie_hellman_keys = DHKeys}; + +key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State, _) + when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> + State#state{diffie_hellman_keys = Key}; +key_exchange(#state{role = server, key_algorithm = Algo, + hashsign_algorithm = HashSignAlgo, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State0, Connection) + when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa; + Algo == ecdh_anon -> + + ECDHKeys = public_key:generate_key(select_curve(State0)), + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), + State#state{diffie_hellman_keys = ECDHKeys}; + +key_exchange(#state{role = server, key_algorithm = psk, + ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> + State; +key_exchange(#state{role = server, key_algorithm = psk, + ssl_options = #ssl_options{psk_identity = PskIdentityHint}, + hashsign_algorithm = HashSignAlgo, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State0, Connection) -> + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{role = server, key_algorithm = dhe_psk, + ssl_options = #ssl_options{psk_identity = PskIdentityHint}, + hashsign_algorithm = HashSignAlgo, + diffie_hellman_params = #'DHParameter'{} = Params, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State0, Connection) -> + DHKeys = public_key:generate_key(Params), + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, + PskIdentityHint, DHKeys, Params, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), + State#state{diffie_hellman_keys = DHKeys}; + +key_exchange(#state{role = server, key_algorithm = rsa_psk, + ssl_options = #ssl_options{psk_identity = undefined}} = State, _) -> + State; +key_exchange(#state{role = server, key_algorithm = rsa_psk, + ssl_options = #ssl_options{psk_identity = PskIdentityHint}, + hashsign_algorithm = HashSignAlgo, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State0, Connection) -> + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{role = server, key_algorithm = Algo, + ssl_options = #ssl_options{user_lookup_fun = LookupFun}, + hashsign_algorithm = HashSignAlgo, + session = #session{srp_username = Username}, + private_key = PrivateKey, + connection_states = ConnectionStates0, + negotiated_version = Version + } = State0, Connection) + when Algo == srp_dss; + Algo == srp_rsa; + Algo == srp_anon -> + SrpParams = handle_srp_identity(Username, LookupFun), + Keys = case generate_srp_server_keys(SrpParams, 0) of + Alert = #alert{} -> + throw(Alert); + Keys0 = {_,_} -> + Keys0 + end, + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates0, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, + HashSignAlgo, ClientRandom, + ServerRandom, + PrivateKey}), + State = Connection:queue_handshake(Msg, State0), + State#state{srp_params = SrpParams, + srp_keys = Keys}; + +key_exchange(#state{role = client, + key_algorithm = rsa, + public_key_info = PublicKeyInfo, + negotiated_version = Version, + premaster_secret = PremasterSecret} = State0, Connection) -> + Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{role = client, + key_algorithm = Algorithm, + negotiated_version = Version, + diffie_hellman_keys = {DhPubKey, _} + } = State0, Connection) + when Algorithm == dhe_dss; + Algorithm == dhe_rsa; + Algorithm == dh_anon -> + Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{role = client, + key_algorithm = Algorithm, + negotiated_version = Version, + diffie_hellman_keys = Keys} = State0, Connection) + when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; + Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; + Algorithm == ecdh_anon -> + Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{role = client, + ssl_options = SslOpts, + key_algorithm = psk, + negotiated_version = Version} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, Version, + {psk, SslOpts#ssl_options.psk_identity}), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{role = client, + ssl_options = SslOpts, + key_algorithm = dhe_psk, + negotiated_version = Version, + diffie_hellman_keys = {DhPubKey, _}} = State0, Connection) -> + Msg = ssl_handshake:key_exchange(client, Version, + {dhe_psk, + SslOpts#ssl_options.psk_identity, DhPubKey}), + Connection:queue_handshake(Msg, State0); +key_exchange(#state{role = client, + ssl_options = SslOpts, + key_algorithm = rsa_psk, + public_key_info = PublicKeyInfo, + negotiated_version = Version, + premaster_secret = PremasterSecret} + = State0, Connection) -> + Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity, + PremasterSecret, PublicKeyInfo), + Connection:queue_handshake(Msg, State0); + +key_exchange(#state{role = client, + key_algorithm = Algorithm, + negotiated_version = Version, + srp_keys = {ClientPubKey, _}} + = State0, Connection) + when Algorithm == srp_dss; + Algorithm == srp_rsa; + Algorithm == srp_anon -> + Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}), + Connection:queue_handshake(Msg, State0). + +rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) + when Algorithm == ?rsaEncryption; + Algorithm == ?md2WithRSAEncryption; + Algorithm == ?md5WithRSAEncryption; + Algorithm == ?sha1WithRSAEncryption; + Algorithm == ?sha224WithRSAEncryption; + Algorithm == ?sha256WithRSAEncryption; + Algorithm == ?sha384WithRSAEncryption; + Algorithm == ?sha512WithRSAEncryption + -> + ssl_handshake:key_exchange(client, Version, + {premaster_secret, PremasterSecret, + PublicKeyInfo}); +rsa_key_exchange(_, _, _) -> + throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). + +rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, + PublicKeyInfo = {Algorithm, _, _}) + when Algorithm == ?rsaEncryption; + Algorithm == ?md2WithRSAEncryption; + Algorithm == ?md5WithRSAEncryption; + Algorithm == ?sha1WithRSAEncryption; + Algorithm == ?sha224WithRSAEncryption; + Algorithm == ?sha256WithRSAEncryption; + Algorithm == ?sha384WithRSAEncryption; + Algorithm == ?sha512WithRSAEncryption + -> + ssl_handshake:key_exchange(client, Version, + {psk_premaster_secret, PskIdentity, PremasterSecret, + PublicKeyInfo}); +rsa_psk_key_exchange(_, _, _, _) -> + throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE, pub_key_is_not_rsa)). + +request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer, + signature_algs = SupportedHashSigns}, + connection_states = ConnectionStates0, + cert_db = CertDbHandle, + cert_db_ref = CertDbRef, + negotiated_version = Version} = State0, Connection) -> + #connection_state{security_parameters = + #security_parameters{cipher_suite = CipherSuite}} = + ssl_record:pending_connection_state(ConnectionStates0, read), + HashSigns = ssl_handshake:available_signature_algs(SupportedHashSigns, Version, [Version]), + Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, + HashSigns, Version), + State = Connection:queue_handshake(Msg, State0), + State#state{client_certificate_requested = true}; + +request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} = + State, _) -> + State. + +calculate_master_secret(PremasterSecret, + #state{negotiated_version = Version, + connection_states = ConnectionStates0, + session = Session0} = State0, Connection, + _Current, Next) -> + case ssl_handshake:master_secret(record_cb(Connection), Version, PremasterSecret, + ConnectionStates0, server) of + {MasterSecret, ConnectionStates} -> + Session = Session0#session{master_secret = MasterSecret}, + State1 = State0#state{connection_states = ConnectionStates, + session = Session}, + {Record, State} = Connection:next_record(State1), + Connection:next_event(Next, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, certify, State0) + end. + +finalize_handshake(State0, StateName, Connection) -> + #state{connection_states = ConnectionStates0} = + State1 = cipher_protocol(State0, Connection), + + ConnectionStates = + ssl_record:activate_pending_connection_state(ConnectionStates0, + write), + + State2 = State1#state{connection_states = ConnectionStates}, + State = next_protocol(State2, Connection), + finished(State, StateName, Connection). + +next_protocol(#state{role = server} = State, _) -> + State; +next_protocol(#state{negotiated_protocol = undefined} = State, _) -> + State; +next_protocol(#state{expecting_next_protocol_negotiation = false} = State, _) -> + State; +next_protocol(#state{negotiated_protocol = NextProtocol} = State0, Connection) -> + NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol), + Connection:queue_handshake(NextProtocolMessage, State0). + +cipher_protocol(State, Connection) -> + Connection:queue_change_cipher(#change_cipher_spec{}, State). + +finished(#state{role = Role, negotiated_version = Version, + session = Session, + connection_states = ConnectionStates0, + tls_handshake_history = Handshake0} = State0, StateName, Connection) -> + MasterSecret = Session#session.master_secret, + Finished = ssl_handshake:finished(Version, Role, + get_current_prf(ConnectionStates0, write), + MasterSecret, Handshake0), + ConnectionStates = save_verify_data(Role, Finished, ConnectionStates0, StateName), + Connection:send_handshake(Finished, State0#state{connection_states = + ConnectionStates}). + +save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> + ssl_record:set_client_verify_data(current_write, Data, ConnectionStates); +save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> + ssl_record:set_server_verify_data(current_both, Data, ConnectionStates); +save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> + ssl_record:set_client_verify_data(current_both, Data, ConnectionStates); +save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> + ssl_record:set_server_verify_data(current_write, Data, ConnectionStates). + +calculate_secret(#server_dh_params{dh_p = Prime, dh_g = Base, + dh_y = ServerPublicDhKey} = Params, + State, Connection) -> + Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), + PremasterSecret = + ssl_handshake:premaster_secret(ServerPublicDhKey, PrivateDhKey, Params), + calculate_master_secret(PremasterSecret, + State#state{diffie_hellman_keys = Keys}, + Connection, certify, certify); + +calculate_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, + State, Connection) -> + ECDHKeys = public_key:generate_key(ECCurve), + PremasterSecret = + ssl_handshake:premaster_secret(#'ECPoint'{point = ECServerPubKey}, ECDHKeys), + calculate_master_secret(PremasterSecret, + State#state{diffie_hellman_keys = ECDHKeys}, + Connection, certify, certify); + +calculate_secret(#server_psk_params{ + hint = IdentityHint}, + State0, Connection) -> + %% store for later use + {Record, State} = Connection:next_record(State0#state{psk_identity = IdentityHint}), + Connection:next_event(certify, Record, State); + +calculate_secret(#server_dhe_psk_params{ + dh_params = #server_dh_params{dh_p = Prime, dh_g = Base}} = ServerKey, + #state{ssl_options = #ssl_options{user_lookup_fun = PSKLookup}} = + State, Connection) -> + Keys = {_, PrivateDhKey} = + crypto:generate_key(dh, [Prime, Base]), + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, PrivateDhKey, PSKLookup), + calculate_master_secret(PremasterSecret, State#state{diffie_hellman_keys = Keys}, + Connection, certify, certify); + +calculate_secret(#server_srp_params{srp_n = Prime, srp_g = Generator} = ServerKey, + #state{ssl_options = #ssl_options{srp_identity = SRPId}} = State, + Connection) -> + Keys = generate_srp_client_keys(Generator, Prime, 0), + PremasterSecret = ssl_handshake:premaster_secret(ServerKey, Keys, SRPId), + calculate_master_secret(PremasterSecret, State#state{srp_keys = Keys}, Connection, + certify, certify). + +master_secret(#alert{} = Alert, _) -> + Alert; +master_secret(PremasterSecret, #state{session = Session, + negotiated_version = Version, role = Role, + connection_states = ConnectionStates0} = State) -> + case ssl_handshake:master_secret(tls_record, Version, PremasterSecret, + ConnectionStates0, Role) of + {MasterSecret, ConnectionStates} -> + State#state{ + session = + Session#session{master_secret = MasterSecret}, + connection_states = ConnectionStates}; + #alert{} = Alert -> + Alert + end. + +generate_srp_server_keys(_SrpParams, 10) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); +generate_srp_server_keys(SrpParams = + #srp_user{generator = Generator, prime = Prime, + verifier = Verifier}, N) -> + case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of + error -> + generate_srp_server_keys(SrpParams, N+1); + Keys -> + Keys + end. + +generate_srp_client_keys(_Generator, _Prime, 10) -> + ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); +generate_srp_client_keys(Generator, Prime, N) -> + + case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of + error -> + generate_srp_client_keys(Generator, Prime, N+1); + Keys -> + Keys + end. + +handle_srp_identity(Username, {Fun, UserState}) -> + case Fun(srp, Username, UserState) of + {ok, {SRPParams, Salt, DerivedKey}} + when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> + {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), + Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), + #srp_user{generator = Generator, prime = Prime, + salt = Salt, verifier = Verifier}; + #alert{} = Alert -> + throw(Alert); + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + + +cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State0, + Connection) -> + ConnectionStates = ssl_record:set_server_verify_data(current_both, Data, + ConnectionStates0), + {Record, State} = prepare_connection(State0#state{session = Session, + connection_states = ConnectionStates}, + Connection), + Connection:next_event(connection, Record, State); +cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State0, + Connection) -> + ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, + ConnectionStates0), + State1 = + finalize_handshake(State0#state{connection_states = ConnectionStates1, + session = Session}, cipher, Connection), + {Record, State} = prepare_connection(State1, Connection), + Connection:next_event(connection, Record, State). + +select_curve(#state{client_ecc = {[Curve|_], _}}) -> + {namedCurve, Curve}; +select_curve(_) -> + {namedCurve, ?secp256r1}. + +is_anonymous(Algo) when Algo == dh_anon; + Algo == ecdh_anon; + Algo == psk; + Algo == dhe_psk; + Algo == rsa_psk; + Algo == srp_anon -> + true; +is_anonymous(_) -> + false. + +get_current_prf(CStates, Direction) -> + CS = ssl_record:current_connection_state(CStates, Direction), + CS#connection_state.security_parameters#security_parameters.prf_algorithm. +get_pending_prf(CStates, Direction) -> + CS = ssl_record:pending_connection_state(CStates, Direction), + CS#connection_state.security_parameters#security_parameters.prf_algorithm. + +opposite_role(client) -> + server; +opposite_role(server) -> + client. + +record_cb(tls_connection) -> + tls_record; +record_cb(dtls_connection) -> + dtls_record. + +call(FsmPid, Event) -> + try gen_statem:call(FsmPid, Event) + catch + exit:{noproc, _} -> + {error, closed}; + exit:{normal, _} -> + {error, closed}; + exit:{{shutdown, _},_} -> + {error, closed} + end. + +get_socket_opts(_,_,[], _, Acc) -> + {ok, Acc}; +get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) -> + get_socket_opts(Transport, Socket, Tags, SockOpts, + [{mode, SockOpts#socket_options.mode} | Acc]); +get_socket_opts(Transport, Socket, [packet | Tags], SockOpts, Acc) -> + case SockOpts#socket_options.packet of + {Type, headers} -> + get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); + Type -> + get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) + end; +get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) -> + get_socket_opts(Transport, Socket, Tags, SockOpts, + [{header, SockOpts#socket_options.header} | Acc]); +get_socket_opts(Transport, Socket, [active | Tags], SockOpts, Acc) -> + get_socket_opts(Transport, Socket, Tags, SockOpts, + [{active, SockOpts#socket_options.active} | Acc]); +get_socket_opts(Transport, Socket, [Tag | Tags], SockOpts, Acc) -> + try ssl_socket:getopts(Transport, Socket, [Tag]) of + {ok, [Opt]} -> + get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]); + {error, Error} -> + {error, {options, {socket_options, Tag, Error}}} + catch + %% So that inet behavior does not crash our process + _:Error -> {error, {options, {socket_options, Tag, Error}}} + end; +get_socket_opts(_, _,Opts, _,_) -> + {error, {options, {socket_options, Opts, function_clause}}}. + +set_socket_opts(_,_, [], SockOpts, []) -> + {ok, SockOpts}; +set_socket_opts(Transport, Socket, [], SockOpts, Other) -> + %% Set non emulated options + try ssl_socket:setopts(Transport, Socket, Other) of + ok -> + {ok, SockOpts}; + {error, InetError} -> + {{error, {options, {socket_options, Other, InetError}}}, SockOpts} + catch + _:Error -> + %% So that inet behavior does not crash our process + {{error, {options, {socket_options, Other, Error}}}, SockOpts} + end; + +set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) + when Mode == list; Mode == binary -> + set_socket_opts(Transport, Socket, Opts, + SockOpts#socket_options{mode = Mode}, Other); +set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) + when Packet == raw; + Packet == 0; + Packet == 1; + Packet == 2; + Packet == 4; + Packet == asn1; + Packet == cdr; + Packet == sunrm; + Packet == fcgi; + Packet == tpkt; + Packet == line; + Packet == http; + Packet == httph; + Packet == http_bin; + Packet == httph_bin -> + set_socket_opts(Transport, Socket, Opts, + SockOpts#socket_options{packet = Packet}, Other); +set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) + when is_integer(Header) -> + set_socket_opts(Transport, Socket, Opts, + SockOpts#socket_options{header = Header}, Other); +set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) -> + {{error,{options, {socket_options, Opt}}}, SockOpts}; +set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) + when Active == once; + Active == true; + Active == false -> + set_socket_opts(Transport, Socket, Opts, + SockOpts#socket_options{active = Active}, Other); +set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) -> + {{error, {options, {socket_options, Opt}} }, SockOpts}; +set_socket_opts(Transport, Socket, [Opt | Opts], SockOpts, Other) -> + set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]). + +start_or_recv_cancel_timer(infinity, _RecvFrom) -> + undefined; +start_or_recv_cancel_timer(Timeout, RecvFrom) -> + erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). + +hibernate_after(connection = StateName, + #state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}} = State, + Actions) -> + {next_state, StateName, State, [{timeout, HibernateAfter, hibernate} | Actions]}; +hibernate_after(StateName, State, Actions) -> + {next_state, StateName, State, Actions}. + +terminate_alert(normal, Version, ConnectionStates) -> + ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + Version, ConnectionStates); +terminate_alert({Reason, _}, Version, ConnectionStates) when Reason == close; + Reason == shutdown -> + ssl_alert:encode(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), + Version, ConnectionStates); + +terminate_alert(_, Version, ConnectionStates) -> + {BinAlert, _} = ssl_alert:encode(?ALERT_REC(?FATAL, ?INTERNAL_ERROR), + Version, ConnectionStates), + BinAlert. + +handle_trusted_certs_db(#state{ssl_options = + #ssl_options{cacertfile = <<>>, cacerts = []}}) -> + %% No trusted certs specified + ok; +handle_trusted_certs_db(#state{cert_db_ref = Ref, + cert_db = CertDb, + ssl_options = #ssl_options{cacertfile = <<>>}}) when CertDb =/= undefined -> + %% Certs provided as DER directly can not be shared + %% with other connections and it is safe to delete them when the connection ends. + ssl_pkix_db:remove_trusted_certs(Ref, CertDb); +handle_trusted_certs_db(#state{file_ref_db = undefined}) -> + %% Something went wrong early (typically cacertfile does not + %% exist) so there is nothing to handle + ok; +handle_trusted_certs_db(#state{cert_db_ref = Ref, + file_ref_db = RefDb, + ssl_options = #ssl_options{cacertfile = File}}) -> + case ssl_pkix_db:ref_count(Ref, RefDb, -1) of + 0 -> + ssl_manager:clean_cert_db(Ref, File); + _ -> + ok + end. + +prepare_connection(#state{renegotiation = Renegotiate, + start_or_recv_from = RecvFrom} = State0, Connection) + when Renegotiate =/= {false, first}, + RecvFrom =/= undefined -> + State1 = Connection:reinit_handshake_data(State0), + {Record, State} = Connection:next_record(State1), + {Record, ack_connection(State)}; +prepare_connection(State0, Connection) -> + State = Connection:reinit_handshake_data(State0), + {no_record, ack_connection(State)}. + +ack_connection(#state{renegotiation = {true, Initiater}} = State) + when Initiater == internal; + Initiater == peer -> + State#state{renegotiation = undefined}; +ack_connection(#state{renegotiation = {true, From}} = State) -> + gen_statem:reply(From, ok), + State#state{renegotiation = undefined}; +ack_connection(#state{renegotiation = {false, first}, + start_or_recv_from = StartFrom, + timer = Timer} = State) when StartFrom =/= undefined -> + gen_statem:reply(StartFrom, connected), + cancel_timer(Timer), + State#state{renegotiation = undefined, + start_or_recv_from = undefined, timer = undefined}; +ack_connection(State) -> + State. + +cancel_timer(undefined) -> + ok; +cancel_timer(Timer) -> + erlang:cancel_timer(Timer), + ok. + +register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> + Session = Session0#session{is_resumable = true}, + ssl_manager:register_session(Host, Port, Session), + Session; +register_session(server, _, Port, #session{is_resumable = new} = Session0) -> + Session = Session0#session{is_resumable = true}, + ssl_manager:register_session(Port, Session), + Session; +register_session(_, _, _, Session) -> + Session. %% Already registered + +handle_new_session(NewId, CipherSuite, Compression, + #state{session = Session0, + protocol_cb = Connection} = State0) -> + Session = Session0#session{session_id = NewId, + cipher_suite = CipherSuite, + compression_method = Compression}, + {Record, State} = Connection:next_record(State0#state{session = Session}), + Connection:next_event(certify, Record, State). + +handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, + negotiated_version = Version, + host = Host, port = Port, + protocol_cb = Connection, + session_cache = Cache, + session_cache_cb = CacheCb} = State0) -> + Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), + case ssl_handshake:master_secret(tls_record, Version, Session, + ConnectionStates0, client) of + {_, ConnectionStates} -> + {Record, State} = + Connection:next_record(State0#state{ + connection_states = ConnectionStates, + session = Session}), + Connection:next_event(abbreviated, Record, State); + #alert{} = Alert -> + Connection:handle_own_alert(Alert, Version, hello, State0) + end. + +make_premaster_secret({MajVer, MinVer}, rsa) -> + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; +make_premaster_secret(_, _) -> + undefined. + +negotiated_hashsign(undefined, KexAlg, PubKeyInfo, Version) -> + %% Not negotiated choose default + case is_anonymous(KexAlg) of + true -> + {null, anon}; + false -> + {PubAlg, _, _} = PubKeyInfo, + ssl_handshake:select_hashsign_algs(undefined, PubAlg, Version) + end; +negotiated_hashsign(HashSign = {_, _}, _, _, _) -> + HashSign. + +ssl_options_list(SslOptions) -> + Fileds = record_info(fields, ssl_options), + Values = tl(tuple_to_list(SslOptions)), + ssl_options_list(Fileds, Values, []). + +ssl_options_list([],[], Acc) -> + lists:reverse(Acc); +%% Skip internal options, only return user options +ssl_options_list([protocol | Keys], [_ | Values], Acc) -> + ssl_options_list(Keys, Values, Acc); +ssl_options_list([erl_dist | Keys], [_ | Values], Acc) -> + ssl_options_list(Keys, Values, Acc); +ssl_options_list([renegotiate_at | Keys], [_ | Values], Acc) -> + ssl_options_list(Keys, Values, Acc); +ssl_options_list([ciphers = Key | Keys], [Value | Values], Acc) -> + ssl_options_list(Keys, Values, + [{Key, lists:map( + fun(Suite) -> + ssl_cipher:erl_suite_definition(Suite) + end, Value)} + | Acc]); +ssl_options_list([Key | Keys], [Value | Values], Acc) -> + ssl_options_list(Keys, Values, [{Key, Value} | Acc]). + +handle_active_option(false, connection = StateName, To, Reply, State) -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + +handle_active_option(_, connection = StateName0, To, Reply, #state{protocol_cb = Connection, + user_data_buffer = <<>>} = State0) -> + %% Need data, set active once + {Record, State1} = Connection:next_record_if_active(State0), + %% Note: Renogotiation may cause StateName0 =/= StateName + case Connection:next_event(StateName0, Record, State1) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, Reason, State} -> + {stop, Reason, State} + end; +handle_active_option(_, StateName, To, Reply, #state{user_data_buffer = <<>>} = State) -> + %% Active once already set + {next_state, StateName, State, [{reply, To, Reply}]}; + +%% user_data_buffer =/= <<>> +handle_active_option(_, StateName0, To, Reply, #state{protocol_cb = Connection} = State0) -> + case Connection:read_application_data(<<>>, State0) of + {stop, Reason, State} -> + {stop, Reason, State}; + {Record, State1} -> + %% Note: Renogotiation may cause StateName0 =/= StateName + case Connection:next_event(StateName0, Record, State1) of + {next_state, StateName, State} -> + hibernate_after(StateName, State, [{reply, To, Reply}]); + {next_state, StateName, State, Actions} -> + hibernate_after(StateName, State, [{reply, To, Reply} | Actions]); + {stop, _, _} = Stop -> + Stop + end + end. diff --git a/lib/ssl/src/ssl_connection.hrl b/lib/ssl/src/ssl_connection.hrl new file mode 100644 index 0000000000..4b54943ddf --- /dev/null +++ b/lib/ssl/src/ssl_connection.hrl @@ -0,0 +1,100 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: SSL/TLS specific state +%%---------------------------------------------------------------------- + +-ifndef(ssl_connection). +-define(ssl_connection, true). + +-include("ssl_internal.hrl"). +-include("ssl_record.hrl"). +-include("ssl_handshake.hrl"). +-include("ssl_srp.hrl"). +-include("ssl_cipher.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-record(state, { + role :: client | server, + user_application :: {Monitor::reference(), User::pid()}, + transport_cb :: atom(), % callback module + protocol_cb :: tls_connection | dtls_connection, + data_tag :: atom(), % ex tcp. + close_tag :: atom(), % ex tcp_closed + error_tag :: atom(), % ex tcp_error + host :: string() | inet:ip_address(), + port :: integer(), + socket :: port(), + ssl_options :: #ssl_options{}, + socket_options :: #socket_options{}, + connection_states :: #connection_states{} | secret_printout(), + protocol_buffers :: term() | secret_printout() , %% #protocol_buffers{} from tls_record.hrl or dtls_recor.hrl + tls_handshake_history :: ssl_handshake:ssl_handshake_history() | secret_printout() + | 'undefined', + cert_db :: reference() | 'undefined', + session :: #session{} | secret_printout(), + session_cache :: db_handle(), + session_cache_cb :: atom(), + crl_db :: term(), + negotiated_version :: ssl_record:ssl_version() | 'undefined', + client_certificate_requested = false :: boolean(), + key_algorithm :: ssl_cipher:key_algo(), + hashsign_algorithm = {undefined, undefined}, + cert_hashsign_algorithm, + public_key_info :: ssl_handshake:public_key_info() | 'undefined', + private_key :: public_key:private_key() | secret_printout() | 'undefined', + diffie_hellman_params:: #'DHParameter'{} | undefined | secret_printout(), + diffie_hellman_keys :: {PublicKey :: binary(), PrivateKey :: binary()} | #'ECPrivateKey'{} | undefined | secret_printout(), + psk_identity :: binary() | 'undefined', % server psk identity hint + srp_params :: #srp_user{} | secret_printout() | 'undefined', + srp_keys ::{PublicKey :: binary(), PrivateKey :: binary()} | secret_printout() | 'undefined', + premaster_secret :: binary() | secret_printout() | 'undefined', + file_ref_db :: db_handle(), + cert_db_ref :: certdb_ref() | 'undefined', + bytes_to_read :: undefined | integer(), %% bytes to read in passive mode + user_data_buffer :: undefined | binary() | secret_printout(), + renegotiation :: undefined | {boolean(), From::term() | internal | peer}, + start_or_recv_from :: term(), + timer :: undefined | reference(), % start_or_recive_timer + %%send_queue :: queue:queue(), + terminated = false ::boolean(), + allow_renegotiate = true ::boolean(), + expecting_next_protocol_negotiation = false ::boolean(), + expecting_finished = false ::boolean(), + negotiated_protocol = undefined :: undefined | binary(), + client_ecc, % {Curves, PointFmt} + tracker :: pid() | 'undefined', %% Tracker process for listen socket + sni_hostname = undefined, + downgrade, + flight_buffer = [] :: list() %% Buffer of TLS/DTLS records, used during the TLS handshake + %% to when possible pack more than on TLS record into the + %% underlaying packet format. Introduced by DTLS - RFC 4347. + %% The mecahnism is also usefull in TLS although we do not + %% need to worry about packet loss in TLS. + }). + +-define(DEFAULT_DIFFIE_HELLMAN_PARAMS, + #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, + base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). +-define(WAIT_TO_ALLOW_RENEGOTIATION, 12000). + +-endif. % -ifdef(ssl_connection). diff --git a/lib/ssl/src/ssl_crl.erl b/lib/ssl/src/ssl_crl.erl new file mode 100644 index 0000000000..d9f21e04ac --- /dev/null +++ b/lib/ssl/src/ssl_crl.erl @@ -0,0 +1,79 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +%---------------------------------------------------------------------- +%% Purpose: CRL handling +%%---------------------------------------------------------------------- + +-module(ssl_crl). + +-include("ssl_alert.hrl"). +-include("ssl_internal.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-export([trusted_cert_and_path/3]). + +trusted_cert_and_path(CRL, {SerialNumber, Issuer},{Db, DbRef} = DbHandle) -> + case ssl_pkix_db:lookup_trusted_cert(Db, DbRef, SerialNumber, Issuer) of + undefined -> + trusted_cert_and_path(CRL, issuer_not_found, DbHandle); + {ok, {_, OtpCert}} -> + {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), + {ok, Root, lists:reverse(Chain)} + end; + +trusted_cert_and_path(CRL, issuer_not_found, {Db, DbRef} = DbHandle) -> + case find_issuer(CRL, DbHandle) of + {ok, OtpCert} -> + {ok, Root, Chain} = ssl_certificate:certificate_chain(OtpCert, Db, DbRef), + {ok, Root, lists:reverse(Chain)}; + {error, issuer_not_found} -> + {ok, unknown_crl_ca, []} + end. + +find_issuer(CRL, {Db,_}) -> + Issuer = public_key:pkix_normalize_name(public_key:pkix_crl_issuer(CRL)), + IsIssuerFun = + fun({_Key, {_Der,ErlCertCandidate}}, Acc) -> + verify_crl_issuer(CRL, ErlCertCandidate, Issuer, Acc); + (_, Acc) -> + Acc + end, + + try ssl_pkix_db:foldl(IsIssuerFun, issuer_not_found, Db) of + issuer_not_found -> + {error, issuer_not_found} + catch + {ok, _} = Result -> + Result + end. + +verify_crl_issuer(CRL, ErlCertCandidate, Issuer, NotIssuer) -> + TBSCert = ErlCertCandidate#'OTPCertificate'.tbsCertificate, + case public_key:pkix_normalize_name(TBSCert#'OTPTBSCertificate'.subject) of + Issuer -> + case public_key:pkix_crl_verify(CRL, ErlCertCandidate) of + true -> + throw({ok, ErlCertCandidate}); + false -> + NotIssuer + end; + _ -> + NotIssuer + end. diff --git a/lib/ssl/src/ssl_crl_cache.erl b/lib/ssl/src/ssl_crl_cache.erl new file mode 100644 index 0000000000..647e0465fe --- /dev/null +++ b/lib/ssl/src/ssl_crl_cache.erl @@ -0,0 +1,181 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +%---------------------------------------------------------------------- +%% Purpose: Simple default CRL cache +%%---------------------------------------------------------------------- + +-module(ssl_crl_cache). + +-include("ssl_internal.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-behaviour(ssl_crl_cache_api). + +-export([lookup/3, select/2, fresh_crl/2]). +-export([insert/1, insert/2, delete/1]). + +%%==================================================================== +%% Cache callback API +%%==================================================================== + +lookup(#'DistributionPoint'{distributionPoint = {fullName, Names}}, + _Issuer, + CRLDbInfo) -> + get_crls(Names, CRLDbInfo); +lookup(_,_,_) -> + not_available. + +select(Issuer, {{_Cache, Mapping},_}) -> + case ssl_pkix_db:lookup(Issuer, Mapping) of + undefined -> + []; + CRLs -> + CRLs + end. + +fresh_crl(#'DistributionPoint'{distributionPoint = {fullName, Names}}, CRL) -> + case get_crls(Names, undefined) of + not_available -> + CRL; + [NewCRL] -> + NewCRL + end. + +%%==================================================================== +%% API +%%==================================================================== + +insert(CRLs) -> + insert(?NO_DIST_POINT, CRLs). + +insert(URI, {file, File}) when is_list(URI) -> + case file:read_file(File) of + {ok, PemBin} -> + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + do_insert(URI, CRLs); + Error -> + Error + end; +insert(URI, {der, CRLs}) -> + do_insert(URI, CRLs). + +delete({file, File}) -> + case file:read_file(File) of + {ok, PemBin} -> + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + ssl_manager:delete_crls({?NO_DIST_POINT, CRLs}); + Error -> + Error + end; +delete({der, CRLs}) -> + ssl_manager:delete_crls({?NO_DIST_POINT, CRLs}); + +delete(URI) -> + case http_uri:parse(URI) of + {ok, {http, _, _ , _, Path,_}} -> + ssl_manager:delete_crls(string:strip(Path, left, $/)); + _ -> + {error, {only_http_distribution_points_supported, URI}} + end. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +do_insert(URI, CRLs) -> + case http_uri:parse(URI) of + {ok, {http, _, _ , _, Path,_}} -> + ssl_manager:insert_crls(string:strip(Path, left, $/), CRLs); + _ -> + {error, {only_http_distribution_points_supported, URI}} + end. + +get_crls([], _) -> + not_available; +get_crls([{uniformResourceIdentifier, "http"++_ = URL} | Rest], + CRLDbInfo) -> + case cache_lookup(URL, CRLDbInfo) of + [] -> + handle_http(URL, Rest, CRLDbInfo); + CRLs -> + CRLs + end; +get_crls([ _| Rest], CRLDbInfo) -> + %% unsupported CRL location + get_crls(Rest, CRLDbInfo). + +http_lookup(URL, Rest, CRLDbInfo, Timeout) -> + case application:ensure_started(inets) of + ok -> + http_get(URL, Rest, CRLDbInfo, Timeout); + _ -> + get_crls(Rest, CRLDbInfo) + end. + +http_get(URL, Rest, CRLDbInfo, Timeout) -> + case httpc:request(get, {URL, [{"connection", "close"}]}, + [{timeout, Timeout}], [{body_format, binary}]) of + {ok, {_Status, _Headers, Body}} -> + case Body of + <<"-----BEGIN", _/binary>> -> + Pem = public_key:pem_decode(Body), + lists:filtermap(fun({'CertificateList', + CRL, not_encrypted}) -> + {true, CRL}; + (_) -> + false + end, Pem); + _ -> + try public_key:der_decode('CertificateList', Body) of + _ -> + [Body] + catch + _:_ -> + get_crls(Rest, CRLDbInfo) + end + end; + {error, _Reason} -> + get_crls(Rest, CRLDbInfo) + end. + +cache_lookup(_, undefined) -> + []; +cache_lookup(URL, {{Cache, _}, _}) -> + {ok, {_, _, _ , _, Path,_}} = http_uri:parse(URL), + case ssl_pkix_db:lookup(string:strip(Path, left, $/), Cache) of + undefined -> + []; + CRLs -> + CRLs + end. + +handle_http(URI, Rest, {_, [{http, Timeout}]} = CRLDbInfo) -> + CRLs = http_lookup(URI, Rest, CRLDbInfo, Timeout), + %% Uncomment to improve performance, but need to + %% implement cache limit and or cleaning to prevent + %% DoS attack possibilities + %%insert(URI, {der, CRLs}), + CRLs; +handle_http(_, Rest, CRLDbInfo) -> + get_crls(Rest, CRLDbInfo). + diff --git a/lib/ssl/src/ssl_crl_cache_api.erl b/lib/ssl/src/ssl_crl_cache_api.erl new file mode 100644 index 0000000000..c3a57e2f49 --- /dev/null +++ b/lib/ssl/src/ssl_crl_cache_api.erl @@ -0,0 +1,32 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_crl_cache_api). + +-include_lib("public_key/include/public_key.hrl"). + +-type db_handle() :: term(). +-type issuer_name() :: {rdnSequence, [#'AttributeTypeAndValue'{}]}. + +-callback lookup(#'DistributionPoint'{}, issuer_name(), db_handle()) -> not_available | [public_key:der_encoded()]. +-callback select(issuer_name(), db_handle()) -> [public_key:der_encoded()]. +-callback fresh_crl(#'DistributionPoint'{}, public_key:der_encoded()) -> public_key:der_encoded(). diff --git a/lib/ssl/src/ssl_crl_hash_dir.erl b/lib/ssl/src/ssl_crl_hash_dir.erl new file mode 100644 index 0000000000..bb62737232 --- /dev/null +++ b/lib/ssl/src/ssl_crl_hash_dir.erl @@ -0,0 +1,106 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2016-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +-module(ssl_crl_hash_dir). + +-include_lib("public_key/include/public_key.hrl"). + +-behaviour(ssl_crl_cache_api). + +-export([lookup/3, select/2, fresh_crl/2]). + +lookup(#'DistributionPoint'{cRLIssuer = CRLIssuer} = DP, CertIssuer, CRLDbInfo) -> + Issuer = + case CRLIssuer of + asn1_NOVALUE -> + %% If the distribution point extension doesn't + %% indicate a CRL issuer, use the certificate issuer. + CertIssuer; + _ -> + CRLIssuer + end, + %% Find all CRLs for this issuer, and return those that match the + %% given distribution point. + AllCRLs = select(Issuer, CRLDbInfo), + lists:filter(fun(DER) -> + public_key:pkix_match_dist_point(DER, DP) + end, AllCRLs). + +fresh_crl(#'DistributionPoint'{}, CurrentCRL) -> + CurrentCRL. + +select(Issuer, {_DbHandle, [{dir, Dir}]}) -> + case find_crls(Issuer, Dir) of + [_|_] = DERs -> + DERs; + [] -> + %% That's okay, just report that we didn't find any CRL. + %% If the crl_check setting is best_effort, ssl_handshake + %% is happy with that, but if it's true, this is an error. + []; + {error, Error} -> + error_logger:error_report( + [{cannot_find_crl, Error}, + {dir, Dir}, + {module, ?MODULE}, + {line, ?LINE}]), + [] + end. + +find_crls(Issuer, Dir) -> + case filelib:is_dir(Dir) of + true -> + Hash = public_key:short_name_hash(Issuer), + find_crls(Issuer, Hash, Dir, 0, []); + false -> + {error, not_a_directory} + end. + +find_crls(Issuer, Hash, Dir, N, Acc) -> + Filename = filename:join(Dir, Hash ++ ".r" ++ integer_to_list(N)), + case file:read_file(Filename) of + {error, enoent} -> + Acc; + {ok, Bin} -> + try maybe_parse_pem(Bin) of + DER when is_binary(DER) -> + %% Found one file. Let's see if there are more. + find_crls(Issuer, Hash, Dir, N + 1, [DER] ++ Acc) + catch + error:Error -> + %% Something is wrong with the file. Report + %% it, and try the next one. + error_logger:error_report( + [{crl_parse_error, Error}, + {filename, Filename}, + {module, ?MODULE}, + {line, ?LINE}]), + find_crls(Issuer, Hash, Dir, N + 1, Acc) + end + end. + +maybe_parse_pem(<<"-----BEGIN", _/binary>> = PEM) -> + %% It's a PEM encoded file. Need to extract the DER + %% encoded data. + [{'CertificateList', DER, not_encrypted}] = public_key:pem_decode(PEM), + DER; +maybe_parse_pem(DER) when is_binary(DER) -> + %% Let's assume it's DER-encoded. + DER. + diff --git a/lib/ssl/src/ssl_dist_sup.erl b/lib/ssl/src/ssl_dist_sup.erl index 9d9afb7707..a6eb1be1f6 100644 --- a/lib/ssl/src/ssl_dist_sup.erl +++ b/lib/ssl/src/ssl_dist_sup.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2011. All Rights Reserved. +%% Copyright Ericsson AB 2011-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -45,9 +46,11 @@ start_link() -> init([]) -> SessionCertManager = session_and_cert_manager_child_spec(), ConnetionManager = connection_manager_child_spec(), + ListenOptionsTracker = listen_options_tracker_child_spec(), ProxyServer = proxy_server_child_spec(), - {ok, {{one_for_all, 10, 3600}, [SessionCertManager, ConnetionManager, + {ok, {{one_for_all, 10, 3600}, [SessionCertManager, ConnetionManager, + ListenOptionsTracker, ProxyServer]}}. %%-------------------------------------------------------------------- @@ -65,10 +68,10 @@ session_and_cert_manager_child_spec() -> connection_manager_child_spec() -> Name = ssl_connection_dist, - StartFunc = {ssl_connection_sup, start_link_dist, []}, + StartFunc = {tls_connection_sup, start_link_dist, []}, Restart = permanent, - Shutdown = 4000, - Modules = [ssl_connection], + Shutdown = infinity, + Modules = [tls_connection_sup], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. @@ -81,3 +84,11 @@ proxy_server_child_spec() -> Type = worker, {Name, StartFunc, Restart, Shutdown, Type, Modules}. +listen_options_tracker_child_spec() -> + Name = ssl_socket_dist, + StartFunc = {ssl_listen_tracker_sup, start_link_dist, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [ssl_socket], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl new file mode 100644 index 0000000000..9c3fe9d73b --- /dev/null +++ b/lib/ssl/src/ssl_handshake.erl @@ -0,0 +1,2204 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +%---------------------------------------------------------------------- +%% Purpose: Help funtions for handling the SSL-handshake protocol (common +%% to SSL/TLS and DTLS +%%---------------------------------------------------------------------- + +-module(ssl_handshake). + +-include("ssl_handshake.hrl"). +-include("ssl_record.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_alert.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_srp.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +-export_type([ssl_handshake/0, ssl_handshake_history/0, + public_key_info/0, oid/0]). + +-type oid() :: tuple(). +-type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term(). +-type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}. +-type ssl_handshake_history() :: {[binary()], [binary()]}. + +-type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} | + #client_key_exchange{} | #finished{} | #certificate_verify{} | + #hello_request{} | #next_protocol{}. + +%% Handshake messages +-export([hello_request/0, server_hello/4, server_hello_done/0, + certificate/4, certificate_request/5, key_exchange/3, + finished/5, next_protocol/1]). + +%% Handle handshake messages +-export([certify/10, client_certificate_verify/6, certificate_verify/6, verify_signature/5, + master_secret/5, server_key_exchange_hash/2, verify_connection/6, + init_handshake_history/0, update_handshake_history/2, verify_server_key/5 + ]). + +%% Encode/Decode +-export([encode_handshake/2, encode_hello_extensions/1, + encode_client_protocol_negotiation/2, encode_protocols_advertised_on_server/1, + decode_handshake/3, decode_hello_extensions/1, + decode_server_key/3, decode_client_key/3, + decode_suites/2 + ]). + +%% Cipher suites handling +-export([available_suites/2, available_signature_algs/3, cipher_suites/2, + select_session/11, supported_ecc/1, available_signature_algs/4]). + +%% Extensions handling +-export([client_hello_extensions/6, + handle_client_hello_extensions/9, %% Returns server hello extensions + handle_server_hello_extensions/9, select_curve/2 + ]). + +%% MISC +-export([select_version/3, prf/6, select_hashsign/5, + select_hashsign_algs/3, + premaster_secret/2, premaster_secret/3, premaster_secret/4]). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%% ---------- Create handshake messages ---------- + +%%-------------------------------------------------------------------- +-spec hello_request() -> #hello_request{}. +%% +%% Description: Creates a hello request message sent by server to +%% trigger renegotiation. +%%-------------------------------------------------------------------- +hello_request() -> + #hello_request{}. + +%%-------------------------------------------------------------------- +-spec server_hello(#session{}, ssl_record:ssl_version(), #connection_states{}, + #hello_extensions{}) -> #server_hello{}. +%% +%% Description: Creates a server hello message. +%%-------------------------------------------------------------------- +server_hello(SessionId, Version, ConnectionStates, Extensions) -> + Pending = ssl_record:pending_connection_state(ConnectionStates, read), + SecParams = Pending#connection_state.security_parameters, + + #server_hello{server_version = Version, + cipher_suite = SecParams#security_parameters.cipher_suite, + compression_method = + SecParams#security_parameters.compression_algorithm, + random = SecParams#security_parameters.server_random, + session_id = SessionId, + extensions = Extensions + }. + +%%-------------------------------------------------------------------- +-spec server_hello_done() -> #server_hello_done{}. +%% +%% Description: Creates a server hello done message. +%%-------------------------------------------------------------------- +server_hello_done() -> + #server_hello_done{}. + +client_hello_extensions(Host, Version, CipherSuites, + #ssl_options{signature_algs = SupportedHashSigns, versions = AllVersions} = SslOpts, ConnectionStates, Renegotiation) -> + {EcPointFormats, EllipticCurves} = + case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of + true -> + client_ecc_extensions(tls_v1, Version); + false -> + {undefined, undefined} + end, + SRP = srp_user(SslOpts), + + #hello_extensions{ + renegotiation_info = renegotiation_info(tls_record, client, + ConnectionStates, Renegotiation), + srp = SRP, + signature_algs = available_signature_algs(SupportedHashSigns, Version, AllVersions), + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + alpn = encode_alpn(SslOpts#ssl_options.alpn_advertised_protocols, Renegotiation), + next_protocol_negotiation = + encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, + Renegotiation), + sni = sni(Host, SslOpts#ssl_options.server_name_indication)}. + +%%-------------------------------------------------------------------- +-spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. +%% +%% Description: Creates a certificate message. +%%-------------------------------------------------------------------- +certificate(OwnCert, CertDbHandle, CertDbRef, client) -> + Chain = + case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of + {ok, _, CertChain} -> + CertChain; + {error, _} -> + %% If no suitable certificate is available, the client + %% SHOULD send a certificate message containing no + %% certificates. (chapter 7.4.6. RFC 4346) + [] + end, + #certificate{asn1_certificates = Chain}; + +certificate(OwnCert, CertDbHandle, CertDbRef, server) -> + case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of + {ok, _, Chain} -> + #certificate{asn1_certificates = Chain}; + {error, _} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, server_has_no_suitable_certificates) + end. + +%%-------------------------------------------------------------------- +-spec next_protocol(binary()) -> #next_protocol{}. +%% +%% Description: Creates a next protocol message +%%------------------------------------------------------------------- +next_protocol(SelectedProtocol) -> + #next_protocol{selected_protocol = SelectedProtocol}. + +%%-------------------------------------------------------------------- +-spec client_certificate_verify(undefined | der_cert(), binary(), + ssl_record:ssl_version(), term(), public_key:private_key(), + ssl_handshake_history()) -> + #certificate_verify{} | ignore | #alert{}. +%% +%% Description: Creates a certificate_verify message, called by the client. +%%-------------------------------------------------------------------- +client_certificate_verify(undefined, _, _, _, _, _) -> + ignore; +client_certificate_verify(_, _, _, _, undefined, _) -> + ignore; +client_certificate_verify(OwnCert, MasterSecret, Version, + {HashAlgo, SignAlgo}, + PrivateKey, {Handshake, _}) -> + case public_key:pkix_is_fixed_dh_cert(OwnCert) of + true -> + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, fixed_diffie_hellman_prohibited); + false -> + Hashes = + calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), + Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey), + #certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}} + end. + +%%-------------------------------------------------------------------- +-spec certificate_request(ssl_cipher:cipher_suite(), db_handle(), + certdb_ref(), #hash_sign_algos{}, ssl_record:ssl_version()) -> + #certificate_request{}. +%% +%% Description: Creates a certificate_request message, called by the server. +%%-------------------------------------------------------------------- +certificate_request(CipherSuite, CertDbHandle, CertDbRef, HashSigns, Version) -> + Types = certificate_types(ssl_cipher:suite_definition(CipherSuite), Version), + Authorities = certificate_authorities(CertDbHandle, CertDbRef), + #certificate_request{ + certificate_types = Types, + hashsign_algorithms = HashSigns, + certificate_authorities = Authorities + }. +%%-------------------------------------------------------------------- +-spec key_exchange(client | server, ssl_record:ssl_version(), + {premaster_secret, binary(), public_key_info()} | + {dh, binary()} | + {dh, {binary(), binary()}, #'DHParameter'{}, {HashAlgo::atom(), SignAlgo::atom()}, + binary(), binary(), public_key:private_key()} | + {ecdh, #'ECPrivateKey'{}} | + {psk, binary()} | + {dhe_psk, binary(), binary()} | + {srp, {binary(), binary()}, #srp_user{}, {HashAlgo::atom(), SignAlgo::atom()}, + binary(), binary(), public_key:private_key()}) -> + #client_key_exchange{} | #server_key_exchange{}. + +%% +%% Description: Creates a keyexchange message. +%%-------------------------------------------------------------------- +key_exchange(client, _Version, {premaster_secret, Secret, {_, PublicKey, _}}) -> + EncPremasterSecret = + encrypted_premaster_secret(Secret, PublicKey), + #client_key_exchange{exchange_keys = EncPremasterSecret}; + +key_exchange(client, _Version, {dh, PublicKey}) -> + #client_key_exchange{ + exchange_keys = #client_diffie_hellman_public{ + dh_public = PublicKey} + }; + +key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey}}) -> + #client_key_exchange{ + exchange_keys = #client_ec_diffie_hellman_public{ + dh_public = ECPublicKey} + }; + +key_exchange(client, _Version, {psk, Identity}) -> + #client_key_exchange{ + exchange_keys = #client_psk_identity{ + identity = Identity} + }; + +key_exchange(client, _Version, {dhe_psk, Identity, PublicKey}) -> + #client_key_exchange{ + exchange_keys = #client_dhe_psk_identity{ + identity = Identity, + dh_public = PublicKey} + }; + +key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, PublicKey, _}}) -> + EncPremasterSecret = + encrypted_premaster_secret(Secret, PublicKey), + #client_key_exchange{ + exchange_keys = #client_rsa_psk_identity{ + identity = PskIdentity, + exchange_keys = EncPremasterSecret}}; + +key_exchange(client, _Version, {srp, PublicKey}) -> + #client_key_exchange{ + exchange_keys = #client_srp_public{ + srp_a = PublicKey} + }; + +key_exchange(server, Version, {dh, {PublicKey, _}, + #'DHParameter'{prime = P, base = G}, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerDHParams = #server_dh_params{dh_p = int_to_bin(P), + dh_g = int_to_bin(G), dh_y = PublicKey}, + enc_server_key_exchange(Version, ServerDHParams, HashSign, + ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = ECPublicKey, + parameters = ECCurve}, HashSign, + ClientRandom, ServerRandom, PrivateKey}) -> + ServerECParams = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}, + enc_server_key_exchange(Version, ServerECParams, HashSign, + ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {psk, PskIdentityHint, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerPSKParams = #server_psk_params{hint = PskIdentityHint}, + enc_server_key_exchange(Version, ServerPSKParams, HashSign, + ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {dhe_psk, PskIdentityHint, {PublicKey, _}, + #'DHParameter'{prime = P, base = G}, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerEDHPSKParams = #server_dhe_psk_params{ + hint = PskIdentityHint, + dh_params = #server_dh_params{dh_p = int_to_bin(P), + dh_g = int_to_bin(G), dh_y = PublicKey} + }, + enc_server_key_exchange(Version, ServerEDHPSKParams, + HashSign, ClientRandom, ServerRandom, PrivateKey); + +key_exchange(server, Version, {srp, {PublicKey, _}, + #srp_user{generator = Generator, prime = Prime, + salt = Salt}, + HashSign, ClientRandom, ServerRandom, PrivateKey}) -> + ServerSRPParams = #server_srp_params{srp_n = Prime, srp_g = Generator, + srp_s = Salt, srp_b = PublicKey}, + enc_server_key_exchange(Version, ServerSRPParams, HashSign, + ClientRandom, ServerRandom, PrivateKey). + +%%-------------------------------------------------------------------- +-spec finished(ssl_record:ssl_version(), client | server, integer(), binary(), ssl_handshake_history()) -> + #finished{}. +%% +%% Description: Creates a handshake finished message +%%------------------------------------------------------------------- +finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the current handshake + #finished{verify_data = + calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake)}. + +%% ---------- Handle handshake messages ---------- + +verify_server_key(#server_key_params{params_bin = EncParams, + signature = Signature}, + HashSign = {HashAlgo, _}, + ConnectionStates, Version, PubKeyInfo) -> + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + Hash = server_key_exchange_hash(HashAlgo, + <<ClientRandom/binary, + ServerRandom/binary, + EncParams/binary>>), + verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo). + +%%-------------------------------------------------------------------- +-spec certificate_verify(binary(), public_key_info(), ssl_record:ssl_version(), term(), + binary(), ssl_handshake_history()) -> valid | #alert{}. +%% +%% Description: Checks that the certificate_verify message is valid. +%%-------------------------------------------------------------------- +certificate_verify(_, _, _, undefined, _, _) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, invalid_certificate_verify_message); + +certificate_verify(Signature, PublicKeyInfo, Version, + HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) -> + Hash = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), + case verify_signature(Version, Hash, HashSign, Signature, PublicKeyInfo) of + true -> + valid; + _ -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE) + end. +%%-------------------------------------------------------------------- +-spec verify_signature(ssl_record:ssl_version(), binary(), {term(), term()}, binary(), + public_key_info()) -> true | false. +%% +%% Description: Checks that a public_key signature is valid. +%%-------------------------------------------------------------------- +verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) -> + true; +verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) + when Minor >= 3 -> + public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey); +verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) -> + case public_key:decrypt_public(Signature, PubKey, + [{rsa_pad, rsa_pkcs1_padding}]) of + Hash -> true; + _ -> false + end; +verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) -> + public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); +verify_signature(_, Hash, {HashAlgo, _SignAlg}, Signature, + {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> + public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}). + + +%%-------------------------------------------------------------------- +-spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, + verify_peer | verify_none, {fun(), term}, fun(), term(), term(), + client | server) -> {der_cert(), public_key_info()} | #alert{}. +%% +%% Description: Handles a certificate handshake message +%%-------------------------------------------------------------------- +certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, + MaxPathLen, _Verify, ValidationFunAndState0, PartialChain, CRLCheck, CRLDbHandle, Role) -> + [PeerCert | _] = ASN1Certs, + + ValidationFunAndState = validation_fun_and_state(ValidationFunAndState0, Role, + CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle), + + try + {TrustedCert, CertPath} = + ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef, PartialChain), + case public_key:pkix_path_validation(TrustedCert, + CertPath, + [{max_path_length, MaxPathLen}, + {verify_fun, ValidationFunAndState}]) of + {ok, {PublicKeyInfo,_}} -> + {PeerCert, PublicKeyInfo}; + {error, Reason} -> + path_validation_alert(Reason) + end + catch + error:_ -> + %% ASN-1 decode of certificate somehow failed + ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, failed_to_decode_certificate) + end. + +%%-------------------------------------------------------------------- +-spec verify_connection(ssl_record:ssl_version(), #finished{}, client | server, integer(), binary(), + ssl_handshake_history()) -> verified | #alert{}. +%% +%% Description: Checks the ssl handshake finished message to verify +%% the connection. +%%------------------------------------------------------------------- +verify_connection(Version, #finished{verify_data = Data}, + Role, PrfAlgo, MasterSecret, {_, Handshake}) -> + %% use the previous hashes + case calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) of + Data -> + verified; + _ -> + ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) + end. + +%%-------------------------------------------------------------------- +-spec init_handshake_history() -> ssl_handshake_history(). + +%% +%% Description: Initialize the empty handshake history buffer. +%%-------------------------------------------------------------------- +init_handshake_history() -> + {[], []}. + +%%-------------------------------------------------------------------- +-spec update_handshake_history(ssl_handshake:ssl_handshake_history(), Data ::term()) -> + ssl_handshake:ssl_handshake_history(). +%% +%% Description: Update the handshake history buffer with Data. +%%-------------------------------------------------------------------- +update_handshake_history(Handshake, % special-case SSL2 client hello + <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor), + ?UINT16(CSLength), ?UINT16(0), + ?UINT16(CDLength), + CipherSuites:CSLength/binary, + ChallengeData:CDLength/binary>>) -> + update_handshake_history(Handshake, + <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor), + ?UINT16(CSLength), ?UINT16(0), + ?UINT16(CDLength), + CipherSuites:CSLength/binary, + ChallengeData:CDLength/binary>>); +update_handshake_history({Handshake0, _Prev}, Data) -> + {[Data|Handshake0], Handshake0}. + +%% %%-------------------------------------------------------------------- +%% -spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). + +%% %% +%% %% Description: Public key decryption using the private key. +%% %%-------------------------------------------------------------------- +%% decrypt_premaster_secret(Secret, RSAPrivateKey) -> +%% try public_key:decrypt_private(Secret, RSAPrivateKey, +%% [{rsa_pad, rsa_pkcs1_padding}]) +%% catch +%% _:_ -> +%% throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) +%% end. + +premaster_secret(OtherPublicDhKey, MyPrivateKey, #'DHParameter'{} = Params) -> + try + public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params) + catch + error:computation_failed -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; +premaster_secret(PublicDhKey, PrivateDhKey, #server_dh_params{dh_p = Prime, dh_g = Base}) -> + try + crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]) + catch + error:computation_failed -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; +premaster_secret(#client_srp_public{srp_a = ClientPublicKey}, ServerKey, #srp_user{prime = Prime, + verifier = Verifier}) -> + case crypto:compute_key(srp, ClientPublicKey, ServerKey, {host, [Verifier, Prime, '6a']}) of + error -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); + PremasterSecret -> + PremasterSecret + end; +premaster_secret(#server_srp_params{srp_n = Prime, srp_g = Generator, srp_s = Salt, srp_b = Public}, + ClientKeys, {Username, Password}) -> + case ssl_srp_primes:check_srp_params(Generator, Prime) of + ok -> + DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), + case crypto:compute_key(srp, Public, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of + error -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)); + PremasterSecret -> + PremasterSecret + end; + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end; +premaster_secret(#client_rsa_psk_identity{ + identity = PSKIdentity, + exchange_keys = #encrypted_premaster_secret{premaster_secret = EncPMS} + }, #'RSAPrivateKey'{} = Key, PSKLookup) -> + PremasterSecret = premaster_secret(EncPMS, Key), + psk_secret(PSKIdentity, PSKLookup, PremasterSecret); +premaster_secret(#server_dhe_psk_params{ + hint = IdentityHint, + dh_params = #server_dh_params{dh_y = PublicDhKey} = Params}, + PrivateDhKey, + LookupFun) -> + PremasterSecret = premaster_secret(PublicDhKey, PrivateDhKey, Params), + psk_secret(IdentityHint, LookupFun, PremasterSecret); +premaster_secret({rsa_psk, PSKIdentity}, PSKLookup, RSAPremasterSecret) -> + psk_secret(PSKIdentity, PSKLookup, RSAPremasterSecret). + +premaster_secret(#client_dhe_psk_identity{ + identity = PSKIdentity, + dh_public = PublicDhKey}, PrivateKey, #'DHParameter'{} = Params, PSKLookup) -> + PremasterSecret = premaster_secret(PublicDhKey, PrivateKey, Params), + psk_secret(PSKIdentity, PSKLookup, PremasterSecret). +premaster_secret(#client_psk_identity{identity = PSKIdentity}, PSKLookup) -> + psk_secret(PSKIdentity, PSKLookup); +premaster_secret({psk, PSKIdentity}, PSKLookup) -> + psk_secret(PSKIdentity, PSKLookup); +premaster_secret(#'ECPoint'{} = ECPoint, #'ECPrivateKey'{} = ECDHKeys) -> + public_key:compute_key(ECPoint, ECDHKeys); +premaster_secret(EncSecret, #'RSAPrivateKey'{} = RSAPrivateKey) -> + try public_key:decrypt_private(EncSecret, RSAPrivateKey, + [{rsa_pad, rsa_pkcs1_padding}]) + catch + _:_ -> + throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) + end. +%%-------------------------------------------------------------------- +-spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary(). +%% +%% Description: Calculate server key exchange hash +%%-------------------------------------------------------------------- +server_key_exchange_hash(md5sha, Value) -> + MD5 = crypto:hash(md5, Value), + SHA = crypto:hash(sha, Value), + <<MD5/binary, SHA/binary>>; + +server_key_exchange_hash(Hash, Value) -> + crypto:hash(Hash, Value). +%%-------------------------------------------------------------------- +-spec prf(ssl_record:ssl_version(), non_neg_integer(), binary(), binary(), [binary()], non_neg_integer()) -> + {ok, binary()} | {error, undefined}. +%% +%% Description: use the TLS PRF to generate key material +%%-------------------------------------------------------------------- +prf({3,0}, _, _, _, _, _) -> + {error, undefined}; +prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> + {ok, tls_v1:prf(PRFAlgo, Secret, Label, Seed, WantedLength)}. + + +%%-------------------------------------------------------------------- +-spec select_hashsign(#hash_sign_algos{} | undefined, undefined | binary(), + atom(), [atom()], ssl_record:ssl_version()) -> + {atom(), atom()} | undefined | #alert{}. + +%% +%% Description: Handles signature_algorithms extension +%%-------------------------------------------------------------------- +select_hashsign(_, undefined, _, _, _Version) -> + {null, anon}; +%% The signature_algorithms extension was introduced with TLS 1.2. Ignore it if we have +%% negotiated a lower version. +select_hashsign(HashSigns, Cert, KeyExAlgo, + undefined, {Major, Minor} = Version) when Major >= 3 andalso Minor >= 3-> + select_hashsign(HashSigns, Cert, KeyExAlgo, tls_v1:default_signature_algs(Version), Version); +select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns, + {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + Sign = cert_sign(Algo), + case lists:filter(fun({sha, dsa = S}) when S == Sign -> + true; + ({_, dsa}) -> + false; + ({_, _} = Algos) -> + is_acceptable_hash_sign(Algos, Sign, KeyExAlgo, SupportedHashSigns); + (_) -> + false + end, HashSigns) of + [] -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm); + [HashSign | _] -> + HashSign + end; +select_hashsign(_, Cert, _, _, Version) -> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + select_hashsign_algs(undefined, Algo, Version). + +%%-------------------------------------------------------------------- +-spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) -> + {atom(), atom()}. + +%% Description: For TLS 1.2 hash function and signature algorithm pairs can be +%% negotiated with the signature_algorithms extension, +%% for previous versions always use appropriate defaults. +%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms +%% If the client does not send the signature_algorithms extension, the +%% server MUST do the following: (e.i defaults for TLS 1.2) +%% +%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, +%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had +%% sent the value {sha1,rsa}. +%% +%% - If the negotiated key exchange algorithm is one of (DHE_DSS, +%% DH_DSS), behave as if the client had sent the value {sha1,dsa}. +%% +%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, +%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. + +%%-------------------------------------------------------------------- +select_hashsign_algs(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso + Major >= 3 andalso Minor >= 3 -> + HashSign; +select_hashsign_algs(undefined, ?rsaEncryption, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> + {sha, rsa}; +select_hashsign_algs(undefined,?'id-ecPublicKey', _) -> + {sha, ecdsa}; +select_hashsign_algs(undefined, ?rsaEncryption, _) -> + {md5sha, rsa}; +select_hashsign_algs(undefined, ?'id-dsa', _) -> + {sha, dsa}. + +%%-------------------------------------------------------------------- +-spec master_secret(atom(), ssl_record:ssl_version(), #session{} | binary(), #connection_states{}, + client | server) -> {binary(), #connection_states{}} | #alert{}. +%% +%% Description: Sets or calculates the master secret and calculate keys, +%% updating the pending connection states. The Mastersecret and the update +%% connection states are returned or an alert if the calculation fails. +%%------------------------------------------------------------------- +master_secret(RecordCB, Version, #session{master_secret = Mastersecret}, + ConnectionStates, Role) -> + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates, read), + SecParams = ConnectionState#connection_state.security_parameters, + try master_secret(RecordCB, Version, Mastersecret, SecParams, + ConnectionStates, Role) + catch + exit:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, key_calculation_failure) + end; + +master_secret(RecordCB, Version, PremasterSecret, ConnectionStates, Role) -> + ConnectionState = + ssl_record:pending_connection_state(ConnectionStates, read), + SecParams = ConnectionState#connection_state.security_parameters, + #security_parameters{prf_algorithm = PrfAlgo, + client_random = ClientRandom, + server_random = ServerRandom} = SecParams, + try master_secret(RecordCB, Version, + calc_master_secret(Version,PrfAlgo,PremasterSecret, + ClientRandom, ServerRandom), + SecParams, ConnectionStates, Role) + catch + exit:_ -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, master_secret_calculation_failure) + end. + +%%-------------Encode/Decode -------------------------------- +encode_handshake(#next_protocol{selected_protocol = SelectedProtocol}, _Version) -> + PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), + {?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary, + ?BYTE(PaddingLength), 0:(PaddingLength * 8)>>}; + +encode_handshake(#server_hello{server_version = {Major, Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = CipherSuite, + compression_method = Comp_method, + extensions = #hello_extensions{} = Extensions}, _Version) -> + SID_length = byte_size(Session_ID), + ExtensionsBin = encode_hello_extensions(Extensions), + {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SID_length), Session_ID/binary, + CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>}; +encode_handshake(#certificate{asn1_certificates = ASN1CertList}, _Version) -> + ASN1Certs = certs_from_list(ASN1CertList), + ACLen = erlang:iolist_size(ASN1Certs), + {?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>}; +encode_handshake(#server_key_exchange{exchange_keys = Keys}, _Version) -> + {?SERVER_KEY_EXCHANGE, Keys}; +encode_handshake(#server_key_params{params_bin = Keys, hashsign = HashSign, + signature = Signature}, Version) -> + EncSign = enc_sign(HashSign, Signature, Version), + {?SERVER_KEY_EXCHANGE, <<Keys/binary, EncSign/binary>>}; +encode_handshake(#certificate_request{certificate_types = CertTypes, + hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, + certificate_authorities = CertAuths}, + {Major, Minor}) + when Major == 3, Minor >= 3 -> + HashSigns= << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || + {Hash, Sign} <- HashSignAlgos >>, + CertTypesLen = byte_size(CertTypes), + HashSignsLen = byte_size(HashSigns), + CertAuthsLen = byte_size(CertAuths), + {?CERTIFICATE_REQUEST, + <<?BYTE(CertTypesLen), CertTypes/binary, + ?UINT16(HashSignsLen), HashSigns/binary, + ?UINT16(CertAuthsLen), CertAuths/binary>> + }; +encode_handshake(#certificate_request{certificate_types = CertTypes, + certificate_authorities = CertAuths}, + _Version) -> + CertTypesLen = byte_size(CertTypes), + CertAuthsLen = byte_size(CertAuths), + {?CERTIFICATE_REQUEST, + <<?BYTE(CertTypesLen), CertTypes/binary, + ?UINT16(CertAuthsLen), CertAuths/binary>> + }; +encode_handshake(#server_hello_done{}, _Version) -> + {?SERVER_HELLO_DONE, <<>>}; +encode_handshake(#client_key_exchange{exchange_keys = ExchangeKeys}, Version) -> + {?CLIENT_KEY_EXCHANGE, encode_client_key(ExchangeKeys, Version)}; +encode_handshake(#certificate_verify{signature = BinSig, hashsign_algorithm = HashSign}, Version) -> + EncSig = enc_sign(HashSign, BinSig, Version), + {?CERTIFICATE_VERIFY, EncSig}; +encode_handshake(#finished{verify_data = VerifyData}, _Version) -> + {?FINISHED, VerifyData}. + +encode_hello_extensions(#hello_extensions{} = Extensions) -> + encode_hello_extensions(hello_extensions_list(Extensions), <<>>). +encode_hello_extensions([], <<>>) -> + <<>>; +encode_hello_extensions([], Acc) -> + Size = byte_size(Acc), + <<?UINT16(Size), Acc/binary>>; + +encode_hello_extensions([#alpn{extension_data = ExtensionData} | Rest], Acc) -> + Len = byte_size(ExtensionData), + ExtLen = Len + 2, + encode_hello_extensions(Rest, <<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), + ExtensionData/binary, Acc/binary>>); +encode_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) -> + Len = byte_size(ExtensionData), + encode_hello_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), + ExtensionData/binary, Acc/binary>>); +encode_hello_extensions([#renegotiation_info{renegotiated_connection = undefined} | Rest], Acc) -> + encode_hello_extensions(Rest, Acc); +encode_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) -> + Len = byte_size(Info), + encode_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>); + +encode_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) -> + InfoLen = byte_size(Info), + Len = InfoLen +1, + encode_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen), + Info/binary, Acc/binary>>); +encode_hello_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) -> + + EllipticCurveList = << <<(tls_v1:oid_to_enum(X)):16>> || X <- EllipticCurves>>, + ListLen = byte_size(EllipticCurveList), + Len = ListLen + 2, + encode_hello_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT), + ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary, Acc/binary>>); +encode_hello_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) -> + ECPointFormatList = list_to_binary(ECPointFormats), + ListLen = byte_size(ECPointFormatList), + Len = ListLen + 1, + encode_hello_extensions(Rest, <<?UINT16(?EC_POINT_FORMATS_EXT), + ?UINT16(Len), ?BYTE(ListLen), ECPointFormatList/binary, Acc/binary>>); +encode_hello_extensions([#srp{username = UserName} | Rest], Acc) -> + SRPLen = byte_size(UserName), + Len = SRPLen + 2, + encode_hello_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), + UserName/binary, Acc/binary>>); +encode_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) -> + SignAlgoList = << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || + {Hash, Sign} <- HashSignAlgos >>, + ListLen = byte_size(SignAlgoList), + Len = ListLen + 2, + encode_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT), + ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>); +encode_hello_extensions([#sni{hostname = Hostname} | Rest], Acc) -> + HostLen = length(Hostname), + HostnameBin = list_to_binary(Hostname), + % Hostname type (1 byte) + Hostname length (2 bytes) + Hostname (HostLen bytes) + ServerNameLength = 1 + 2 + HostLen, + % ServerNameListSize (2 bytes) + ServerNameLength + ExtLength = 2 + ServerNameLength, + encode_hello_extensions(Rest, <<?UINT16(?SNI_EXT), ?UINT16(ExtLength), + ?UINT16(ServerNameLength), + ?BYTE(?SNI_NAMETYPE_HOST_NAME), + ?UINT16(HostLen), HostnameBin/binary, + Acc/binary>>). + +enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, + ClientRandom, ServerRandom, PrivateKey) -> + EncParams = encode_server_key(Params), + case HashAlgo of + null -> + #server_key_params{params = Params, + params_bin = EncParams, + hashsign = {null, anon}, + signature = <<>>}; + _ -> + Hash = + server_key_exchange_hash(HashAlgo, <<ClientRandom/binary, + ServerRandom/binary, + EncParams/binary>>), + Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), + #server_key_params{params = Params, + params_bin = EncParams, + hashsign = {HashAlgo, SignAlgo}, + signature = Signature} + end. + +%%-------------------------------------------------------------------- +-spec decode_client_key(binary(), ssl_cipher:key_algo(), ssl_record:ssl_version()) -> + #encrypted_premaster_secret{} + | #client_diffie_hellman_public{} + | #client_ec_diffie_hellman_public{} + | #client_psk_identity{} + | #client_dhe_psk_identity{} + | #client_rsa_psk_identity{} + | #client_srp_public{}. +%% +%% Description: Decode client_key data and return appropriate type +%%-------------------------------------------------------------------- +decode_client_key(ClientKey, Type, Version) -> + dec_client_key(ClientKey, key_exchange_alg(Type), Version). + +%%-------------------------------------------------------------------- +-spec decode_server_key(binary(), ssl_cipher:key_algo(), ssl_record:ssl_version()) -> + #server_key_params{}. +%% +%% Description: Decode server_key data and return appropriate type +%%-------------------------------------------------------------------- +decode_server_key(ServerKey, Type, Version) -> + dec_server_key(ServerKey, key_exchange_alg(Type), Version). + +%% +%% Description: Encode and decode functions for ALPN extension data. +%%-------------------------------------------------------------------- + +%% While the RFC opens the door to allow ALPN during renegotiation, in practice +%% this does not work and it is recommended to ignore any ALPN extension during +%% renegotiation, as done here. +encode_alpn(_, true) -> + undefined; +encode_alpn(undefined, _) -> + undefined; +encode_alpn(Protocols, _) -> + #alpn{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. + +decode_alpn(undefined) -> + undefined; +decode_alpn(#alpn{extension_data=Data}) -> + decode_protocols(Data, []). + +encode_client_protocol_negotiation(undefined, _) -> + undefined; +encode_client_protocol_negotiation(_, false) -> + #next_protocol_negotiation{extension_data = <<>>}; +encode_client_protocol_negotiation(_, _) -> + undefined. + +encode_protocols_advertised_on_server(undefined) -> + undefined; + +encode_protocols_advertised_on_server(Protocols) -> + #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. + +decode_handshake(_, ?HELLO_REQUEST, <<>>) -> + #hello_request{}; +decode_handshake(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength), + SelectedProtocol:SelectedProtocolLength/binary, + ?BYTE(PaddingLength), _Padding:PaddingLength/binary>>) -> + #next_protocol{selected_protocol = SelectedProtocol}; + +decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SID_length), Session_ID:SID_length/binary, + Cipher_suite:2/binary, ?BYTE(Comp_method)>>) -> + #server_hello{ + server_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method, + extensions = #hello_extensions{}}; + +decode_handshake(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SID_length), Session_ID:SID_length/binary, + Cipher_suite:2/binary, ?BYTE(Comp_method), + ?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> + + HelloExtensions = decode_hello_extensions(Extensions), + + #server_hello{ + server_version = {Major,Minor}, + random = Random, + session_id = Session_ID, + cipher_suite = Cipher_suite, + compression_method = Comp_method, + extensions = HelloExtensions}; + +decode_handshake(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) -> + #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; +decode_handshake(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> + #server_key_exchange{exchange_keys = Keys}; +decode_handshake({Major, Minor}, ?CERTIFICATE_REQUEST, + <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, + ?UINT16(HashSignsLen), HashSigns:HashSignsLen/binary, + ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) + when Major >= 3, Minor >= 3 -> + HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || + <<?BYTE(Hash), ?BYTE(Sign)>> <= HashSigns], + #certificate_request{certificate_types = CertTypes, + hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, + certificate_authorities = CertAuths}; +decode_handshake(_Version, ?CERTIFICATE_REQUEST, + <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, + ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) -> + #certificate_request{certificate_types = CertTypes, + certificate_authorities = CertAuths}; +decode_handshake(_Version, ?SERVER_HELLO_DONE, <<>>) -> + #server_hello_done{}; +decode_handshake({Major, Minor}, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen), + Signature:SignLen/binary>>) + when Major == 3, Minor >= 3 -> + #certificate_verify{hashsign_algorithm = dec_hashsign(HashSign), signature = Signature}; +decode_handshake(_Version, ?CERTIFICATE_VERIFY,<<?UINT16(SignLen), Signature:SignLen/binary>>)-> + #certificate_verify{signature = Signature}; +decode_handshake(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) -> + #client_key_exchange{exchange_keys = PKEPMS}; +decode_handshake(_Version, ?FINISHED, VerifyData) -> + #finished{verify_data = VerifyData}; +decode_handshake(_, Message, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_handshake, Message})). + +%%-------------------------------------------------------------------- +-spec decode_hello_extensions({client, binary()} | binary()) -> #hello_extensions{}. +%% +%% Description: Decodes TLS hello extensions +%%-------------------------------------------------------------------- +decode_hello_extensions({client, <<>>}) -> + #hello_extensions{}; +decode_hello_extensions({client, <<?UINT16(ExtLen), Extensions:ExtLen/binary>>}) -> + decode_hello_extensions(Extensions); +decode_hello_extensions(Extensions) -> + dec_hello_extensions(Extensions, #hello_extensions{}). + +dec_server_key(<<?UINT16(PLen), P:PLen/binary, + ?UINT16(GLen), G:GLen/binary, + ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, + ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) -> + Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, + {BinMsg, HashSign, Signature} = dec_server_key_params(PLen + GLen + YLen + 6, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +%% ECParameters with named_curve +%% TODO: explicit curve +dec_server_key(<<?BYTE(?NAMED_CURVE), ?UINT16(CurveID), + ?BYTE(PointLen), ECPoint:PointLen/binary, + _/binary>> = KeyStruct, + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> + Params = #server_ecdh_params{curve = {namedCurve, tls_v1:enum_to_oid(CurveID)}, + public = ECPoint}, + {BinMsg, HashSign, Signature} = dec_server_key_params(PointLen + 4, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<<?UINT16(Len), PskIdentityHint:Len/binary, _/binary>> = KeyStruct, + KeyExchange, Version) + when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> + Params = #server_psk_params{ + hint = PskIdentityHint}, + {BinMsg, HashSign, Signature} = dec_server_key_params(Len + 2, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary, + ?UINT16(PLen), P:PLen/binary, + ?UINT16(GLen), G:GLen/binary, + ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, + ?KEY_EXCHANGE_DHE_PSK, Version) -> + DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, + Params = #server_dhe_psk_params{ + hint = IdentityHint, + dh_params = DHParams}, + {BinMsg, HashSign, Signature} = dec_server_key_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(<<?UINT16(NLen), N:NLen/binary, + ?UINT16(GLen), G:GLen/binary, + ?BYTE(SLen), S:SLen/binary, + ?UINT16(BLen), B:BLen/binary, _/binary>> = KeyStruct, + ?KEY_EXCHANGE_SRP, Version) -> + Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, + {BinMsg, HashSign, Signature} = dec_server_key_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version), + #server_key_params{params = Params, + params_bin = BinMsg, + hashsign = HashSign, + signature = Signature}; +dec_server_key(_, KeyExchange, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {unknown_or_malformed_key_exchange, KeyExchange})). + +%%-------------------------------------------------------------------- +-spec decode_suites('2_bytes'|'3_bytes', binary()) -> list(). +%% +%% Description: +%%-------------------------------------------------------------------- +decode_suites('2_bytes', Dec) -> + from_2bytes(Dec); +decode_suites('3_bytes', Dec) -> + from_3bytes(Dec). + +%%-------------Cipeher suite handling -------------------------------- + +available_suites(UserSuites, Version) -> + lists:filtermap(fun(Suite) -> + lists:member(Suite, ssl_cipher:all_suites(Version)) + end, UserSuites). + +available_suites(ServerCert, UserSuites, Version, undefined, Curve) -> + ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)) + -- unavailable_ecc_suites(Curve); +available_suites(ServerCert, UserSuites, Version, HashSigns, Curve) -> + Suites = available_suites(ServerCert, UserSuites, Version, undefined, Curve), + filter_hashsigns(Suites, [ssl_cipher:suite_definition(Suite) || Suite <- Suites], HashSigns, []). +filter_hashsigns([], [], _, Acc) -> + lists:reverse(Acc); +filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, + Acc) when KeyExchange == dhe_ecdsa; + KeyExchange == ecdhe_ecdsa -> + do_filter_hashsigns(ecdsa, Suite, Suites, Algos, HashSigns, Acc); + +filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, + Acc) when KeyExchange == rsa; + KeyExchange == dhe_rsa; + KeyExchange == ecdhe_rsa; + KeyExchange == srp_rsa; + KeyExchange == rsa_psk -> + do_filter_hashsigns(rsa, Suite, Suites, Algos, HashSigns, Acc); +filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when + KeyExchange == dhe_dss; + KeyExchange == srp_dss -> + do_filter_hashsigns(dsa, Suite, Suites, Algos, HashSigns, Acc); +filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when + KeyExchange == dh_dss; + KeyExchange == dh_rsa; + KeyExchange == dh_ecdsa; + KeyExchange == ecdh_rsa; + KeyExchange == ecdh_ecdsa -> + %% Fixed DH certificates MAY be signed with any hash/signature + %% algorithm pair appearing in the hash_sign extension. The names + %% DH_DSS, DH_RSA, ECDH_ECDSA, and ECDH_RSA are historical. + filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]); +filter_hashsigns([Suite | Suites], [{KeyExchange,_,_,_} | Algos], HashSigns, Acc) when + KeyExchange == dh_anon; + KeyExchange == ecdh_anon; + KeyExchange == srp_anon; + KeyExchange == psk; + KeyExchange == dhe_psk -> + %% In this case hashsigns is not used as the kexchange is anonaymous + filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]). + +do_filter_hashsigns(SignAlgo, Suite, Suites, Algos, HashSigns, Acc) -> + case lists:keymember(SignAlgo, 2, HashSigns) of + true -> + filter_hashsigns(Suites, Algos, HashSigns, [Suite| Acc]); + false -> + filter_hashsigns(Suites, Algos, HashSigns, Acc) + end. + +unavailable_ecc_suites(no_curve) -> + ssl_cipher:ec_keyed_suites(); +unavailable_ecc_suites(_) -> + []. + +cipher_suites(Suites, false) -> + [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; +cipher_suites(Suites, true) -> + Suites. + +select_session(SuggestedSessionId, CipherSuites, HashSigns, Compressions, Port, #session{ecc = ECCCurve} = + Session, Version, + #ssl_options{ciphers = UserSuites, honor_cipher_order = HonorCipherOrder} = SslOpts, + Cache, CacheCb, Cert) -> + {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId, + SslOpts, Cert, + Cache, CacheCb), + case Resumed of + undefined -> + Suites = available_suites(Cert, UserSuites, Version, HashSigns, ECCCurve), + CipherSuite = select_cipher_suite(CipherSuites, Suites, HonorCipherOrder), + Compression = select_compression(Compressions), + {new, Session#session{session_id = SessionId, + cipher_suite = CipherSuite, + compression_method = Compression}}; + _ -> + {resumed, Resumed} + end. + +supported_ecc({Major, Minor} = Version) when ((Major == 3) and (Minor >= 1)) orelse (Major > 3) -> + Curves = tls_v1:ecc_curves(Version), + #elliptic_curves{elliptic_curve_list = Curves}; +supported_ecc(_) -> + #elliptic_curves{elliptic_curve_list = []}. + +%%-------------certificate handling -------------------------------- + +certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 -> + case proplists:get_bool(ecdsa, + proplists:get_value(public_keys, crypto:supports())) of + true -> + <<?BYTE(?ECDSA_SIGN), ?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>; + false -> + <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>> + end; + +certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa; + KeyExchange == dhe_rsa; + KeyExchange == ecdhe_rsa -> + <<?BYTE(?RSA_SIGN)>>; + +certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dhe_dss; + KeyExchange == srp_dss -> + <<?BYTE(?DSS_SIGN)>>; + +certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_ecdsa; + KeyExchange == dhe_ecdsa; + KeyExchange == ecdh_ecdsa; + KeyExchange == ecdhe_ecdsa -> + <<?BYTE(?ECDSA_SIGN)>>; + +certificate_types(_, _) -> + <<?BYTE(?RSA_SIGN)>>. + +certificate_authorities(CertDbHandle, CertDbRef) -> + Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef), + Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> + OTPSubj = TBSCert#'OTPTBSCertificate'.subject, + DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), + DNEncodedLen = byte_size(DNEncodedBin), + <<?UINT16(DNEncodedLen), DNEncodedBin/binary>> + end, + list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). + +certificate_authorities_from_db(CertDbHandle, CertDbRef) -> + ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> + [Cert | Acc]; + (_, Acc) -> + Acc + end, + ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle). + +%%-------------Extension handling -------------------------------- + +handle_client_hello_extensions(RecordCB, Random, ClientCipherSuites, + #hello_extensions{renegotiation_info = Info, + srp = SRP, + ec_point_formats = ECCFormat, + alpn = ALPN, + next_protocol_negotiation = NextProtocolNegotiation}, Version, + #ssl_options{secure_renegotiate = SecureRenegotation, + alpn_preferred_protocols = ALPNPreferredProtocols} = Opts, + #session{cipher_suite = NegotiatedCipherSuite, + compression_method = Compression} = Session0, + ConnectionStates0, Renegotiation) -> + Session = handle_srp_extension(SRP, Session0), + ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info, + Random, NegotiatedCipherSuite, + ClientCipherSuites, Compression, + ConnectionStates0, Renegotiation, SecureRenegotation), + + ServerHelloExtensions = #hello_extensions{ + renegotiation_info = renegotiation_info(RecordCB, server, + ConnectionStates, Renegotiation), + ec_point_formats = server_ecc_extension(Version, ECCFormat) + }, + + %% If we receive an ALPN extension and have ALPN configured for this connection, + %% we handle it. Otherwise we check for the NPN extension. + if + ALPN =/= undefined, ALPNPreferredProtocols =/= undefined -> + case handle_alpn_extension(ALPNPreferredProtocols, decode_alpn(ALPN)) of + #alert{} = Alert -> + Alert; + Protocol -> + {Session, ConnectionStates, Protocol, + ServerHelloExtensions#hello_extensions{alpn=encode_alpn([Protocol], Renegotiation)}} + end; + true -> + ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts), + {Session, ConnectionStates, undefined, + ServerHelloExtensions#hello_extensions{next_protocol_negotiation= + encode_protocols_advertised_on_server(ProtocolsToAdvertise)}} + end. + +handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, + #hello_extensions{renegotiation_info = Info, + alpn = ALPN, + next_protocol_negotiation = NextProtocolNegotiation}, Version, + #ssl_options{secure_renegotiate = SecureRenegotation, + next_protocol_selector = NextProtoSelector}, + ConnectionStates0, Renegotiation) -> + ConnectionStates = handle_renegotiation_extension(client, RecordCB, Version, Info, Random, + CipherSuite, undefined, + Compression, ConnectionStates0, + Renegotiation, SecureRenegotation), + + %% If we receive an ALPN extension then this is the protocol selected, + %% otherwise handle the NPN extension. + case decode_alpn(ALPN) of + %% ServerHello contains exactly one protocol: the one selected. + %% We also ignore the ALPN extension during renegotiation (see encode_alpn/2). + [Protocol] when not Renegotiation -> + {ConnectionStates, alpn, Protocol}; + undefined -> + case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of + #alert{} = Alert -> + Alert; + Protocol -> + {ConnectionStates, npn, Protocol} + end; + {error, Reason} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); + [] -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_protocols_in_server_hello); + [_|_] -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, too_many_protocols_in_server_hello) + end. + +select_version(RecordCB, ClientVersion, Versions) -> + ServerVersion = RecordCB:highest_protocol_version(Versions), + RecordCB:lowest_protocol_version(ClientVersion, ServerVersion). + +renegotiation_info(_, client, _, false) -> + #renegotiation_info{renegotiated_connection = undefined}; +renegotiation_info(_RecordCB, server, ConnectionStates, false) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case CS#connection_state.secure_renegotiation of + true -> + #renegotiation_info{renegotiated_connection = ?byte(0)}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end; +renegotiation_info(_RecordCB, client, ConnectionStates, true) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case CS#connection_state.secure_renegotiation of + true -> + Data = CS#connection_state.client_verify_data, + #renegotiation_info{renegotiated_connection = Data}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end; + +renegotiation_info(_RecordCB, server, ConnectionStates, true) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case CS#connection_state.secure_renegotiation of + true -> + CData = CS#connection_state.client_verify_data, + SData =CS#connection_state.server_verify_data, + #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>}; + false -> + #renegotiation_info{renegotiated_connection = undefined} + end. + +handle_renegotiation_info(_RecordCB, _, #renegotiation_info{renegotiated_connection = ?byte(0)}, + ConnectionStates, false, _, _) -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; + +handle_renegotiation_info(_RecordCB, server, undefined, ConnectionStates, _, _, CipherSuites) -> + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + {ok, ssl_record:set_renegotiation_flag(true, ConnectionStates)}; + false -> + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)} + end; + +handle_renegotiation_info(_RecordCB, _, undefined, ConnectionStates, false, _, _) -> + {ok, ssl_record:set_renegotiation_flag(false, ConnectionStates)}; + +handle_renegotiation_info(_RecordCB, client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, + ConnectionStates, true, _, _) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + CData = CS#connection_state.client_verify_data, + SData = CS#connection_state.server_verify_data, + case <<CData/binary, SData/binary>> == ClientServerVerify of + true -> + {ok, ConnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, client_renegotiation) + end; +handle_renegotiation_info(_RecordCB, server, #renegotiation_info{renegotiated_connection = ClientVerify}, + ConnectionStates, true, _, CipherSuites) -> + + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); + false -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + Data = CS#connection_state.client_verify_data, + case Data == ClientVerify of + true -> + {ok, ConnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, server_renegotiation) + end + end; + +handle_renegotiation_info(RecordCB, client, undefined, ConnectionStates, true, SecureRenegotation, _) -> + handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation); + +handle_renegotiation_info(RecordCB, server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> + case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of + true -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, {server_renegotiation, empty_renegotiation_info_scsv}); + false -> + handle_renegotiation_info(RecordCB, ConnectionStates, SecureRenegotation) + end. + +handle_renegotiation_info(_RecordCB, ConnectionStates, SecureRenegotation) -> + CS = ssl_record:current_connection_state(ConnectionStates, read), + case {SecureRenegotation, CS#connection_state.secure_renegotiation} of + {_, true} -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, already_secure); + {true, false} -> + ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); + {false, false} -> + {ok, ConnectionStates} + end. + +hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo, + srp = SRP, + signature_algs = HashSigns, + ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves, + alpn = ALPN, + next_protocol_negotiation = NextProtocolNegotiation, + sni = Sni}) -> + [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns, + EcPointFormats, EllipticCurves, ALPN, NextProtocolNegotiation, Sni], Ext =/= undefined]. + +srp_user(#ssl_options{srp_identity = {UserName, _}}) -> + #srp{username = UserName}; +srp_user(_) -> + undefined. + +client_ecc_extensions(Module, Version) -> + CryptoSupport = proplists:get_value(public_keys, crypto:supports()), + case proplists:get_bool(ecdh, CryptoSupport) of + true -> + EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, + EllipticCurves = #elliptic_curves{elliptic_curve_list = Module:ecc_curves(Version)}, + {EcPointFormats, EllipticCurves}; + _ -> + {undefined, undefined} + end. + +server_ecc_extension(_Version, EcPointFormats) -> + CryptoSupport = proplists:get_value(public_keys, crypto:supports()), + case proplists:get_bool(ecdh, CryptoSupport) of + true -> + handle_ecc_point_fmt_extension(EcPointFormats); + false -> + undefined + end. + +handle_ecc_point_fmt_extension(undefined) -> + undefined; +handle_ecc_point_fmt_extension(_) -> + #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. + +advertises_ec_ciphers([]) -> + false; +advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdhe_ecdsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdh_rsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdhe_rsa, _,_,_} | _]) -> + true; +advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) -> + true; +advertises_ec_ciphers([_| Rest]) -> + advertises_ec_ciphers(Rest). +select_curve(#elliptic_curves{elliptic_curve_list = ClientCurves}, + #elliptic_curves{elliptic_curve_list = ServerCurves}) -> + select_curve(ClientCurves, ServerCurves); +select_curve(undefined, _) -> + %% Client did not send ECC extension use default curve if + %% ECC cipher is negotiated + {namedCurve, ?secp256r1}; +select_curve(_, []) -> + no_curve; +select_curve(Curves, [Curve| Rest]) -> + case lists:member(Curve, Curves) of + true -> + {namedCurve, Curve}; + false -> + select_curve(Curves, Rest) + end. +%% RFC 6066, Section 3: Currently, the only server names supported are +%% DNS hostnames +sni(_, disable) -> + undefined; +sni(Host, undefined) -> + sni1(Host); +sni(_Host, SNIOption) -> + sni1(SNIOption). + +sni1(Hostname) -> + case inet_parse:domain(Hostname) of + false -> undefined; + true -> #sni{hostname = Hostname} + end. +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +validation_fun_and_state({Fun, UserState0}, Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle) -> + {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> + case ssl_certificate:validate(OtpCert, + Extension, + SslState) of + {valid, NewSslState} -> + {valid, {NewSslState, UserState}}; + {fail, Reason} -> + apply_user_fun(Fun, OtpCert, Reason, UserState, + SslState); + {unknown, _} -> + apply_user_fun(Fun, OtpCert, + Extension, UserState, SslState) + end; + (OtpCert, VerifyResult, {SslState, UserState}) -> + apply_user_fun(Fun, OtpCert, VerifyResult, UserState, + SslState) + end, {{Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle}, UserState0}}; +validation_fun_and_state(undefined, Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle) -> + {fun(OtpCert, {extension, _} = Extension, SslState) -> + ssl_certificate:validate(OtpCert, + Extension, + SslState); + (OtpCert, VerifyResult, SslState) when (VerifyResult == valid) or (VerifyResult == valid_peer) -> + case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, CRLDbHandle, VerifyResult) of + valid -> + {VerifyResult, SslState}; + Reason -> + {fail, Reason} + end; + (OtpCert, VerifyResult, SslState) -> + ssl_certificate:validate(OtpCert, + VerifyResult, + SslState) + end, {Role, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle}}. + +apply_user_fun(Fun, OtpCert, VerifyResult, UserState0, + {_, CertDbHandle, CertDbRef, CRLCheck, CRLDbHandle} = SslState) when + (VerifyResult == valid) or (VerifyResult == valid_peer) -> + case Fun(OtpCert, VerifyResult, UserState0) of + {Valid, UserState} when (Valid == valid) or (Valid == valid_peer) -> + case crl_check(OtpCert, CRLCheck, CertDbHandle, CertDbRef, CRLDbHandle, VerifyResult) of + valid -> + {Valid, {SslState, UserState}}; + Result -> + apply_user_fun(Fun, OtpCert, Result, UserState, SslState) + end; + {fail, _} = Fail -> + Fail + end; +apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState) -> + case Fun(OtpCert, ExtensionOrError, UserState0) of + {Valid, UserState} when (Valid == valid) or (Valid == valid_peer)-> + {Valid, {SslState, UserState}}; + {fail, _} = Fail -> + Fail; + {unknown, UserState} -> + {unknown, {SslState, UserState}} + end. + +path_validation_alert({bad_cert, cert_expired}) -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); +path_validation_alert({bad_cert, invalid_issuer}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, invalid_signature}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, name_not_permitted}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, unknown_critical_extension}) -> + ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); +path_validation_alert({bad_cert, {revoked, _}}) -> + ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); +path_validation_alert({bad_cert, revocation_status_undetermined}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, selfsigned_peer}) -> + ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); +path_validation_alert({bad_cert, unknown_ca}) -> + ?ALERT_REC(?FATAL, ?UNKNOWN_CA); +path_validation_alert(Reason) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason). + +encrypted_premaster_secret(Secret, RSAPublicKey) -> + try + PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey, + [{rsa_pad, + rsa_pkcs1_padding}]), + #encrypted_premaster_secret{premaster_secret = PreMasterSecret} + catch + _:_-> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, premaster_encryption_failed)) + end. + +digitally_signed(Version, Hashes, HashAlgo, PrivateKey) -> + try do_digitally_signed(Version, Hashes, HashAlgo, PrivateKey) of + Signature -> + Signature + catch + error:badkey-> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, bad_key(PrivateKey))) + end. + +do_digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> + public_key:sign({digest, Hash}, HashAlgo, Key); +do_digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) -> + public_key:sign({digest, Hash}, HashAlgo, Key); +do_digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> + public_key:encrypt_private(Hash, Key, + [{rsa_pad, rsa_pkcs1_padding}]); +do_digitally_signed(_Version, Hash, HashAlgo, Key) -> + public_key:sign({digest, Hash}, HashAlgo, Key). + +calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) -> + ssl_v3:certificate_verify(HashAlgo, MasterSecret, lists:reverse(Handshake)); +calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) -> + tls_v1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)). + +calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> + ssl_v3:finished(Role, MasterSecret, lists:reverse(Handshake)); +calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> + tls_v1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). + +master_secret(_RecordCB, Version, MasterSecret, + #security_parameters{ + bulk_cipher_algorithm = BCA, + client_random = ClientRandom, + server_random = ServerRandom, + hash_size = HashSize, + prf_algorithm = PrfAlgo, + key_material_length = KML, + expanded_key_material_length = EKML, + iv_size = IVS}, + ConnectionStates, Role) -> + {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, + ServerWriteKey, ClientIV, ServerIV} = + setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, + ClientRandom, HashSize, KML, EKML, IVS), + + ConnStates1 = ssl_record:set_master_secret(MasterSecret, ConnectionStates), + ConnStates2 = + ssl_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, + Role, ConnStates1), + + ClientCipherState = ssl_cipher:cipher_init(BCA, ClientIV, ClientWriteKey), + ServerCipherState = ssl_cipher:cipher_init(BCA, ServerIV, ServerWriteKey), + {MasterSecret, + ssl_record:set_pending_cipher_state(ConnStates2, ClientCipherState, + ServerCipherState, Role)}. + +setup_keys({3,0}, _PrfAlgo, MasterSecret, + ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) -> + ssl_v3:setup_keys(MasterSecret, ServerRandom, + ClientRandom, HashSize, KML, EKML, IVS); + +setup_keys({3,N}, PrfAlgo, MasterSecret, + ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) -> + tls_v1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, + KML, IVS). + +calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> + ssl_v3:master_secret(PremasterSecret, ClientRandom, ServerRandom); + +calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> + tls_v1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom). + +handle_renegotiation_extension(Role, RecordCB, Version, Info, Random, NegotiatedCipherSuite, + ClientCipherSuites, Compression, + ConnectionStates0, Renegotiation, SecureRenegotation) -> + case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0, + Renegotiation, SecureRenegotation, + ClientCipherSuites) of + {ok, ConnectionStates} -> + hello_pending_connection_states(RecordCB, Role, + Version, + NegotiatedCipherSuite, + Random, + Compression, + ConnectionStates); + #alert{} = Alert -> + throw(Alert) + end. + +%% Update pending connection states with parameters exchanged via +%% hello messages +%% NOTE : Role is the role of the receiver of the hello message +%% currently being processed. +hello_pending_connection_states(_RecordCB, Role, Version, CipherSuite, Random, Compression, + ConnectionStates) -> + ReadState = + ssl_record:pending_connection_state(ConnectionStates, read), + WriteState = + ssl_record:pending_connection_state(ConnectionStates, write), + + NewReadSecParams = + hello_security_parameters(Role, Version, ReadState, CipherSuite, + Random, Compression), + + NewWriteSecParams = + hello_security_parameters(Role, Version, WriteState, CipherSuite, + Random, Compression), + + ssl_record:set_security_params(NewReadSecParams, + NewWriteSecParams, + ConnectionStates). + +hello_security_parameters(client, Version, ConnectionState, CipherSuite, Random, + Compression) -> + SecParams = ConnectionState#connection_state.security_parameters, + NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), + NewSecParams#security_parameters{ + server_random = Random, + compression_algorithm = Compression + }; + +hello_security_parameters(server, Version, ConnectionState, CipherSuite, Random, + Compression) -> + SecParams = ConnectionState#connection_state.security_parameters, + NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), + NewSecParams#security_parameters{ + client_random = Random, + compression_algorithm = Compression + }. + +%%-------------Encode/Decode -------------------------------- + +encode_server_key(#server_dh_params{dh_p = P, dh_g = G, dh_y = Y}) -> + PLen = byte_size(P), + GLen = byte_size(G), + YLen = byte_size(Y), + <<?UINT16(PLen), P/binary, ?UINT16(GLen), G/binary, ?UINT16(YLen), Y/binary>>; +encode_server_key(#server_ecdh_params{curve = {namedCurve, ECCurve}, public = ECPubKey}) -> + %%TODO: support arbitrary keys + KLen = size(ECPubKey), + <<?BYTE(?NAMED_CURVE), ?UINT16((tls_v1:oid_to_enum(ECCurve))), + ?BYTE(KLen), ECPubKey/binary>>; +encode_server_key(#server_psk_params{hint = PskIdentityHint}) -> + Len = byte_size(PskIdentityHint), + <<?UINT16(Len), PskIdentityHint/binary>>; +encode_server_key(Params = #server_dhe_psk_params{hint = undefined}) -> + encode_server_key(Params#server_dhe_psk_params{hint = <<>>}); +encode_server_key(#server_dhe_psk_params{ + hint = PskIdentityHint, + dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}}) -> + Len = byte_size(PskIdentityHint), + PLen = byte_size(P), + GLen = byte_size(G), + YLen = byte_size(Y), + <<?UINT16(Len), PskIdentityHint/binary, + ?UINT16(PLen), P/binary, ?UINT16(GLen), G/binary, ?UINT16(YLen), Y/binary>>; +encode_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}) -> + NLen = byte_size(N), + GLen = byte_size(G), + SLen = byte_size(S), + BLen = byte_size(B), + <<?UINT16(NLen), N/binary, ?UINT16(GLen), G/binary, + ?BYTE(SLen), S/binary, ?UINT16(BLen), B/binary>>. + +encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) -> + PKEPMS; +encode_client_key(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) -> + PKEPMSLen = byte_size(PKEPMS), + <<?UINT16(PKEPMSLen), PKEPMS/binary>>; +encode_client_key(#client_diffie_hellman_public{dh_public = DHPublic}, _) -> + Len = byte_size(DHPublic), + <<?UINT16(Len), DHPublic/binary>>; +encode_client_key(#client_ec_diffie_hellman_public{dh_public = DHPublic}, _) -> + Len = byte_size(DHPublic), + <<?BYTE(Len), DHPublic/binary>>; +encode_client_key(#client_psk_identity{identity = undefined}, _) -> + Id = <<"psk_identity">>, + Len = byte_size(Id), + <<?UINT16(Len), Id/binary>>; +encode_client_key(#client_psk_identity{identity = Id}, _) -> + Len = byte_size(Id), + <<?UINT16(Len), Id/binary>>; +encode_client_key(Identity = #client_dhe_psk_identity{identity = undefined}, Version) -> + encode_client_key(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>}, Version); +encode_client_key(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}, _) -> + Len = byte_size(Id), + DHLen = byte_size(DHPublic), + <<?UINT16(Len), Id/binary, ?UINT16(DHLen), DHPublic/binary>>; +encode_client_key(Identity = #client_rsa_psk_identity{identity = undefined}, Version) -> + encode_client_key(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version); +encode_client_key(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) -> + EncPMS = encode_client_key(ExchangeKeys, Version), + Len = byte_size(Id), + <<?UINT16(Len), Id/binary, EncPMS/binary>>; +encode_client_key(#client_srp_public{srp_a = A}, _) -> + Len = byte_size(A), + <<?UINT16(Len), A/binary>>. + +enc_sign({_, anon}, _Sign, _Version) -> + <<>>; +enc_sign({HashAlg, SignAlg}, Signature, _Version = {Major, Minor}) + when Major == 3, Minor >= 3-> + SignLen = byte_size(Signature), + HashSign = enc_hashsign(HashAlg, SignAlg), + <<HashSign/binary, ?UINT16(SignLen), Signature/binary>>; +enc_sign(_HashSign, Sign, _Version) -> + SignLen = byte_size(Sign), + <<?UINT16(SignLen), Sign/binary>>. + +enc_hashsign(HashAlgo, SignAlgo) -> + Hash = ssl_cipher:hash_algorithm(HashAlgo), + Sign = ssl_cipher:sign_algorithm(SignAlgo), + <<?BYTE(Hash), ?BYTE(Sign)>>. + +encode_protocol(Protocol, Acc) -> + Len = byte_size(Protocol), + <<Acc/binary, ?BYTE(Len), Protocol/binary>>. + +dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) -> + #encrypted_premaster_secret{premaster_secret = PKEPMS}; +dec_client_key(<<?UINT16(_), PKEPMS/binary>>, ?KEY_EXCHANGE_RSA, _) -> + #encrypted_premaster_secret{premaster_secret = PKEPMS}; +dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> + throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public)); +dec_client_key(<<?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>, + ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> + #client_diffie_hellman_public{dh_public = DH_Y}; +dec_client_key(<<>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> + throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE, empty_dh_public)); +dec_client_key(<<?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>, + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> + #client_ec_diffie_hellman_public{dh_public = DH_Y}; +dec_client_key(<<?UINT16(Len), Id:Len/binary>>, + ?KEY_EXCHANGE_PSK, _) -> + #client_psk_identity{identity = Id}; +dec_client_key(<<?UINT16(Len), Id:Len/binary, + ?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>, + ?KEY_EXCHANGE_DHE_PSK, _) -> + #client_dhe_psk_identity{identity = Id, dh_public = DH_Y}; +dec_client_key(<<?UINT16(Len), Id:Len/binary, PKEPMS/binary>>, + ?KEY_EXCHANGE_RSA_PSK, {3, 0}) -> + #client_rsa_psk_identity{identity = Id, + exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; +dec_client_key(<<?UINT16(Len), Id:Len/binary, ?UINT16(_), PKEPMS/binary>>, + ?KEY_EXCHANGE_RSA_PSK, _) -> + #client_rsa_psk_identity{identity = Id, + exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; +dec_client_key(<<?UINT16(ALen), A:ALen/binary>>, + ?KEY_EXCHANGE_SRP, _) -> + #client_srp_public{srp_a = A}. + +dec_server_key_params(Len, Keys, Version) -> + <<Params:Len/bytes, Signature/binary>> = Keys, + dec_server_key_signature(Params, Signature, Version). + +dec_server_key_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo), + ?UINT16(0)>>, {Major, Minor}) + when Major == 3, Minor >= 3 -> + HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, + {Params, HashSign, <<>>}; +dec_server_key_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo), + ?UINT16(Len), Signature:Len/binary>>, {Major, Minor}) + when Major == 3, Minor >= 3 -> + HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, + {Params, HashSign, Signature}; +dec_server_key_signature(Params, <<>>, _) -> + {Params, {null, anon}, <<>>}; +dec_server_key_signature(Params, <<?UINT16(0)>>, _) -> + {Params, {null, anon}, <<>>}; +dec_server_key_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) -> + {Params, undefined, Signature}; +dec_server_key_signature(_, _, _) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, failed_to_decrypt_server_key_sign)). + +dec_hello_extensions(<<>>, Acc) -> + Acc; +dec_hello_extensions(<<?UINT16(?ALPN_EXT), ?UINT16(ExtLen), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) + when Len + 2 =:= ExtLen -> + ALPN = #alpn{extension_data = ExtensionData}, + dec_hello_extensions(Rest, Acc#hello_extensions{alpn = ALPN}); +dec_hello_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) -> + NextP = #next_protocol_negotiation{extension_data = ExtensionData}, + dec_hello_extensions(Rest, Acc#hello_extensions{next_protocol_negotiation = NextP}); +dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) -> + RenegotiateInfo = case Len of + 1 -> % Initial handshake + Info; % should be <<0>> will be matched in handle_renegotiation_info + _ -> + VerifyLen = Len - 1, + <<?BYTE(VerifyLen), VerifyInfo/binary>> = Info, + VerifyInfo + end, + dec_hello_extensions(Rest, Acc#hello_extensions{renegotiation_info = + #renegotiation_info{renegotiated_connection = + RenegotiateInfo}}); + +dec_hello_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), SRP:SRPLen/binary, Rest/binary>>, Acc) + when Len == SRPLen + 2 -> + dec_hello_extensions(Rest, Acc#hello_extensions{srp = #srp{username = SRP}}); + +dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Acc) -> + SignAlgoListLen = Len - 2, + <<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData, + HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || + <<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList], + dec_hello_extensions(Rest, Acc#hello_extensions{signature_algs = + #hash_sign_algos{hash_sign_algos = HashSignAlgos}}); + +dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Acc) -> + <<?UINT16(_), EllipticCurveList/binary>> = ExtData, + %% Ignore unknown curves + Pick = fun(Enum) -> + case tls_v1:enum_to_oid(Enum) of + undefined -> + false; + Oid -> + {true, Oid} + end + end, + EllipticCurves = lists:filtermap(Pick, [ECC || <<ECC:16>> <= EllipticCurveList]), + dec_hello_extensions(Rest, Acc#hello_extensions{elliptic_curves = + #elliptic_curves{elliptic_curve_list = + EllipticCurves}}); +dec_hello_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Acc) -> + <<?BYTE(_), ECPointFormatList/binary>> = ExtData, + ECPointFormats = binary_to_list(ECPointFormatList), + dec_hello_extensions(Rest, Acc#hello_extensions{ec_point_formats = + #ec_point_formats{ec_point_format_list = + ECPointFormats}}); + +dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), Rest/binary>>, Acc) when Len == 0 -> + dec_hello_extensions(Rest, Acc#hello_extensions{sni = ""}); %% Server may send an empy SNI + +dec_hello_extensions(<<?UINT16(?SNI_EXT), ?UINT16(Len), + ExtData:Len/binary, Rest/binary>>, Acc) -> + <<?UINT16(_), NameList/binary>> = ExtData, + dec_hello_extensions(Rest, Acc#hello_extensions{sni = dec_sni(NameList)}); +%% Ignore data following the ClientHello (i.e., +%% extensions) if not understood. + +dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) -> + dec_hello_extensions(Rest, Acc); +%% This theoretically should not happen if the protocol is followed, but if it does it is ignored. +dec_hello_extensions(_, Acc) -> + Acc. + +dec_hashsign(<<?BYTE(HashAlgo), ?BYTE(SignAlgo)>>) -> + {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}. + +%% Ignore unknown names (only host_name is supported) +dec_sni(<<?BYTE(?SNI_NAMETYPE_HOST_NAME), ?UINT16(Len), + HostName:Len/binary, _/binary>>) -> + #sni{hostname = binary_to_list(HostName)}; +dec_sni(<<?BYTE(_), ?UINT16(Len), _:Len, Rest/binary>>) -> dec_sni(Rest); +dec_sni(_) -> undefined. + +decode_next_protocols({next_protocol_negotiation, Protocols}) -> + decode_protocols(Protocols, []). + +decode_protocols(<<>>, Acc) -> + lists:reverse(Acc); +decode_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> + case Len of + 0 -> + {error, invalid_protocols}; + _ -> + decode_protocols(Rest, [Protocol|Acc]) + end; +decode_protocols(_Bytes, _Acc) -> + {error, invalid_protocols}. + +%% encode/decode stream of certificate data to/from list of certificate data +certs_to_list(ASN1Certs) -> + certs_to_list(ASN1Certs, []). + +certs_to_list(<<?UINT24(CertLen), Cert:CertLen/binary, Rest/binary>>, Acc) -> + certs_to_list(Rest, [Cert | Acc]); +certs_to_list(<<>>, Acc) -> + lists:reverse(Acc, []). + +certs_from_list(ACList) -> + list_to_binary([begin + CertLen = byte_size(Cert), + <<?UINT24(CertLen), Cert/binary>> + end || Cert <- ACList]). +from_3bytes(Bin3) -> + from_3bytes(Bin3, []). + +from_3bytes(<<>>, Acc) -> + lists:reverse(Acc); +from_3bytes(<<?UINT24(N), Rest/binary>>, Acc) -> + from_3bytes(Rest, [?uint16(N) | Acc]). + +from_2bytes(Bin2) -> + from_2bytes(Bin2, []). + +from_2bytes(<<>>, Acc) -> + lists:reverse(Acc); +from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) -> + from_2bytes(Rest, [?uint16(N) | Acc]). +key_exchange_alg(rsa) -> + ?KEY_EXCHANGE_RSA; +key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; + Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> + ?KEY_EXCHANGE_DIFFIE_HELLMAN; +key_exchange_alg(Alg) when Alg == ecdhe_rsa; Alg == ecdh_rsa; + Alg == ecdhe_ecdsa; Alg == ecdh_ecdsa; + Alg == ecdh_anon -> + ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN; +key_exchange_alg(psk) -> + ?KEY_EXCHANGE_PSK; +key_exchange_alg(dhe_psk) -> + ?KEY_EXCHANGE_DHE_PSK; +key_exchange_alg(rsa_psk) -> + ?KEY_EXCHANGE_RSA_PSK; +key_exchange_alg(Alg) + when Alg == srp_rsa; Alg == srp_dss; Alg == srp_anon -> + ?KEY_EXCHANGE_SRP; +key_exchange_alg(_) -> + ?NULL. + +%%-------------Extension handling -------------------------------- + +%% Receive protocols, choose one from the list, return it. +handle_alpn_extension(_, {error, Reason}) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); +handle_alpn_extension([], _) -> + ?ALERT_REC(?FATAL, ?NO_APPLICATION_PROTOCOL); +handle_alpn_extension([ServerProtocol|Tail], ClientProtocols) -> + case lists:member(ServerProtocol, ClientProtocols) of + true -> ServerProtocol; + false -> handle_alpn_extension(Tail, ClientProtocols) + end. + +handle_next_protocol(undefined, + _NextProtocolSelector, _Renegotiating) -> + undefined; + +handle_next_protocol(#next_protocol_negotiation{} = NextProtocols, + NextProtocolSelector, Renegotiating) -> + + case next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) of + true -> + select_next_protocol(decode_next_protocols(NextProtocols), NextProtocolSelector); + false -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension) + end. + + +handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, SslOpts)-> + case handle_next_protocol_on_server(NextProtocolNegotiation, Renegotiation, SslOpts) of + #alert{} = Alert -> + Alert; + ProtocolsToAdvertise -> + ProtocolsToAdvertise + end. + +handle_next_protocol_on_server(undefined, _Renegotiation, _SslOpts) -> + undefined; + +handle_next_protocol_on_server(#next_protocol_negotiation{extension_data = <<>>}, + false, #ssl_options{next_protocols_advertised = Protocols}) -> + Protocols; + +handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, unexpected_next_protocol_extension). + +next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) -> + NextProtocolSelector =/= undefined andalso not Renegotiating. + +select_next_protocol({error, Reason}, _NextProtocolSelector) -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, Reason); +select_next_protocol(Protocols, NextProtocolSelector) -> + case NextProtocolSelector(Protocols) of + ?NO_PROTOCOL -> + ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, no_next_protocol); + Protocol when is_binary(Protocol) -> + Protocol + end. + +handle_srp_extension(undefined, Session) -> + Session; +handle_srp_extension(#srp{username = Username}, Session) -> + Session#session{srp_username = Username}. + +%%-------------Misc -------------------------------- + +select_cipher_suite(CipherSuites, Suites, false) -> + select_cipher_suite(CipherSuites, Suites); +select_cipher_suite(CipherSuites, Suites, true) -> + select_cipher_suite(Suites, CipherSuites). + +select_cipher_suite([], _) -> + no_suite; +select_cipher_suite([Suite | ClientSuites], SupportedSuites) -> + case is_member(Suite, SupportedSuites) of + true -> + Suite; + false -> + select_cipher_suite(ClientSuites, SupportedSuites) + end. + +int_to_bin(I) -> + L = (length(integer_to_list(I, 16)) + 1) div 2, + <<I:(L*8)>>. + +is_member(Suite, SupportedSuites) -> + lists:member(Suite, SupportedSuites). + +select_compression(_CompressionMetodes) -> + ?NULL. + +available_signature_algs(undefined, _, _) -> + undefined; +available_signature_algs(SupportedHashSigns, {Major, Minor}, AllVersions) when Major >= 3 andalso Minor >= 3 -> + case tls_record:lowest_protocol_version(AllVersions) of + {3, 3} -> + #hash_sign_algos{hash_sign_algos = SupportedHashSigns}; + _ -> + undefined + end; +available_signature_algs(_, _, _) -> + undefined. + +psk_secret(PSKIdentity, PSKLookup) -> + case handle_psk_identity(PSKIdentity, PSKLookup) of + {ok, PSK} when is_binary(PSK) -> + Len = erlang:byte_size(PSK), + <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>; + #alert{} = Alert -> + Alert; + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + +psk_secret(PSKIdentity, PSKLookup, PremasterSecret) -> + case handle_psk_identity(PSKIdentity, PSKLookup) of + {ok, PSK} when is_binary(PSK) -> + Len = erlang:byte_size(PremasterSecret), + PSKLen = erlang:byte_size(PSK), + <<?UINT16(Len), PremasterSecret/binary, ?UINT16(PSKLen), PSK/binary>>; + #alert{} = Alert -> + Alert; + _ -> + throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) + end. + +handle_psk_identity(_PSKIdentity, LookupFun) + when LookupFun == undefined -> + error; +handle_psk_identity(PSKIdentity, {Fun, UserState}) -> + Fun(psk, PSKIdentity, UserState). + +crl_check(_, false, _,_,_, _) -> + valid; +crl_check(_, peer, _, _,_, valid) -> %% Do not check CAs with this option. + valid; +crl_check(OtpCert, Check, CertDbHandle, CertDbRef, {Callback, CRLDbHandle}, _) -> + Options = [{issuer_fun, {fun(_DP, CRL, Issuer, DBInfo) -> + ssl_crl:trusted_cert_and_path(CRL, Issuer, DBInfo) + end, {CertDbHandle, CertDbRef}}}, + {update_crl, fun(DP, CRL) -> Callback:fresh_crl(DP, CRL) end} + ], + case dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) of + no_dps -> + crl_check_same_issuer(OtpCert, Check, + dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer), + Options); + DpsAndCRLs -> %% This DP list may be empty if relevant CRLs existed + %% but could not be retrived, will result in {bad_cert, revocation_status_undetermined} + case public_key:pkix_crls_validate(OtpCert, DpsAndCRLs, Options) of + {bad_cert, revocation_status_undetermined} -> + crl_check_same_issuer(OtpCert, Check, dps_and_crls(OtpCert, Callback, + CRLDbHandle, same_issuer), Options); + Other -> + Other + end + end. + +crl_check_same_issuer(OtpCert, best_effort, Dps, Options) -> + case public_key:pkix_crls_validate(OtpCert, Dps, Options) of + {bad_cert, revocation_status_undetermined} -> + valid; + Other -> + Other + end; +crl_check_same_issuer(OtpCert, _, Dps, Options) -> + public_key:pkix_crls_validate(OtpCert, Dps, Options). + +dps_and_crls(OtpCert, Callback, CRLDbHandle, ext) -> + case public_key:pkix_dist_points(OtpCert) of + [] -> + no_dps; + DistPoints -> + Issuer = OtpCert#'OTPCertificate'.tbsCertificate#'OTPTBSCertificate'.issuer, + distpoints_lookup(DistPoints, Issuer, Callback, CRLDbHandle) + end; + +dps_and_crls(OtpCert, Callback, CRLDbHandle, same_issuer) -> + DP = #'DistributionPoint'{distributionPoint = {fullName, GenNames}} = + public_key:pkix_dist_point(OtpCert), + CRLs = lists:flatmap(fun({directoryName, Issuer}) -> + Callback:select(Issuer, CRLDbHandle); + (_) -> + [] + end, GenNames), + [{DP, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs]. + +distpoints_lookup([], _, _, _) -> + []; +distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) -> + Result = + try Callback:lookup(DistPoint, Issuer, CRLDbHandle) + catch + error:undef -> + %% The callback module still uses the 2-argument + %% version of the lookup function. + Callback:lookup(DistPoint, CRLDbHandle) + end, + case Result of + not_available -> + distpoints_lookup(Rest, Issuer, Callback, CRLDbHandle); + CRLs -> + [{DistPoint, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs] + end. + +cert_sign(?rsaEncryption) -> + rsa; +cert_sign(?'id-ecPublicKey') -> + ecdsa; +cert_sign(?'id-dsa') -> + dsa; +cert_sign(Alg) -> + {_, Sign} =public_key:pkix_sign_types(Alg), + Sign. + +is_acceptable_hash_sign({_, Sign} = Algos, Sign, _, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(Algos,_, KeyExAlgo, SupportedHashSigns) when KeyExAlgo == dh_ecdsa; + KeyExAlgo == ecdh_rsa; + KeyExAlgo == ecdh_ecdsa -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(_,_,_,_) -> + false. +is_acceptable_hash_sign(Algos, SupportedHashSigns) -> + lists:member(Algos, SupportedHashSigns). + +bad_key(#'DSAPrivateKey'{}) -> + unacceptable_dsa_key; +bad_key(#'RSAPrivateKey'{}) -> + unacceptable_rsa_key; +bad_key(#'ECPrivateKey'{}) -> + unacceptable_ecdsa_key. + +available_signature_algs(undefined, SupportedHashSigns, _, {Major, Minor}) when + (Major >= 3) andalso (Minor >= 3) -> + SupportedHashSigns; +available_signature_algs(#hash_sign_algos{hash_sign_algos = ClientHashSigns}, SupportedHashSigns, + _, {Major, Minor}) when (Major >= 3) andalso (Minor >= 3) -> + sets:to_list(sets:intersection(sets:from_list(ClientHashSigns), + sets:from_list(SupportedHashSigns))); +available_signature_algs(_, _, _, _) -> + undefined. + diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl index eb1a1dbf62..fde92035a2 100644 --- a/lib/ssl/src/ssl_handshake.hrl +++ b/lib/ssl/src/ssl_handshake.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -28,11 +29,6 @@ -include_lib("public_key/include/public_key.hrl"). --type oid() :: tuple(). --type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term(). --type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}. --type tls_handshake_history() :: {[binary()], [binary()]}. - -define(NO_PROTOCOL, <<>>). %% Signature algorithms @@ -50,13 +46,15 @@ master_secret, srp_username, is_resumable, - time_stamp + time_stamp, + ecc }). -define(NUM_OF_SESSION_ID_BYTES, 32). % TSL 1.1 & SSL 3 -define(NUM_OF_PREMASTERSECRET_BYTES, 48). -define(DEFAULT_DIFFIE_HELLMAN_GENERATOR, 2). --define(DEFAULT_DIFFIE_HELLMAN_PRIME, 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF). +-define(DEFAULT_DIFFIE_HELLMAN_PRIME, + 16#FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Handsake protocol - RFC 4346 section 7.4 @@ -96,17 +94,24 @@ %% client_hello defined in tls_handshake.hrl and dtls_handshake.hrl +-record(hello_extensions, { + renegotiation_info, + signature_algs, % supported combinations of hashes/signature algos + alpn, + next_protocol_negotiation = undefined, % [binary()] + srp, + ec_point_formats, + elliptic_curves, + sni + }). + -record(server_hello, { server_version, random, session_id, % opaque SessionID<0..32> cipher_suite, % cipher_suites compression_method, % compression_method - renegotiation_info, - hash_signs, % supported combinations of hashes/signature algos - ec_point_formats, % supported ec point formats - elliptic_curves, % supported elliptic curver - next_protocol_negotiation = undefined % [binary()] + extensions }). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -299,6 +304,14 @@ }). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Application-Layer Protocol Negotiation RFC 7301 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(ALPN_EXT, 16). + +-record(alpn, {extension_data}). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Next Protocol Negotiation %% (http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-02) %% (http://technotes.googlecode.com/git/nextprotoneg.html) @@ -337,7 +350,17 @@ -define(EXPLICIT_CHAR2, 2). -define(NAMED_CURVE, 3). --endif. % -ifdef(ssl_handshake). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% Server name indication RFC 6066 section 3 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-define(SNI_EXT, 16#0000). +%% enum { host_name(0), (255) } NameType; +-define(SNI_NAMETYPE_HOST_NAME, 0). - +-record(sni, { + hostname = undefined + }). + +-endif. % -ifdef(ssl_handshake). diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl index 14db4a6067..c19c1787ff 100644 --- a/lib/ssl/src/ssl_internal.hrl +++ b/lib/ssl/src/ssl_internal.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -24,8 +25,7 @@ -include_lib("public_key/include/public_key.hrl"). -%% Looks like it does for backwards compatibility reasons --record(sslsocket, {fd = nil, pid = nil}). +-define(SECRET_PRINTOUT, "***"). -type reason() :: term(). -type reply() :: term(). @@ -33,16 +33,13 @@ -type from() :: term(). -type host() :: inet:ip_address() | inet:hostname(). -type session_id() :: 0 | binary(). --type tls_version() :: {integer(), integer()}. --type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'. -type certdb_ref() :: reference(). -type db_handle() :: term(). --type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. -type der_cert() :: binary(). --type private_key() :: #'RSAPrivateKey'{} | #'DSAPrivateKey'{} | #'ECPrivateKey'{}. -type issuer() :: tuple(). -type serialnumber() :: integer(). -type cert_key() :: {reference(), integer(), issuer()}. +-type secret_printout() :: list(). %% basic binary constructors -define(BOOLEAN(X), X:8/unsigned-big-integer). @@ -50,6 +47,7 @@ -define(UINT16(X), X:16/unsigned-big-integer). -define(UINT24(X), X:24/unsigned-big-integer). -define(UINT32(X), X:32/unsigned-big-integer). +-define(UINT48(X), X:48/unsigned-big-integer). -define(UINT64(X), X:64/unsigned-big-integer). -define(STRING(X), ?UINT32((size(X))), (X)/binary). @@ -57,41 +55,54 @@ -define(uint16(X), << ?UINT16(X) >> ). -define(uint24(X), << ?UINT24(X) >> ). -define(uint32(X), << ?UINT32(X) >> ). +-define(uint48(X), << ?UINT48(X) >> ). -define(uint64(X), << ?UINT64(X) >> ). -define(CDR_MAGIC, "GIOP"). -define(CDR_HDR_SIZE, 12). -define(DEFAULT_TIMEOUT, 5000). +-define(NO_DIST_POINT, "http://dummy/no_distribution_point"). +-define(NO_DIST_POINT_PATH, "dummy/no_distribution_point"). %% Common enumerate values in for SSL-protocols -define(NULL, 0). -define(TRUE, 0). -define(FALSE, 1). --define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). --define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1, sslv3]). +%% sslv3 is considered insecure due to lack of padding check (Poodle attack) +%% Keep as interop with legacy software but do not support as default +-define(ALL_AVAILABLE_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]). +-define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1]). +-define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1]). +-define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]). +-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]). + +-define('24H_in_msec', 86400000). +-define('24H_in_sec', 86400). -record(ssl_options, { - versions, % 'tlsv1.2' | 'tlsv1.1' | tlsv1 | sslv3 - verify, % verify_none | verify_peer - verify_fun, % fun(CertVerifyErrors) -> boolean() - fail_if_no_peer_cert, % boolean() - verify_client_once, % boolean() + protocol :: tls | dtls, + versions :: [ssl_record:ssl_version()], %% ssl_record:atom_version() in API + verify :: verify_none | verify_peer, + verify_fun, %%:: fun(CertVerifyErrors::term()) -> boolean(), + partial_chain :: fun(), + fail_if_no_peer_cert :: boolean(), + verify_client_once :: boolean(), %% fun(Extensions, State, Verify, AccError) -> {Extensions, State, AccError} validate_extensions_fun, - depth, % integer() - certfile, % file() - cert, % der_encoded() - keyfile, % file() - key, % der_encoded() - password, % - cacerts, % [der_encoded()] - cacertfile, % file() - dh, % der_encoded() - dhfile, % file() + depth :: integer(), + certfile :: binary(), + cert :: public_key:der_encoded() | secret_printout() | 'undefined', + keyfile :: binary(), + key :: {'RSAPrivateKey' | 'DSAPrivateKey' | 'ECPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()} | secret_printout() | 'undefined', + password :: string() | secret_printout() | 'undefined', + cacerts :: [public_key:der_encoded()] | secret_printout() | 'undefined', + cacertfile :: binary(), + dh :: public_key:der_encoded() | secret_printout(), + dhfile :: binary() | secret_printout() | 'undefined', user_lookup_fun, % server option, fun to lookup the user - psk_identity, % binary + psk_identity :: binary() | secret_printout() | 'undefined', srp_identity, % client option {User, Password} ciphers, % %% Local policy for the server if it want's to reuse the session @@ -100,18 +111,36 @@ reuse_session, %% If false sessions will never be reused, if true they %% will be reused if possible. - reuse_sessions, % boolean() + reuse_sessions :: boolean(), renegotiate_at, secure_renegotiate, - debug, - hibernate_after,% undefined if not hibernating, - % or number of ms of inactivity - % after which ssl_connection will - % go into hibernation + client_renegotiation, + %% undefined if not hibernating, or number of ms of + %% inactivity after which ssl_connection will go into + %% hibernation + hibernate_after :: timeout(), %% This option should only be set to true by inet_tls_dist - erl_dist = false, - next_protocols_advertised = undefined, %% [binary()], - next_protocol_selector = undefined %% fun([binary()]) -> binary()) + erl_dist = false :: boolean(), + alpn_advertised_protocols = undefined :: [binary()] | undefined , + alpn_preferred_protocols = undefined :: [binary()] | undefined, + next_protocols_advertised = undefined :: [binary()] | undefined, + next_protocol_selector = undefined, %% fun([binary()]) -> binary()) + log_alert :: boolean(), + server_name_indication = undefined, + sni_hosts :: [{inet:hostname(), [tuple()]}], + sni_fun :: function() | undefined, + %% Should the server prefer its own cipher order over the one provided by + %% the client? + honor_cipher_order = false :: boolean(), + padding_check = true :: boolean(), + %%Should we use 1/n-1 or 0/n splitting to mitigate BEAST, or disable + %%mitigation entirely? + beast_mitigation = one_n_minus_one :: one_n_minus_one | zero_n | disabled, + fallback = false :: boolean(), + crl_check :: boolean() | peer | best_effort, + crl_cache, + signature_algs, + v2_hello_compatible :: boolean() }). -record(socket_options, @@ -123,6 +152,19 @@ active = true }). +-record(config, {ssl, %% SSL parameters + inet_user, %% User set inet options + emulated, %% Emulated option list or "inherit_tracker" pid + inet_ssl, %% inet options for internal ssl socket + transport_info, %% Callback info + connection_cb + }). + + +-type state_name() :: hello | abbreviated | certify | cipher | connection. +-type gen_fsm_state_return() :: {next_state, state_name(), term()} | + {next_state, state_name(), term(), timeout()} | + {stop, term(), term()}. -endif. % -ifdef(ssl_internal). diff --git a/lib/ssl/src/ssl_listen_tracker_sup.erl b/lib/ssl/src/ssl_listen_tracker_sup.erl new file mode 100644 index 0000000000..7f685a2ead --- /dev/null +++ b/lib/ssl/src/ssl_listen_tracker_sup.erl @@ -0,0 +1,72 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Supervisor for a listen options tracker +%%---------------------------------------------------------------------- +-module(ssl_listen_tracker_sup). + +-behaviour(supervisor). + +%% API +-export([start_link/0, start_link_dist/0]). +-export([start_child/1, start_child_dist/1]). + +%% Supervisor callback +-export([init/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +start_link() -> + supervisor:start_link({local, tracker_name(normal)}, ?MODULE, []). + +start_link_dist() -> + supervisor:start_link({local, tracker_name(dist)}, ?MODULE, []). + +start_child(Args) -> + supervisor:start_child(tracker_name(normal), Args). + +start_child_dist(Args) -> + supervisor:start_child(tracker_name(dist), Args). + +%%%========================================================================= +%%% Supervisor callback +%%%========================================================================= +init(_O) -> + RestartStrategy = simple_one_for_one, + MaxR = 0, + MaxT = 3600, + + Name = undefined, % As simple_one_for_one is used. + StartFunc = {ssl_socket, start_link, []}, + Restart = temporary, % E.g. should not be restarted + Shutdown = 4000, + Modules = [ssl_socket], + Type = worker, + + ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, + {ok, {{RestartStrategy, MaxR, MaxT}, [ChildSpec]}}. + +tracker_name(normal) -> + ?MODULE; +tracker_name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "dist"). diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index 7af4a68461..c7dcbaabe9 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -26,14 +27,15 @@ %% Internal application API -export([start_link/1, start_link_dist/1, - connection_init/2, cache_pem_file/2, + connection_init/3, cache_pem_file/2, lookup_trusted_cert/4, new_session_id/1, clean_cert_db/2, register_session/2, register_session/3, invalidate_session/2, - invalidate_session/3, clear_pem_cache/0]). + insert_crls/2, insert_crls/3, delete_crls/1, delete_crls/2, + invalidate_session/3, invalidate_pem/1, clear_pem_cache/0, manager_name/1]). % Spawn export --export([init_session_validator/1]). +-export([init_session_validator/1, init_pem_cache_validator/1]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -44,26 +46,44 @@ -include_lib("kernel/include/file.hrl"). -record(state, { - session_cache, - session_cache_cb, - session_lifetime, - certificate_db, - session_validation_timer, - last_delay_timer = {undefined, undefined}%% Keep for testing purposes + session_cache_client :: db_handle(), + session_cache_server :: db_handle(), + session_cache_cb :: atom(), + session_lifetime :: integer(), + certificate_db :: db_handle(), + session_validation_timer :: reference(), + last_delay_timer = {undefined, undefined},%% Keep for testing purposes + last_pem_check :: erlang:timestamp(), + clear_pem_cache :: integer(), + session_cache_client_max :: integer(), + session_cache_server_max :: integer(), + session_server_invalidator :: undefined | pid(), + session_client_invalidator :: undefined | pid() }). --define('24H_in_msec', 8640000). --define('24H_in_sec', 8640). -define(GEN_UNIQUE_ID_MAX_TRIES, 10). -define(SESSION_VALIDATION_INTERVAL, 60000). -define(CLEAR_PEM_CACHE, 120000). -define(CLEAN_SESSION_DB, 60000). -define(CLEAN_CERT_DB, 500). --define(NOT_TO_BIG, 10). +-define(DEFAULT_MAX_SESSION_CACHE, 1000). +-define(LOAD_MITIGATION, 10). %%==================================================================== %% API %%==================================================================== + +%%-------------------------------------------------------------------- +-spec manager_name(normal | dist) -> atom(). +%% +%% Description: Returns the registered name of the ssl manager process +%% in the operation modes 'normal' and 'dist'. +%%-------------------------------------------------------------------- +manager_name(normal) -> + ?MODULE; +manager_name(dist) -> + list_to_atom(atom_to_list(?MODULE) ++ "dist"). + %%-------------------------------------------------------------------- -spec start_link(list()) -> {ok, pid()} | ignore | {error, term()}. %% @@ -71,7 +91,9 @@ %% and certificate caching. %%-------------------------------------------------------------------- start_link(Opts) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [?MODULE, Opts], []). + DistMangerName = manager_name(normal), + gen_server:start_link({local, DistMangerName}, + ?MODULE, [DistMangerName, Opts], []). %%-------------------------------------------------------------------- -spec start_link_dist(list()) -> {ok, pid()} | ignore | {error, term()}. @@ -80,37 +102,40 @@ start_link(Opts) -> %% be used by the erlang distribution. Note disables soft upgrade! %%-------------------------------------------------------------------- start_link_dist(Opts) -> - gen_server:start_link({local, ssl_manager_dist}, ?MODULE, [ssl_manager_dist, Opts], []). + DistMangerName = manager_name(dist), + gen_server:start_link({local, DistMangerName}, + ?MODULE, [DistMangerName, Opts], []). %%-------------------------------------------------------------------- --spec connection_init(binary()| {der, list()}, client | server) -> - {ok, certdb_ref(), db_handle(), db_handle(), db_handle(), db_handle()}. +-spec connection_init(binary()| {der, list()}, client | server, + {Cb :: atom(), Handle:: term()}) -> + {ok, certdb_ref(), db_handle(), db_handle(), + db_handle(), db_handle(), CRLInfo::term()}. %% %% Description: Do necessary initializations for a new connection. %%-------------------------------------------------------------------- -connection_init({der, _} = Trustedcerts, Role) -> - call({connection_init, Trustedcerts, Role}); +connection_init({der, _} = Trustedcerts, Role, CRLCache) -> + call({connection_init, Trustedcerts, Role, CRLCache}); -connection_init(<<>> = Trustedcerts, Role) -> - call({connection_init, Trustedcerts, Role}); +connection_init(<<>> = Trustedcerts, Role, CRLCache) -> + call({connection_init, Trustedcerts, Role, CRLCache}); -connection_init(Trustedcerts, Role) -> - call({connection_init, Trustedcerts, Role}). +connection_init(Trustedcerts, Role, CRLCache) -> + call({connection_init, Trustedcerts, Role, CRLCache}). %%-------------------------------------------------------------------- -spec cache_pem_file(binary(), term()) -> {ok, term()} | {error, reason()}. %% -%% Description: Cach a pem file and return its content. +%% Description: Cache a pem file and return its content. %%-------------------------------------------------------------------- cache_pem_file(File, DbHandle) -> - MD5 = crypto:hash(md5, File), - case ssl_pkix_db:lookup_cached_pem(DbHandle, MD5) of + case ssl_pkix_db:lookup_cached_pem(DbHandle, File) of [{Content,_}] -> {ok, Content}; [Content] -> - {ok, Content}; + {ok, Content}; undefined -> - call({cache_pem, {MD5, File}}) + call({cache_pem, File}) end. %%-------------------------------------------------------------------- @@ -120,7 +145,7 @@ cache_pem_file(File, DbHandle) -> %%-------------------------------------------------------------------- clear_pem_cache() -> %% Not supported for distribution at the moement, should it be? - put(ssl_manager, ssl_manager), + put(ssl_manager, manager_name(normal)), call(unconditionally_clear_pem_cache). %%-------------------------------------------------------------------- @@ -149,34 +174,59 @@ new_session_id(Port) -> %% be called by ssl-connection processes. %%-------------------------------------------------------------------- clean_cert_db(Ref, File) -> - erlang:send_after(?CLEAN_CERT_DB, get(ssl_manager), {clean_cert_db, Ref, File}), + erlang:send_after(?CLEAN_CERT_DB, get(ssl_manager), + {clean_cert_db, Ref, File}), ok. %%-------------------------------------------------------------------- --spec register_session(inet:port_number(), #session{}) -> ok. --spec register_session(host(), inet:port_number(), #session{}) -> ok. %% %% Description: Make the session available for reuse. %%-------------------------------------------------------------------- +-spec register_session(host(), inet:port_number(), #session{}) -> ok. register_session(Host, Port, Session) -> cast({register_session, Host, Port, Session}). +-spec register_session(inet:port_number(), #session{}) -> ok. register_session(Port, Session) -> cast({register_session, Port, Session}). %%-------------------------------------------------------------------- --spec invalidate_session(inet:port_number(), #session{}) -> ok. --spec invalidate_session(host(), inet:port_number(), #session{}) -> ok. %% %% Description: Make the session unavailable for reuse. After %% a the session has been marked "is_resumable = false" for some while %% it will be safe to remove the data from the session database. %%-------------------------------------------------------------------- +-spec invalidate_session(host(), inet:port_number(), #session{}) -> ok. invalidate_session(Host, Port, Session) -> + load_mitigation(), cast({invalidate_session, Host, Port, Session}). +-spec invalidate_session(inet:port_number(), #session{}) -> ok. invalidate_session(Port, Session) -> + load_mitigation(), cast({invalidate_session, Port, Session}). +-spec invalidate_pem(File::binary()) -> ok. +invalidate_pem(File) -> + cast({invalidate_pem, File}). + +insert_crls(Path, CRLs)-> + insert_crls(Path, CRLs, normal). +insert_crls(?NO_DIST_POINT_PATH = Path, CRLs, ManagerType)-> + put(ssl_manager, manager_name(ManagerType)), + cast({insert_crls, Path, CRLs}); +insert_crls(Path, CRLs, ManagerType)-> + put(ssl_manager, manager_name(ManagerType)), + call({insert_crls, Path, CRLs}). + +delete_crls(Path)-> + delete_crls(Path, normal). +delete_crls(?NO_DIST_POINT_PATH = Path, ManagerType)-> + put(ssl_manager, manager_name(ManagerType)), + cast({delete_crls, Path}); +delete_crls(Path, ManagerType)-> + put(ssl_manager, manager_name(ManagerType)), + call({delete_crls, Path}). + %%==================================================================== %% gen_server callbacks %%==================================================================== @@ -195,15 +245,31 @@ init([Name, Opts]) -> SessionLifeTime = proplists:get_value(session_lifetime, Opts, ?'24H_in_sec'), CertDb = ssl_pkix_db:create(), - SessionCache = CacheCb:init(proplists:get_value(session_cb_init_args, Opts, [])), + ClientSessionCache = + CacheCb:init([{role, client} | + proplists:get_value(session_cb_init_args, Opts, [])]), + ServerSessionCache = + CacheCb:init([{role, server} | + proplists:get_value(session_cb_init_args, Opts, [])]), Timer = erlang:send_after(SessionLifeTime * 1000 + 5000, self(), validate_sessions), - erlang:send_after(?CLEAR_PEM_CACHE, self(), clear_pem_cache), + Interval = pem_check_interval(), + erlang:send_after(Interval, self(), clear_pem_cache), {ok, #state{certificate_db = CertDb, - session_cache = SessionCache, + session_cache_client = ClientSessionCache, + session_cache_server = ServerSessionCache, session_cache_cb = CacheCb, session_lifetime = SessionLifeTime, - session_validation_timer = Timer}}. + session_validation_timer = Timer, + last_pem_check = os:timestamp(), + clear_pem_cache = Interval, + session_cache_client_max = + max_session_cache_size(session_cache_client_max), + session_cache_server_max = + max_session_cache_size(session_cache_server_max), + session_client_invalidator = undefined, + session_server_invalidator = undefined + }}. %%-------------------------------------------------------------------- -spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. @@ -216,33 +282,40 @@ init([Name, Opts]) -> %% %% Description: Handling call messages %%-------------------------------------------------------------------- -handle_call({{connection_init, <<>>, _Role}, _Pid}, _From, - #state{certificate_db = [CertDb, FileRefDb, PemChace], - session_cache = Cache} = State) -> - Result = {ok, make_ref(),CertDb, FileRefDb, PemChace, Cache}, - {reply, Result, State}; - -handle_call({{connection_init, Trustedcerts, _Role}, Pid}, _From, - #state{certificate_db = [CertDb, FileRefDb, PemChace] = Db, - session_cache = Cache} = State) -> - Result = - try - {ok, Ref} = ssl_pkix_db:add_trusted_certs(Pid, Trustedcerts, Db), - {ok, Ref, CertDb, FileRefDb, PemChace, Cache} - catch - _:Reason -> - {error, Reason} - end, - {reply, Result, State}; - -handle_call({{new_session_id,Port}, _}, +handle_call({{connection_init, <<>>, Role, {CRLCb, UserCRLDb}}, _Pid}, _From, + #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) -> + Ref = make_ref(), + Result = {ok, Ref, CertDb, FileRefDb, PemChace, + session_cache(Role, State), {CRLCb, crl_db_info(Db, UserCRLDb)}}, + {reply, Result, State#state{certificate_db = Db}}; + +handle_call({{connection_init, Trustedcerts, Role, {CRLCb, UserCRLDb}}, Pid}, _From, + #state{certificate_db = [CertDb, FileRefDb, PemChace | _] = Db} = State) -> + case add_trusted_certs(Pid, Trustedcerts, Db) of + {ok, Ref} -> + {reply, {ok, Ref, CertDb, FileRefDb, PemChace, session_cache(Role, State), + {CRLCb, crl_db_info(Db, UserCRLDb)}}, State}; + {error, _} = Error -> + {reply, Error, State} + end; + +handle_call({{insert_crls, Path, CRLs}, _}, _From, + #state{certificate_db = Db} = State) -> + ssl_pkix_db:add_crls(Db, Path, CRLs), + {reply, ok, State}; + +handle_call({{delete_crls, CRLsOrPath}, _}, _From, + #state{certificate_db = Db} = State) -> + ssl_pkix_db:remove_crls(Db, CRLsOrPath), + {reply, ok, State}; + +handle_call({{new_session_id, Port}, _}, _, #state{session_cache_cb = CacheCb, - session_cache = Cache} = State) -> + session_cache_server = Cache} = State) -> Id = new_id(Port, ?GEN_UNIQUE_ID_MAX_TRIES, Cache, CacheCb), {reply, Id, State}; - -handle_call({{cache_pem, File}, _Pid}, _, +handle_call({{cache_pem,File}, _Pid}, _, #state{certificate_db = Db} = State) -> try ssl_pkix_db:cache_pem_file(File, Db) of Result -> @@ -251,7 +324,8 @@ handle_call({{cache_pem, File}, _Pid}, _, _:Reason -> {reply, {error, Reason}, State} end; -handle_call({unconditionally_clear_pem_cache, _},_, #state{certificate_db = [_,_,PemChace]} = State) -> +handle_call({unconditionally_clear_pem_cache, _},_, + #state{certificate_db = [_,_,PemChace | _]} = State) -> ssl_pkix_db:clear(PemChace), {reply, ok, State}. @@ -263,33 +337,40 @@ handle_call({unconditionally_clear_pem_cache, _},_, #state{certificate_db = [_,_ %% %% Description: Handling cast messages %%-------------------------------------------------------------------- -handle_cast({register_session, Host, Port, Session}, - #state{session_cache = Cache, - session_cache_cb = CacheCb} = State) -> - TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), - NewSession = Session#session{time_stamp = TimeStamp}, - CacheCb:update(Cache, {{Host, Port}, - NewSession#session.session_id}, NewSession), +handle_cast({register_session, Host, Port, Session}, State0) -> + State = ssl_client_register_session(Host, Port, Session, State0), {noreply, State}; -handle_cast({register_session, Port, Session}, - #state{session_cache = Cache, - session_cache_cb = CacheCb} = State) -> - TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), - NewSession = Session#session{time_stamp = TimeStamp}, - CacheCb:update(Cache, {Port, NewSession#session.session_id}, NewSession), +handle_cast({register_session, Port, Session}, State0) -> + State = server_register_session(Port, Session, State0), {noreply, State}; handle_cast({invalidate_session, Host, Port, #session{session_id = ID} = Session}, - #state{session_cache = Cache, + #state{session_cache_client = Cache, session_cache_cb = CacheCb} = State) -> invalidate_session(Cache, CacheCb, {{Host, Port}, ID}, Session, State); handle_cast({invalidate_session, Port, #session{session_id = ID} = Session}, - #state{session_cache = Cache, + #state{session_cache_server = Cache, session_cache_cb = CacheCb} = State) -> - invalidate_session(Cache, CacheCb, {Port, ID}, Session, State). + invalidate_session(Cache, CacheCb, {Port, ID}, Session, State); + + +handle_cast({insert_crls, Path, CRLs}, + #state{certificate_db = Db} = State) -> + ssl_pkix_db:add_crls(Db, Path, CRLs), + {noreply, State}; + +handle_cast({delete_crls, CRLsOrPath}, + #state{certificate_db = Db} = State) -> + ssl_pkix_db:remove_crls(Db, CRLsOrPath), + {noreply, State}; + +handle_cast({invalidate_pem, File}, + #state{certificate_db = [_, _, PemCache | _]} = State) -> + ssl_pkix_db:remove(File, PemCache), + {noreply, State}. %%-------------------------------------------------------------------- -spec handle_info(msg(), #state{}) -> {noreply, #state{}}. @@ -300,33 +381,36 @@ handle_cast({invalidate_session, Port, #session{session_id = ID} = Session}, %% Description: Handling all non call/cast messages %%------------------------------------------------------------------- handle_info(validate_sessions, #state{session_cache_cb = CacheCb, - session_cache = Cache, - session_lifetime = LifeTime + session_cache_client = ClientCache, + session_cache_server = ServerCache, + session_lifetime = LifeTime, + session_client_invalidator = Client, + session_server_invalidator = Server } = State) -> Timer = erlang:send_after(?SESSION_VALIDATION_INTERVAL, self(), validate_sessions), - start_session_validator(Cache, CacheCb, LifeTime), - {noreply, State#state{session_validation_timer = Timer}}; + CPid = start_session_validator(ClientCache, CacheCb, LifeTime, Client), + SPid = start_session_validator(ServerCache, CacheCb, LifeTime, Server), + {noreply, State#state{session_validation_timer = Timer, + session_client_invalidator = CPid, + session_server_invalidator = SPid}}; -handle_info({delayed_clean_session, Key}, #state{session_cache = Cache, - session_cache_cb = CacheCb - } = State) -> - CacheCb:delete(Cache, Key), - {noreply, State}; -handle_info(clear_pem_cache, #state{certificate_db = [_,_,PemChace]} = State) -> - case ssl_pkix_db:db_size(PemChace) of - N when N < ?NOT_TO_BIG -> - ok; - _ -> - ssl_pkix_db:clear(PemChace) - end, - erlang:send_after(?CLEAR_PEM_CACHE, self(), clear_pem_cache), +handle_info({delayed_clean_session, Key, Cache}, #state{session_cache_cb = CacheCb + } = State) -> + CacheCb:delete(Cache, Key), {noreply, State}; +handle_info(clear_pem_cache, #state{certificate_db = [_,_,PemChace | _], + clear_pem_cache = Interval, + last_pem_check = CheckPoint} = State) -> + NewCheckPoint = os:timestamp(), + start_pem_cache_validator(PemChace, CheckPoint), + erlang:send_after(Interval, self(), clear_pem_cache), + {noreply, State#state{last_pem_check = NewCheckPoint}}; handle_info({clean_cert_db, Ref, File}, - #state{certificate_db = [CertDb,RefDb, PemCache]} = State) -> + #state{certificate_db = [CertDb,RefDb, PemCache | _]} = State) -> case ssl_pkix_db:lookup(Ref, RefDb) of undefined -> %% Alredy cleaned @@ -336,10 +420,10 @@ handle_info({clean_cert_db, Ref, File}, end, {noreply, State}; -handle_info({'EXIT', _, _}, State) -> - %% Session validator died!! Do we need to take any action? - %% maybe error log - {noreply, State}; +handle_info({'EXIT', Pid, _}, #state{session_client_invalidator = Pid} = State) -> + {noreply, State#state{session_client_invalidator = undefined}}; +handle_info({'EXIT', Pid, _}, #state{session_server_invalidator = Pid} = State) -> + {noreply, State#state{session_server_invalidator = undefined}}; handle_info(_Info, State) -> {noreply, State}. @@ -353,12 +437,14 @@ handle_info(_Info, State) -> %% The return value is ignored. %%-------------------------------------------------------------------- terminate(_Reason, #state{certificate_db = Db, - session_cache = SessionCache, + session_cache_client = ClientSessionCache, + session_cache_server = ServerSessionCache, session_cache_cb = CacheCb, session_validation_timer = Timer}) -> erlang:cancel_timer(Timer), ssl_pkix_db:remove(Db), - CacheCb:terminate(SessionCache), + catch CacheCb:terminate(ClientSessionCache), + catch CacheCb:terminate(ServerSessionCache), ok. %%-------------------------------------------------------------------- @@ -394,9 +480,11 @@ validate_session(Port, Session, LifeTime) -> invalidate_session(Port, Session) end. -start_session_validator(Cache, CacheCb, LifeTime) -> +start_session_validator(Cache, CacheCb, LifeTime, undefined) -> spawn_link(?MODULE, init_session_validator, - [[get(ssl_manager), Cache, CacheCb, LifeTime]]). + [[get(ssl_manager), Cache, CacheCb, LifeTime]]); +start_session_validator(_,_,_, Pid) -> + Pid. init_session_validator([SslManagerName, Cache, CacheCb, LifeTime]) -> put(ssl_manager, SslManagerName), @@ -418,7 +506,15 @@ delay_time() -> ?CLEAN_SESSION_DB end. -invalidate_session(Cache, CacheCb, Key, Session, #state{last_delay_timer = LastTimer} = State) -> +max_session_cache_size(CacheType) -> + case application:get_env(ssl, CacheType) of + {ok, Size} when is_integer(Size) -> + Size; + _ -> + ?DEFAULT_MAX_SESSION_CACHE + end. + +invalidate_session(Cache, CacheCb, Key, Session, State) -> case CacheCb:lookup(Cache, Key) of undefined -> %% Session is already invalidated {noreply, State}; @@ -426,15 +522,23 @@ invalidate_session(Cache, CacheCb, Key, Session, #state{last_delay_timer = LastT CacheCb:delete(Cache, Key), {noreply, State}; _ -> - %% When a registered session is invalidated we need to wait a while before deleting - %% it as there might be pending connections that rightfully needs to look - %% up the session data but new connections should not get to use this session. - CacheCb:update(Cache, Key, Session#session{is_resumable = false}), - TRef = - erlang:send_after(delay_time(), self(), {delayed_clean_session, Key}), - {noreply, State#state{last_delay_timer = last_delay_timer(Key, TRef, LastTimer)}} + delayed_invalidate_session(CacheCb, Cache, Key, Session, State) end. +delayed_invalidate_session(CacheCb, Cache, Key, Session, + #state{last_delay_timer = LastTimer} = State) -> + %% When a registered session is invalidated we need to + %% wait a while before deleting it as there might be + %% pending connections that rightfully needs to look up + %% the session data but new connections should not get to + %% use this session. + CacheCb:update(Cache, Key, Session#session{is_resumable = false}), + TRef = + erlang:send_after(delay_time(), self(), + {delayed_clean_session, Key, Cache}), + {noreply, State#state{last_delay_timer = + last_delay_timer(Key, TRef, LastTimer)}}. + last_delay_timer({{_,_},_}, TRef, {LastServer, _}) -> {LastServer, TRef}; last_delay_timer({_,_}, TRef, {_, LastClient}) -> @@ -450,15 +554,15 @@ last_delay_timer({_,_}, TRef, {_, LastClient}) -> new_id(_, 0, _, _) -> <<>>; new_id(Port, Tries, Cache, CacheCb) -> - Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES), + Id = ssl_cipher:random_bytes(?NUM_OF_SESSION_ID_BYTES), case CacheCb:lookup(Cache, {Port, Id}) of undefined -> - Now = calendar:datetime_to_gregorian_seconds({date(), time()}), + Now = erlang:monotonic_time(), %% New sessions can not be set to resumable %% until handshake is compleate and the %% other session values are set. CacheCb:update(Cache, {Port, Id}, #session{session_id = Id, - is_resumable = false, + is_resumable = new, time_stamp = Now}), Id; _ -> @@ -468,10 +572,9 @@ new_id(Port, Tries, Cache, CacheCb) -> clean_cert_db(Ref, CertDb, RefDb, PemCache, File) -> case ssl_pkix_db:ref_count(Ref, RefDb, 0) of 0 -> - MD5 = crypto:hash(md5, File), - case ssl_pkix_db:lookup_cached_pem(PemCache, MD5) of + case ssl_pkix_db:lookup_cached_pem(PemCache, File) of [{Content, Ref}] -> - ssl_pkix_db:insert(MD5, Content, PemCache); + ssl_pkix_db:insert(File, Content, PemCache); _ -> ok end, @@ -480,3 +583,150 @@ clean_cert_db(Ref, CertDb, RefDb, PemCache, File) -> _ -> ok end. + +ssl_client_register_session(Host, Port, Session, #state{session_cache_client = Cache, + session_cache_cb = CacheCb, + session_cache_client_max = Max, + session_client_invalidator = Pid0} = State) -> + TimeStamp = erlang:monotonic_time(), + NewSession = Session#session{time_stamp = TimeStamp}, + + case CacheCb:select_session(Cache, {Host, Port}) of + no_session -> + Pid = do_register_session({{Host, Port}, + NewSession#session.session_id}, + NewSession, Max, Pid0, Cache, CacheCb), + State#state{session_client_invalidator = Pid}; + Sessions -> + register_unique_session(Sessions, NewSession, {Host, Port}, State) + end. + +server_register_session(Port, Session, #state{session_cache_server_max = Max, + session_cache_server = Cache, + session_cache_cb = CacheCb, + session_server_invalidator = Pid0} = State) -> + TimeStamp = erlang:monotonic_time(), + NewSession = Session#session{time_stamp = TimeStamp}, + Pid = do_register_session({Port, NewSession#session.session_id}, + NewSession, Max, Pid0, Cache, CacheCb), + State#state{session_server_invalidator = Pid}. + +do_register_session(Key, Session, Max, Pid, Cache, CacheCb) -> + try CacheCb:size(Cache) of + Max -> + invalidate_session_cache(Pid, CacheCb, Cache); + _ -> + CacheCb:update(Cache, Key, Session), + Pid + catch + error:undef -> + CacheCb:update(Cache, Key, Session), + Pid + end. + + +%% Do not let dumb clients create a gigantic session table +%% for itself creating big delays at connection time. +register_unique_session(Sessions, Session, PartialKey, + #state{session_cache_client_max = Max, + session_cache_client = Cache, + session_cache_cb = CacheCb, + session_client_invalidator = Pid0} = State) -> + case exists_equivalent(Session , Sessions) of + true -> + State; + false -> + Pid = do_register_session({PartialKey, + Session#session.session_id}, + Session, Max, Pid0, Cache, CacheCb), + State#state{session_client_invalidator = Pid} + end. + +exists_equivalent(_, []) -> + false; +exists_equivalent(#session{ + peer_certificate = PeerCert, + own_certificate = OwnCert, + compression_method = Compress, + cipher_suite = CipherSuite, + srp_username = SRP, + ecc = ECC} , + [#session{ + peer_certificate = PeerCert, + own_certificate = OwnCert, + compression_method = Compress, + cipher_suite = CipherSuite, + srp_username = SRP, + ecc = ECC} | _]) -> + true; +exists_equivalent(Session, [ _ | Rest]) -> + exists_equivalent(Session, Rest). + +start_pem_cache_validator(PemCache, CheckPoint) -> + spawn_link(?MODULE, init_pem_cache_validator, + [[get(ssl_manager), PemCache, CheckPoint]]). + +init_pem_cache_validator([SslManagerName, PemCache, CheckPoint]) -> + put(ssl_manager, SslManagerName), + ssl_pkix_db:foldl(fun pem_cache_validate/2, + CheckPoint, PemCache). + +pem_cache_validate({File, _}, CheckPoint) -> + case file:read_file_info(File, []) of + {ok, #file_info{mtime = Time}} -> + case is_before_checkpoint(Time, CheckPoint) of + true -> + ok; + false -> + invalidate_pem(File) + end; + _ -> + invalidate_pem(File) + end, + CheckPoint. + +pem_check_interval() -> + case application:get_env(ssl, ssl_pem_cache_clean) of + {ok, Interval} when is_integer(Interval) -> + Interval; + _ -> + ?CLEAR_PEM_CACHE + end. + +is_before_checkpoint(Time, CheckPoint) -> + calendar:datetime_to_gregorian_seconds( + calendar:now_to_datetime(CheckPoint)) - + calendar:datetime_to_gregorian_seconds(Time) > 0. + +add_trusted_certs(Pid, Trustedcerts, Db) -> + try + ssl_pkix_db:add_trusted_certs(Pid, Trustedcerts, Db) + catch + _:Reason -> + {error, Reason} + end. + +session_cache(client, #state{session_cache_client = Cache}) -> + Cache; +session_cache(server, #state{session_cache_server = Cache}) -> + Cache. + +crl_db_info([_,_,_,Local], {internal, Info}) -> + {Local, Info}; +crl_db_info(_, UserCRLDb) -> + UserCRLDb. + +%% Only start a session invalidator if there is not +%% one already active +invalidate_session_cache(undefined, CacheCb, Cache) -> + start_session_validator(Cache, CacheCb, {invalidate_before, erlang:monotonic_time()}, undefined); +invalidate_session_cache(Pid, _CacheCb, _Cache) -> + Pid. + +load_mitigation() -> + MSec = rand:uniform(?LOAD_MITIGATION), + receive + after + MSec -> + continue + end. diff --git a/lib/ssl/src/ssl_pkix_db.erl b/lib/ssl/src/ssl_pkix_db.erl index 9de50c8f26..b16903d7c7 100644 --- a/lib/ssl/src/ssl_pkix_db.erl +++ b/lib/ssl/src/ssl_pkix_db.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -27,9 +28,9 @@ -include_lib("public_key/include/public_key.hrl"). -include_lib("kernel/include/file.hrl"). --export([create/0, remove/1, add_trusted_certs/3, +-export([create/0, add_crls/3, remove_crls/2, remove/1, add_trusted_certs/3, remove_trusted_certs/2, insert/3, remove/2, clear/1, db_size/1, - ref_count/3, lookup_trusted_cert/4, foldl/3, + ref_count/3, lookup_trusted_cert/4, foldl/3, select_cert_by_issuer/2, lookup_cached_pem/2, cache_pem_file/2, cache_pem_file/3, lookup/2]). @@ -51,16 +52,24 @@ create() -> ets:new(ssl_otp_cacertificate_db, [set, public]), %% Let connection processes call ref_count/3 directly ets:new(ssl_otp_ca_file_ref, [set, public]), - ets:new(ssl_otp_pem_cache, [set, protected]) + ets:new(ssl_otp_pem_cache, [set, protected]), + %% Default cache + {ets:new(ssl_otp_crl_cache, [set, protected]), + ets:new(ssl_otp_crl_issuer_mapping, [bag, protected])} ]. %%-------------------------------------------------------------------- --spec remove([db_handle()]) -> ok. +-spec remove([db_handle()]) -> ok. %% %% Description: Removes database db %%-------------------------------------------------------------------- remove(Dbs) -> - lists:foreach(fun(Db) -> + lists:foreach(fun({Db0, Db1}) -> + true = ets:delete(Db0), + true = ets:delete(Db1); + (undefined) -> + ok; + (Db) -> true = ets:delete(Db) end, Dbs). @@ -81,10 +90,10 @@ lookup_trusted_cert(DbHandle, Ref, SerialNumber, Issuer) -> {ok, Certs} end. -lookup_cached_pem([_, _, PemChache], MD5) -> - lookup_cached_pem(PemChache, MD5); -lookup_cached_pem(PemChache, MD5) -> - lookup(MD5, PemChache). +lookup_cached_pem([_, _, PemChache | _], File) -> + lookup_cached_pem(PemChache, File); +lookup_cached_pem(PemChache, File) -> + lookup(File, PemChache). %%-------------------------------------------------------------------- -spec add_trusted_certs(pid(), {erlang:timestamp(), string()} | @@ -94,42 +103,42 @@ lookup_cached_pem(PemChache, MD5) -> %% runtime database. Returns Ref that should be handed to lookup_trusted_cert %% together with the cert serialnumber and issuer. %%-------------------------------------------------------------------- -add_trusted_certs(_Pid, {der, DerList}, [CerDb, _,_]) -> +add_trusted_certs(_Pid, {der, DerList}, [CertDb, _,_ | _]) -> NewRef = make_ref(), - add_certs_from_der(DerList, NewRef, CerDb), + add_certs_from_der(DerList, NewRef, CertDb), {ok, NewRef}; -add_trusted_certs(_Pid, File, [CertsDb, RefDb, PemChache] = Db) -> - MD5 = crypto:hash(md5, File), - case lookup_cached_pem(Db, MD5) of +add_trusted_certs(_Pid, File, [CertsDb, RefDb, PemChache | _] = Db) -> + case lookup_cached_pem(Db, File) of [{_Content, Ref}] -> ref_count(Ref, RefDb, 1), {ok, Ref}; [Content] -> Ref = make_ref(), update_counter(Ref, 1, RefDb), - insert(MD5, {Content, Ref}, PemChache), + insert(File, {Content, Ref}, PemChache), add_certs_from_pem(Content, Ref, CertsDb), {ok, Ref}; undefined -> - new_trusted_cert_entry({MD5, File}, Db) + new_trusted_cert_entry(File, Db) end. %%-------------------------------------------------------------------- --spec cache_pem_file({binary(), binary()}, [db_handle()]) -> {ok, term()}. --spec cache_pem_file(reference(), {binary(), binary()}, [db_handle()]) -> {ok, term()}. %% %% Description: Cache file as binary in DB %%-------------------------------------------------------------------- -cache_pem_file({MD5, File}, [_CertsDb, _RefDb, PemChache]) -> +-spec cache_pem_file(binary(), [db_handle()]) -> {ok, term()}. +cache_pem_file(File, [_CertsDb, _RefDb, PemChache | _]) -> {ok, PemBin} = file:read_file(File), Content = public_key:pem_decode(PemBin), - insert(MD5, Content, PemChache), + insert(File, Content, PemChache), {ok, Content}. -cache_pem_file(Ref, {MD5, File}, [_CertsDb, _RefDb, PemChache]) -> + +-spec cache_pem_file(reference(), binary(), [db_handle()]) -> {ok, term()}. +cache_pem_file(Ref, File, [_CertsDb, _RefDb, PemChache| _]) -> {ok, PemBin} = file:read_file(File), Content = public_key:pem_decode(PemBin), - insert(MD5, {Content, Ref}, PemChache), + insert(File, {Content, Ref}, PemChache), {ok, Content}. %%-------------------------------------------------------------------- @@ -150,6 +159,15 @@ remove(Key, Db) -> ok. %%-------------------------------------------------------------------- +-spec remove(term(), term(), db_handle()) -> ok. +%% +%% Description: Removes an element in a <Db>. +%%-------------------------------------------------------------------- +remove(Key, Data, Db) -> + ets:delete_object(Db, {Key, Data}), + ok. + +%%-------------------------------------------------------------------- -spec lookup(term(), db_handle()) -> [term()] | undefined. %% %% Description: Looks up an element in a <Db>. @@ -176,6 +194,10 @@ lookup(Key, Db) -> foldl(Fun, Acc0, Cache) -> ets:foldl(Fun, Acc0, Cache). + +select_cert_by_issuer(Cache, Issuer) -> + ets:select(Cache, [{{{'_','_', Issuer},{'_', '$1'}},[],['$$']}]). + %%-------------------------------------------------------------------- -spec ref_count(term(), db_handle(), integer()) -> integer(). %% @@ -245,9 +267,39 @@ add_certs(Cert, Ref, CertsDb) -> error_logger:info_report(Report) end. -new_trusted_cert_entry(FileRef, [CertsDb, RefDb, _] = Db) -> +new_trusted_cert_entry(File, [CertsDb, RefDb, _ | _] = Db) -> Ref = make_ref(), update_counter(Ref, 1, RefDb), - {ok, Content} = cache_pem_file(Ref, FileRef, Db), + {ok, Content} = cache_pem_file(Ref, File, Db), add_certs_from_pem(Content, Ref, CertsDb), {ok, Ref}. + +add_crls([_,_,_, {_, Mapping} | _], ?NO_DIST_POINT, CRLs) -> + [add_crls(CRL, Mapping) || CRL <- CRLs]; +add_crls([_,_,_, {Cache, Mapping} | _], Path, CRLs) -> + insert(Path, CRLs, Cache), + [add_crls(CRL, Mapping) || CRL <- CRLs]. + +add_crls(CRL, Mapping) -> + insert(crl_issuer(CRL), CRL, Mapping). + +remove_crls([_,_,_, {_, Mapping} | _], {?NO_DIST_POINT, CRLs}) -> + [rm_crls(CRL, Mapping) || CRL <- CRLs]; + +remove_crls([_,_,_, {Cache, Mapping} | _], Path) -> + case lookup(Path, Cache) of + undefined -> + ok; + CRLs -> + remove(Path, Cache), + [rm_crls(CRL, Mapping) || CRL <- CRLs] + end. + +rm_crls(CRL, Mapping) -> + remove(crl_issuer(CRL), CRL, Mapping). + +crl_issuer(DerCRL) -> + CRL = public_key:der_decode('CertificateList', DerCRL), + TBSCRL = CRL#'CertificateList'.tbsCertList, + TBSCRL#'TBSCertList'.issuer. + diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl new file mode 100644 index 0000000000..5bb1c92c2d --- /dev/null +++ b/lib/ssl/src/ssl_record.erl @@ -0,0 +1,554 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% + +%%---------------------------------------------------------------------- +%% Purpose: Handle TLS/SSL/DTLS record protocol. Note epoch is only +%% used by DTLS but handled here so we can avoid code duplication. +%%---------------------------------------------------------------------- + +-module(ssl_record). + +-include("ssl_record.hrl"). +-include("ssl_internal.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_alert.hrl"). + +%% Connection state handling +-export([init_connection_states/2, + current_connection_state/2, pending_connection_state/2, + activate_pending_connection_state/2, + set_security_params/3, + set_mac_secret/4, + set_master_secret/2, + set_pending_cipher_state/4, + set_renegotiation_flag/2, + set_client_verify_data/3, + set_server_verify_data/3]). + +%% Encoding records +-export([encode_handshake/3, encode_alert_record/3, + encode_change_cipher_spec/2, encode_data/3]). + +%% Compression +-export([compress/3, uncompress/3, compressions/0]). + +%% Payload encryption/decryption +-export([cipher/4, decipher/4, is_correct_mac/2, + cipher_aead/4, decipher_aead/4]). + +-export_type([ssl_version/0, ssl_atom_version/0]). + +-type ssl_version() :: {integer(), integer()}. +-type ssl_atom_version() :: tls_record:tls_atom_version(). + +%%==================================================================== +%% Internal application API +%%==================================================================== + +%%-------------------------------------------------------------------- +-spec init_connection_states(client | server, one_n_minus_one | zero_n | disabled ) -> + #connection_states{}. +%% +%% Description: Creates a connection_states record with appropriate +%% values for the initial SSL connection setup. +%%-------------------------------------------------------------------- +init_connection_states(Role, BeastMitigation) -> + ConnectionEnd = record_protocol_role(Role), + Current = initial_connection_state(ConnectionEnd, BeastMitigation), + Pending = empty_connection_state(ConnectionEnd, BeastMitigation), + #connection_states{dtls_write_msg_seq = 1, % only used by dtls + current_read = Current, + pending_read = Pending, + current_write = Current, + pending_write = Pending + }. + +%%-------------------------------------------------------------------- +-spec current_connection_state(#connection_states{}, read | write) -> + #connection_state{}. +%% +%% Description: Returns the instance of the connection_state record +%% that is currently defined as the current conection state. +%%-------------------------------------------------------------------- +current_connection_state(#connection_states{current_read = Current}, + read) -> + Current; +current_connection_state(#connection_states{current_write = Current}, + write) -> + Current. + +%%-------------------------------------------------------------------- +-spec pending_connection_state(#connection_states{}, read | write) -> + term(). +%% +%% Description: Returns the instance of the connection_state record +%% that is currently defined as the pending conection state. +%%-------------------------------------------------------------------- +pending_connection_state(#connection_states{pending_read = Pending}, + read) -> + Pending; +pending_connection_state(#connection_states{pending_write = Pending}, + write) -> + Pending. + + +%%-------------------------------------------------------------------- +-spec activate_pending_connection_state(#connection_states{}, read | write) -> + #connection_states{}. +%% +%% Description: Creates a new instance of the connection_states record +%% where the pending state of <Type> has been activated. +%%-------------------------------------------------------------------- +activate_pending_connection_state(States = + #connection_states{current_read = Current, + pending_read = Pending}, + read) -> + NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), + sequence_number = 0}, + BeastMitigation = Pending#connection_state.beast_mitigation, + SecParams = Pending#connection_state.security_parameters, + ConnectionEnd = SecParams#security_parameters.connection_end, + EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation), + SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, + NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, + States#connection_states{current_read = NewCurrent, + pending_read = NewPending + }; + +activate_pending_connection_state(States = + #connection_states{current_write = Current, + pending_write = Pending}, + write) -> + NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current), + sequence_number = 0}, + BeastMitigation = Pending#connection_state.beast_mitigation, + SecParams = Pending#connection_state.security_parameters, + ConnectionEnd = SecParams#security_parameters.connection_end, + EmptyPending = empty_connection_state(ConnectionEnd, BeastMitigation), + SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, + NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, + States#connection_states{current_write = NewCurrent, + pending_write = NewPending + }. + + +%%-------------------------------------------------------------------- +-spec set_security_params(#security_parameters{}, #security_parameters{}, + #connection_states{}) -> #connection_states{}. +%% +%% Description: Creates a new instance of the connection_states record +%% where the pending states gets its security parameters updated. +%%-------------------------------------------------------------------- +set_security_params(ReadParams, WriteParams, States = + #connection_states{pending_read = Read, + pending_write = Write}) -> + States#connection_states{pending_read = + Read#connection_state{security_parameters = + ReadParams}, + pending_write = + Write#connection_state{security_parameters = + WriteParams} + }. +%%-------------------------------------------------------------------- +-spec set_mac_secret(binary(), binary(), client | server, + #connection_states{}) -> #connection_states{}. +%% +%% Description: update the mac_secret field in pending connection states +%%-------------------------------------------------------------------- +set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, client, States) -> + set_mac_secret(ServerWriteMacSecret, ClientWriteMacSecret, States); +set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, server, States) -> + set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, States). + +set_mac_secret(ReadMacSecret, WriteMacSecret, + States = #connection_states{pending_read = Read, + pending_write = Write}) -> + States#connection_states{ + pending_read = Read#connection_state{mac_secret = ReadMacSecret}, + pending_write = Write#connection_state{mac_secret = WriteMacSecret} + }. + + +%%-------------------------------------------------------------------- +-spec set_master_secret(binary(), #connection_states{}) -> #connection_states{}. +%% +%% Description: Set master_secret in pending connection states +%%-------------------------------------------------------------------- +set_master_secret(MasterSecret, + States = #connection_states{pending_read = Read, + pending_write = Write}) -> + ReadSecPar = Read#connection_state.security_parameters, + Read1 = Read#connection_state{ + security_parameters = ReadSecPar#security_parameters{ + master_secret = MasterSecret}}, + WriteSecPar = Write#connection_state.security_parameters, + Write1 = Write#connection_state{ + security_parameters = WriteSecPar#security_parameters{ + master_secret = MasterSecret}}, + States#connection_states{pending_read = Read1, pending_write = Write1}. + +%%-------------------------------------------------------------------- +-spec set_renegotiation_flag(boolean(), #connection_states{}) -> #connection_states{}. +%% +%% Description: Set secure_renegotiation in pending connection states +%%-------------------------------------------------------------------- +set_renegotiation_flag(Flag, #connection_states{ + current_read = CurrentRead0, + current_write = CurrentWrite0, + pending_read = PendingRead0, + pending_write = PendingWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{secure_renegotiation = Flag}, + CurrentWrite = CurrentWrite0#connection_state{secure_renegotiation = Flag}, + PendingRead = PendingRead0#connection_state{secure_renegotiation = Flag}, + PendingWrite = PendingWrite0#connection_state{secure_renegotiation = Flag}, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite, + pending_read = PendingRead, + pending_write = PendingWrite}. + +%%-------------------------------------------------------------------- +-spec set_client_verify_data(current_read | current_write | current_both, + binary(), #connection_states{})-> + #connection_states{}. +%% +%% Description: Set verify data in connection states. +%%-------------------------------------------------------------------- +set_client_verify_data(current_read, Data, + #connection_states{current_read = CurrentRead0, + pending_write = PendingWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, + PendingWrite = PendingWrite0#connection_state{client_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + pending_write = PendingWrite}; +set_client_verify_data(current_write, Data, + #connection_states{pending_read = PendingRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + PendingRead = PendingRead0#connection_state{client_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, + ConnectionStates#connection_states{pending_read = PendingRead, + current_write = CurrentWrite}; +set_client_verify_data(current_both, Data, + #connection_states{current_read = CurrentRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite}. +%%-------------------------------------------------------------------- +-spec set_server_verify_data(current_read | current_write | current_both, + binary(), #connection_states{})-> + #connection_states{}. +%% +%% Description: Set verify data in pending connection states. +%%-------------------------------------------------------------------- +set_server_verify_data(current_write, Data, + #connection_states{pending_read = PendingRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + PendingRead = PendingRead0#connection_state{server_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, + ConnectionStates#connection_states{pending_read = PendingRead, + current_write = CurrentWrite}; + +set_server_verify_data(current_read, Data, + #connection_states{current_read = CurrentRead0, + pending_write = PendingWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, + PendingWrite = PendingWrite0#connection_state{server_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + pending_write = PendingWrite}; + +set_server_verify_data(current_both, Data, + #connection_states{current_read = CurrentRead0, + current_write = CurrentWrite0} + = ConnectionStates) -> + CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, + CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, + ConnectionStates#connection_states{current_read = CurrentRead, + current_write = CurrentWrite}. +%%-------------------------------------------------------------------- +-spec set_pending_cipher_state(#connection_states{}, #cipher_state{}, + #cipher_state{}, client | server) -> + #connection_states{}. +%% +%% Description: Set the cipher state in the specified pending connection state. +%%-------------------------------------------------------------------- +set_pending_cipher_state(#connection_states{pending_read = Read, + pending_write = Write} = States, + ClientState, ServerState, server) -> + States#connection_states{ + pending_read = Read#connection_state{cipher_state = ClientState}, + pending_write = Write#connection_state{cipher_state = ServerState}}; + +set_pending_cipher_state(#connection_states{pending_read = Read, + pending_write = Write} = States, + ClientState, ServerState, client) -> + States#connection_states{ + pending_read = Read#connection_state{cipher_state = ServerState}, + pending_write = Write#connection_state{cipher_state = ClientState}}. + + +%%-------------------------------------------------------------------- +-spec encode_handshake(iolist(), ssl_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes a handshake message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_handshake(Frag, Version, + #connection_states{current_write = + #connection_state{ + beast_mitigation = BeastMitigation, + security_parameters = + #security_parameters{bulk_cipher_algorithm = BCA}}} = + ConnectionStates) +when is_list(Frag) -> + case iolist_size(Frag) of + N when N > ?MAX_PLAIN_TEXT_LENGTH -> + Data = split_bin(iolist_to_binary(Frag), ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), + encode_iolist(?HANDSHAKE, Data, Version, ConnectionStates); + _ -> + encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates) + end; +%% TODO: this is a workarround for DTLS +%% +%% DTLS need to select the connection write state based on Epoch it wants to +%% send this fragment in. That Epoch does not nessarily has to be the same +%% as the current_write epoch. +%% The right solution might be to pass the WriteState instead of the ConnectionStates, +%% however, this will require substantion API changes. +encode_handshake(Frag, Version, ConnectionStates) -> + encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_alert_record(#alert{}, ssl_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes an alert message to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_alert_record(#alert{level = Level, description = Description}, + Version, ConnectionStates) -> + encode_plain_text(?ALERT, Version, <<?BYTE(Level), ?BYTE(Description)>>, + ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_change_cipher_spec(ssl_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes a change_cipher_spec-message to send on the ssl socket. +%%-------------------------------------------------------------------- +encode_change_cipher_spec(Version, ConnectionStates) -> + encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). + +%%-------------------------------------------------------------------- +-spec encode_data(binary(), ssl_version(), #connection_states{}) -> + {iolist(), #connection_states{}}. +%% +%% Description: Encodes data to send on the ssl-socket. +%%-------------------------------------------------------------------- +encode_data(Frag, Version, + #connection_states{current_write = #connection_state{ + beast_mitigation = BeastMitigation, + security_parameters = + #security_parameters{bulk_cipher_algorithm = BCA}}} = + ConnectionStates) -> + Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA, BeastMitigation), + encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). + +uncompress(?NULL, Data, CS) -> + {Data, CS}. + +compress(?NULL, Data, CS) -> + {Data, CS}. + +%%-------------------------------------------------------------------- +-spec compressions() -> [binary()]. +%% +%% Description: return a list of compressions supported (currently none) +%%-------------------------------------------------------------------- +compressions() -> + [?byte(?NULL)]. + +%%-------------------------------------------------------------------- +-spec cipher(ssl_version(), iodata(), #connection_state{}, MacHash::binary()) -> + {CipherFragment::binary(), #connection_state{}}. +%% +%% Description: Payload encryption +%%-------------------------------------------------------------------- +cipher(Version, Fragment, + #connection_state{cipher_state = CipherS0, + security_parameters= + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo} + } = WriteState0, MacHash) -> + + {CipherFragment, CipherS1} = + ssl_cipher:cipher(BulkCipherAlgo, CipherS0, MacHash, Fragment, Version), + {CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}. +%%-------------------------------------------------------------------- +-spec cipher_aead(ssl_version(), iodata(), #connection_state{}, MacHash::binary()) -> + {CipherFragment::binary(), #connection_state{}}. +%% +%% Description: Payload encryption +%%-------------------------------------------------------------------- +cipher_aead(Version, Fragment, + #connection_state{cipher_state = CipherS0, + sequence_number = SeqNo, + security_parameters= + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo} + } = WriteState0, AAD) -> + + {CipherFragment, CipherS1} = + ssl_cipher:cipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, Fragment, Version), + {CipherFragment, WriteState0#connection_state{cipher_state = CipherS1}}. + +%%-------------------------------------------------------------------- +-spec decipher(ssl_version(), binary(), #connection_state{}, boolean()) -> {binary(), binary(), #connection_state{}} | #alert{}. +%% +%% Description: Payload decryption +%%-------------------------------------------------------------------- +decipher(Version, CipherFragment, + #connection_state{security_parameters = + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo, + hash_size = HashSz}, + cipher_state = CipherS0 + } = ReadState, PaddingCheck) -> + case ssl_cipher:decipher(BulkCipherAlgo, HashSz, CipherS0, CipherFragment, Version, PaddingCheck) of + {PlainFragment, Mac, CipherS1} -> + CS1 = ReadState#connection_state{cipher_state = CipherS1}, + {PlainFragment, Mac, CS1}; + #alert{} = Alert -> + Alert + end. +%%-------------------------------------------------------------------- +-spec decipher_aead(ssl_version(), binary(), #connection_state{}, binary()) -> {binary(), binary(), #connection_state{}} | #alert{}. +%% +%% Description: Payload decryption +%%-------------------------------------------------------------------- +decipher_aead(Version, CipherFragment, + #connection_state{sequence_number = SeqNo, + security_parameters = + #security_parameters{bulk_cipher_algorithm = + BulkCipherAlgo}, + cipher_state = CipherS0 + } = ReadState, AAD) -> + case ssl_cipher:decipher_aead(BulkCipherAlgo, CipherS0, SeqNo, AAD, CipherFragment, Version) of + {PlainFragment, CipherS1} -> + CS1 = ReadState#connection_state{cipher_state = CipherS1}, + {PlainFragment, CS1}; + #alert{} = Alert -> + Alert + end. +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +empty_connection_state(ConnectionEnd, BeastMitigation) -> + SecParams = empty_security_params(ConnectionEnd), + #connection_state{security_parameters = SecParams, + beast_mitigation = BeastMitigation}. + +empty_security_params(ConnectionEnd = ?CLIENT) -> + #security_parameters{connection_end = ConnectionEnd, + client_random = random()}; +empty_security_params(ConnectionEnd = ?SERVER) -> + #security_parameters{connection_end = ConnectionEnd, + server_random = random()}. +random() -> + Secs_since_1970 = calendar:datetime_to_gregorian_seconds( + calendar:universal_time()) - 62167219200, + Random_28_bytes = ssl_cipher:random_bytes(28), + <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. + +dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS + undefined; +dtls_next_epoch(#connection_state{epoch = Epoch}) -> %% DTLS + Epoch + 1. + +is_correct_mac(Mac, Mac) -> + true; +is_correct_mac(_M,_H) -> + false. + +record_protocol_role(client) -> + ?CLIENT; +record_protocol_role(server) -> + ?SERVER. + +initial_connection_state(ConnectionEnd, BeastMitigation) -> + #connection_state{security_parameters = + initial_security_params(ConnectionEnd), + sequence_number = 0, + beast_mitigation = BeastMitigation + }. + +initial_security_params(ConnectionEnd) -> + SecParams = #security_parameters{connection_end = ConnectionEnd, + compression_algorithm = ?NULL}, + ssl_cipher:security_parameters(?TLS_NULL_WITH_NULL_NULL, SecParams). + + +encode_plain_text(Type, Version, Data, ConnectionStates) -> + RecordCB = protocol_module(Version), + RecordCB:encode_plain_text(Type, Version, Data, ConnectionStates). + +encode_iolist(Type, Data, Version, ConnectionStates0) -> + RecordCB = protocol_module(Version), + {ConnectionStates, EncodedMsg} = + lists:foldl(fun(Text, {CS0, Encoded}) -> + {Enc, CS1} = + RecordCB:encode_plain_text(Type, Version, Text, CS0), + {CS1, [Enc | Encoded]} + end, {ConnectionStates0, []}, Data), + {lists:reverse(EncodedMsg), ConnectionStates}. + +%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are +%% not vulnerable to this attack. +split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA, one_n_minus_one) when + BCA =/= ?RC4 andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + do_split_bin(Rest, ChunkSize, [[FirstByte]]); +%% 0/n splitting countermeasure for clients that are incompatible with 1/n-1 +%% splitting. +split_bin(Bin, ChunkSize, Version, BCA, zero_n) when + BCA =/= ?RC4 andalso ({3, 1} == Version orelse + {3, 0} == Version) -> + do_split_bin(Bin, ChunkSize, [[<<>>]]); +split_bin(Bin, ChunkSize, _, _, _) -> + do_split_bin(Bin, ChunkSize, []). + +do_split_bin(<<>>, _, Acc) -> + lists:reverse(Acc); +do_split_bin(Bin, ChunkSize, Acc) -> + case Bin of + <<Chunk:ChunkSize/binary, Rest/binary>> -> + do_split_bin(Rest, ChunkSize, [Chunk | Acc]); + _ -> + lists:reverse(Acc, [Bin]) + end. + +protocol_module({3, _}) -> + tls_record; +protocol_module({254, _}) -> + dtls_record. diff --git a/lib/ssl/src/ssl_record.hrl b/lib/ssl/src/ssl_record.hrl index 2fd17f9c35..a41264ff9b 100644 --- a/lib/ssl/src/ssl_record.hrl +++ b/lib/ssl/src/ssl_record.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -20,7 +21,7 @@ %% %%---------------------------------------------------------------------- %% Purpose: Record and constant defenitions for the SSL-record protocol -%% see RFC 2246 +% see RFC 2246 %%---------------------------------------------------------------------- -ifndef(ssl_record). @@ -29,8 +30,24 @@ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %%% Connection states - RFC 4346 section 6.1 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-record(connection_state, { + security_parameters, + compression_state, + cipher_state, + mac_secret, + epoch, %% Only used by DTLS + sequence_number, + %% RFC 5746 + secure_renegotiation, + client_verify_data, + server_verify_data, + %% How to do BEAST mitigation? + beast_mitigation + }). -record(connection_states, { + dtls_write_msg_seq, %% Only used by DTLS + current_read, pending_read, current_write, @@ -56,19 +73,9 @@ exportable % boolean }). --record(connection_state, { - security_parameters, - compression_state, - cipher_state, - mac_secret, - sequence_number, - %% RFC 5746 - secure_renegotiation, - client_verify_data, - server_verify_data - }). +-define(INITIAL_BYTES, 5). --define(MAX_SEQENCE_NUMBER, 18446744073709552000). %% math:pow(2, 64) - 1 = 1.8446744073709552e19 +-define(MAX_SEQENCE_NUMBER, 18446744073709551615). %% (1 bsl 64) - 1 = 18446744073709551615 %% Sequence numbers can not wrap so when max is about to be reached we should renegotiate. %% We will renegotiate a little before so that there will be sequence numbers left %% for the rehandshake and a little data. Currently we decided to renegotiate a little more @@ -88,11 +95,14 @@ -define('3DES', 4). -define(DES40, 5). -define(IDEA, 6). --define(AES, 7). +-define(AES_CBC, 7). +-define(AES_GCM, 8). +-define(CHACHA20_POLY1305, 9). %% CipherType -define(STREAM, 0). -define(BLOCK, 1). +-define(AEAD, 2). %% IsExportable %-define(TRUE, 0). %% Already defined by ssl_internal.hrl diff --git a/lib/ssl/src/ssl_session.erl b/lib/ssl/src/ssl_session.erl index a24b2d9444..c9607489e9 100644 --- a/lib/ssl/src/ssl_session.erl +++ b/lib/ssl/src/ssl_session.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -30,8 +31,6 @@ %% Internal application API -export([is_new/2, client_id/4, server_id/6, valid_session/2]). --define('24H_in_sec', 8640). - -type seconds() :: integer(). %%-------------------------------------------------------------------- @@ -62,13 +61,16 @@ client_id(ClientInfo, Cache, CacheCb, OwnCert) -> SessionId end. --spec valid_session(#session{}, seconds()) -> boolean(). +-spec valid_session(#session{}, seconds() | {invalidate_before, integer()}) -> boolean(). %% %% Description: Check that the session has not expired %%-------------------------------------------------------------------- +valid_session(#session{time_stamp = TimeStamp}, {invalidate_before, Before}) -> + TimeStamp > Before; valid_session(#session{time_stamp = TimeStamp}, LifeTime) -> - Now = calendar:datetime_to_gregorian_seconds({date(), time()}), - Now - TimeStamp < LifeTime. + Now = erlang:monotonic_time(), + Lived = erlang:convert_time_unit(Now-TimeStamp, native, seconds), + Lived < LifeTime. server_id(Port, <<>>, _SslOpts, _Cert, _, _) -> {ssl_manager:new_session_id(Port), undefined}; @@ -99,14 +101,14 @@ select_session([], _, _) -> no_session; select_session(Sessions, #ssl_options{ciphers = Ciphers}, OwnCert) -> IsNotResumable = - fun([_Id, Session]) -> + fun(Session) -> not (resumable(Session#session.is_resumable) andalso lists:member(Session#session.cipher_suite, Ciphers) andalso (OwnCert == Session#session.own_certificate)) end, case lists:dropwhile(IsNotResumable, Sessions) of [] -> no_session; - [[Id, _]|_] -> Id + [Session | _] -> Session#session.session_id end. is_resumable(_, _, #ssl_options{reuse_sessions = false}, _, _, _, _) -> diff --git a/lib/ssl/src/ssl_session_cache.erl b/lib/ssl/src/ssl_session_cache.erl index 5c6ee3c54c..c79ad1523b 100644 --- a/lib/ssl/src/ssl_session_cache.erl +++ b/lib/ssl/src/ssl_session_cache.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2012. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -26,13 +27,13 @@ -include("ssl_internal.hrl"). -export([init/1, terminate/1, lookup/2, update/3, delete/2, foldl/3, - select_session/2]). + select_session/2, size/1]). %%-------------------------------------------------------------------- %% Description: Return table reference. Called by ssl_manager process. %%-------------------------------------------------------------------- -init(_) -> - ets:new(cache_name(), [ordered_set, protected]). +init(Options) -> + ets:new(cache_name(proplists:get_value(role, Options)), [ordered_set, protected]). %%-------------------------------------------------------------------- %% Description: Handles cache table at termination of ssl manager. @@ -82,10 +83,16 @@ foldl(Fun, Acc0, Cache) -> %%-------------------------------------------------------------------- select_session(Cache, PartialKey) -> ets:select(Cache, - [{{{PartialKey,'$1'}, '$2'},[],['$$']}]). + [{{{PartialKey,'_'}, '$1'},[],['$1']}]). + +%%-------------------------------------------------------------------- +%% Description: Returns the cache size +%%-------------------------------------------------------------------- +size(Cache) -> + ets:info(Cache, size). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -cache_name() -> - ssl_otp_session_cache. +cache_name(Name) -> + list_to_atom(atom_to_list(Name) ++ "_ssl_otp_session_cache"). diff --git a/lib/ssl/src/ssl_session_cache_api.erl b/lib/ssl/src/ssl_session_cache_api.erl index f2b22b0f1b..b68c75a09b 100644 --- a/lib/ssl/src/ssl_session_cache_api.erl +++ b/lib/ssl/src/ssl_session_cache_api.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2011. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -32,3 +33,4 @@ -callback delete(db_handle(), key()) -> any(). -callback foldl(fun(), term(), db_handle()) -> term(). -callback select_session(db_handle(), {host(), inet:port_number()} | inet:port_number()) -> [#session{}]. +-callback size(db_handle()) -> integer(). diff --git a/lib/ssl/src/ssl_socket.erl b/lib/ssl/src/ssl_socket.erl index 4778db2333..b2aea2ba9c 100644 --- a/lib/ssl/src/ssl_socket.erl +++ b/lib/ssl/src/ssl_socket.erl @@ -1,24 +1,84 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 1998-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% -module(ssl_socket). +-behaviour(gen_server). + -include("ssl_internal.hrl"). +-include("ssl_api.hrl"). + +-export([socket/5, setopts/3, getopts/3, getstat/3, peername/2, sockname/2, port/2]). +-export([emulated_options/0, internal_inet_values/0, default_inet_values/0, + init/1, start_link/3, terminate/2, inherit_tracker/3, get_emulated_opts/1, + set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2, + handle_info/2, code_change/3]). --export([socket/3, setopts/3, getopts/3, peername/2, sockname/2, port/2]). +-record(state, { + emulated_opts, + port, + ssl_opts + }). -socket(Pid, Transport, Socket) -> +%%-------------------------------------------------------------------- +%%% Internal API +%%-------------------------------------------------------------------- +socket(Pid, Transport, Socket, ConnectionCb, Tracker) -> #sslsocket{pid = Pid, %% "The name "fd" is keept for backwards compatibility - fd = {Transport, Socket}}. - + fd = {Transport, Socket, ConnectionCb, Tracker}}. +setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> + {SockOpts, EmulatedOpts} = split_options(Options), + ok = set_emulated_opts(Tracker, EmulatedOpts), + inet:setopts(ListenSocket, SockOpts); +setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}, + emulated = Tracker}}}, Options) -> + {SockOpts, EmulatedOpts} = split_options(Options), + ok = set_emulated_opts(Tracker, EmulatedOpts), + Transport:setopts(ListenSocket, SockOpts); +%%% Following clauses will not be called for emulated options, they are handled in the connection process setopts(gen_tcp, Socket, Options) -> inet:setopts(Socket, Options); setopts(Transport, Socket, Options) -> Transport:setopts(Socket, Options). +getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> + {SockOptNames, EmulatedOptNames} = split_options(Options), + EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames), + SocketOpts = get_socket_opts(ListenSocket, SockOptNames, inet), + {ok, EmulatedOpts ++ SocketOpts}; +getopts(Transport, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> + {SockOptNames, EmulatedOptNames} = split_options(Options), + EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames), + SocketOpts = get_socket_opts(ListenSocket, SockOptNames, Transport), + {ok, EmulatedOpts ++ SocketOpts}; +%%% Following clauses will not be called for emulated options, they are handled in the connection process getopts(gen_tcp, Socket, Options) -> inet:getopts(Socket, Options); getopts(Transport, Socket, Options) -> Transport:getopts(Socket, Options). +getstat(gen_tcp, Socket, Options) -> + inet:getstat(Socket, Options); +getstat(Transport, Socket, Options) -> + Transport:getstat(Socket, Options). + peername(gen_tcp, Socket) -> inet:peername(Socket); peername(Transport, Socket) -> @@ -33,3 +93,151 @@ port(gen_tcp, Socket) -> inet:port(Socket); port(Transport, Socket) -> Transport:port(Socket). + +emulated_options() -> + [mode, packet, active, header, packet_size]. + +internal_inet_values() -> + [{packet_size,0}, {packet, 0}, {header, 0}, {active, false}, {mode,binary}]. + +default_inet_values() -> + [{packet_size, 0}, {packet,0}, {header, 0}, {active, true}, {mode, list}]. + +inherit_tracker(ListenSocket, EmOpts, #ssl_options{erl_dist = false} = SslOpts) -> + ssl_listen_tracker_sup:start_child([ListenSocket, EmOpts, SslOpts]); +inherit_tracker(ListenSocket, EmOpts, #ssl_options{erl_dist = true} = SslOpts) -> + ssl_listen_tracker_sup:start_child_dist([ListenSocket, EmOpts, SslOpts]). + +get_emulated_opts(TrackerPid) -> + call(TrackerPid, get_emulated_opts). +set_emulated_opts(TrackerPid, InetValues) -> + call(TrackerPid, {set_emulated_opts, InetValues}). +get_all_opts(TrackerPid) -> + call(TrackerPid, get_all_opts). + +%%==================================================================== +%% ssl_listen_tracker_sup API +%%==================================================================== + +start_link(Port, SockOpts, SslOpts) -> + gen_server:start_link(?MODULE, [Port, SockOpts, SslOpts], []). + +%%-------------------------------------------------------------------- +-spec init(list()) -> {ok, #state{}}. +%% Possible return values not used now. +%% | {ok, #state{}, timeout()} | ignore | {stop, term()}. +%% +%% Description: Initiates the server +%%-------------------------------------------------------------------- +init([Port, Opts, SslOpts]) -> + process_flag(trap_exit, true), + true = link(Port), + {ok, #state{emulated_opts = Opts, port = Port, ssl_opts = SslOpts}}. + +%%-------------------------------------------------------------------- +-spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. +%% Possible return values not used now. +%% {reply, reply(), #state{}, timeout()} | +%% {noreply, #state{}} | +%% {noreply, #state{}, timeout()} | +%% {stop, reason(), reply(), #state{}} | +%% {stop, reason(), #state{}}. +%% +%% Description: Handling call messages +%%-------------------------------------------------------------------- +handle_call({set_emulated_opts, Opts0}, _From, + #state{emulated_opts = Opts1} = State) -> + Opts = do_set_emulated_opts(Opts0, Opts1), + {reply, ok, State#state{emulated_opts = Opts}}; +handle_call(get_emulated_opts, _From, + #state{emulated_opts = Opts} = State) -> + {reply, {ok, Opts}, State}; +handle_call(get_all_opts, _From, + #state{emulated_opts = EmOpts, + ssl_opts = SslOpts} = State) -> + {reply, {ok, EmOpts, SslOpts}, State}. + +%%-------------------------------------------------------------------- +-spec handle_cast(msg(), #state{}) -> {noreply, #state{}}. +%% Possible return values not used now. +%% | {noreply, #state{}, timeout()} | +%% {stop, reason(), #state{}}. +%% +%% Description: Handling cast messages +%%-------------------------------------------------------------------- +handle_cast(_, State)-> + {noreply, State}. + +%%-------------------------------------------------------------------- +-spec handle_info(msg(), #state{}) -> {stop, reason(), #state{}}. +%% Possible return values not used now. +%% {noreply, #state{}}. +%% |{noreply, #state{}, timeout()} | +%% +%% +%% Description: Handling all non call/cast messages +%%------------------------------------------------------------------- +handle_info({'EXIT', Port, _}, #state{port = Port} = State) -> + {stop, normal, State}. + + +%%-------------------------------------------------------------------- +-spec terminate(reason(), #state{}) -> ok. +%% +%% Description: This function is called by a gen_server when it is about to +%% terminate. It should be the opposite of Module:init/1 and do any necessary +%% cleaning up. When it returns, the gen_server terminates with Reason. +%% The return value is ignored. +%%-------------------------------------------------------------------- +terminate(_Reason, _State) -> + ok. + +%%-------------------------------------------------------------------- +-spec code_change(term(), #state{}, list()) -> {ok, #state{}}. +%% +%% Description: Convert process state when code is changed +%%-------------------------------------------------------------------- +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%-------------------------------------------------------------------- +%%% Internal functions +%%-------------------------------------------------------------------- +call(Pid, Msg) -> + gen_server:call(Pid, Msg, infinity). + +split_options(Opts) -> + split_options(Opts, emulated_options(), [], []). +split_options([], _, SocketOpts, EmuOpts) -> + {SocketOpts, EmuOpts}; +split_options([{Name, _} = Opt | Opts], Emu, SocketOpts, EmuOpts) -> + case lists:member(Name, Emu) of + true -> + split_options(Opts, Emu, SocketOpts, [Opt | EmuOpts]); + false -> + split_options(Opts, Emu, [Opt | SocketOpts], EmuOpts) + end; +split_options([Name | Opts], Emu, SocketOptNames, EmuOptNames) -> + case lists:member(Name, Emu) of + true -> + split_options(Opts, Emu, SocketOptNames, [Name | EmuOptNames]); + false -> + split_options(Opts, Emu, [Name | SocketOptNames], EmuOptNames) + end. + +do_set_emulated_opts([], Opts) -> + Opts; +do_set_emulated_opts([{Name,_} = Opt | Rest], Opts) -> + do_set_emulated_opts(Rest, [Opt | proplists:delete(Name, Opts)]). + +get_socket_opts(_, [], _) -> + []; +get_socket_opts(ListenSocket, SockOptNames, Cb) -> + {ok, Opts} = Cb:getopts(ListenSocket, SockOptNames), + Opts. + +get_emulated_opts(TrackerPid, EmOptNames) -> + {ok, EmOpts} = get_emulated_opts(TrackerPid), + lists:map(fun(Name) -> {value, Value} = lists:keysearch(Name, 1, EmOpts), + Value end, + EmOptNames). diff --git a/lib/ssl/src/ssl_srp.hrl b/lib/ssl/src/ssl_srp.hrl index ab2be33ab2..d6e45adeee 100644 --- a/lib/ssl/src/ssl_srp.hrl +++ b/lib/ssl/src/ssl_srp.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -23,9 +24,14 @@ %% see RFC 5054 %%---------------------------------------------------------------------- +-ifndef(ssl_srp). +-define(ssl_srp, true). + -record(srp_user, { generator :: binary(), prime :: binary(), salt :: binary(), verifier :: binary() }). + +-endif. % -ifdef(ssl_srp). diff --git a/lib/ssl/src/ssl_ssl2.erl b/lib/ssl/src/ssl_ssl2.erl deleted file mode 100644 index a9ab6a2678..0000000000 --- a/lib/ssl/src/ssl_ssl2.erl +++ /dev/null @@ -1,37 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2007-2011. All Rights Reserved. -%% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. -%% -%% %CopyrightEnd% -%% - -%% -%%---------------------------------------------------------------------- -%% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher -%% will send an sslv2 hello. -%%---------------------------------------------------------------------- - --module(ssl_ssl2). - --export([client_random/2]). - -client_random(ChallengeData, 32) -> - ChallengeData; -client_random(ChallengeData, N) when N > 32 -> - <<NewChallengeData:32/binary, _/binary>> = ChallengeData, - NewChallengeData; -client_random(ChallengeData, N) -> - Pad = list_to_binary(lists:duplicate(N, 0)), - <<Pad/binary, ChallengeData/binary>>. diff --git a/lib/ssl/src/ssl_sup.erl b/lib/ssl/src/ssl_sup.erl index 59039a6e0a..7fa1f7dc9e 100644 --- a/lib/ssl/src/ssl_sup.erl +++ b/lib/ssl/src/ssl_sup.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1998-2011. All Rights Reserved. +%% Copyright Ericsson AB 1998-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -43,22 +44,14 @@ start_link() -> %%%========================================================================= init([]) -> - %% OLD ssl - moved start to ssl.erl only if old - %% ssl is acctualy run! - %%Child1 = {ssl_server, {ssl_server, start_link, []}, - %% permanent, 2000, worker, [ssl_server]}, - - %% Does not start any port programs so it does matter - %% so much if it is not used! - %% Child2 = {ssl_broker_sup, {ssl_broker_sup, start_link, []}, - %% permanent, 2000, supervisor, [ssl_broker_sup]}, - - - %% New ssl SessionCertManager = session_and_cert_manager_child_spec(), - ConnetionManager = connection_manager_child_spec(), - - {ok, {{one_for_all, 10, 3600}, [SessionCertManager, ConnetionManager]}}. + TLSConnetionManager = tls_connection_manager_child_spec(), + %% Not supported yet + %%DTLSConnetionManager = tls_connection_manager_child_spec(), + %% Handles emulated options so that they inherited by the accept socket, even when setopts is performed on + %% the listen socket + ListenOptionsTracker = listen_options_tracker_child_spec(), + {ok, {{one_for_all, 10, 3600}, [SessionCertManager, TLSConnetionManager, ListenOptionsTracker]}}. manager_opts() -> @@ -90,12 +83,31 @@ session_and_cert_manager_child_spec() -> Type = worker, {Name, StartFunc, Restart, Shutdown, Type, Modules}. -connection_manager_child_spec() -> - Name = ssl_connection, - StartFunc = {ssl_connection_sup, start_link, []}, +tls_connection_manager_child_spec() -> + Name = tls_connection, + StartFunc = {tls_connection_sup, start_link, []}, + Restart = permanent, + Shutdown = 4000, + Modules = [tls_connection_sup], + Type = supervisor, + {Name, StartFunc, Restart, Shutdown, Type, Modules}. + +%% dtls_connection_manager_child_spec() -> +%% Name = dtls_connection, +%% StartFunc = {dtls_connection_sup, start_link, []}, +%% Restart = permanent, +%% Shutdown = 4000, +%% Modules = [dtls_connection, ssl_connection], +%% Type = supervisor, +%% {Name, StartFunc, Restart, Shutdown, Type, Modules}. + + +listen_options_tracker_child_spec() -> + Name = ssl_socket, + StartFunc = {ssl_listen_tracker_sup, start_link, []}, Restart = permanent, Shutdown = 4000, - Modules = [ssl_connection], + Modules = [ssl_socket], Type = supervisor, {Name, StartFunc, Restart, Shutdown, Type, Modules}. diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl index a22af6b960..a920f54ed2 100644 --- a/lib/ssl/src/ssl_tls_dist_proxy.erl +++ b/lib/ssl/src/ssl_tls_dist_proxy.erl @@ -1,25 +1,26 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% -module(ssl_tls_dist_proxy). --export([listen/1, accept/1, connect/2, get_tcp_address/1]). +-export([listen/2, accept/2, connect/3, get_tcp_address/1]). -export([init/1, start_link/0, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, ssl_options/2]). @@ -38,14 +39,63 @@ %% Internal application API %%==================================================================== -listen(Name) -> - gen_server:call(?MODULE, {listen, Name}, infinity). +listen(Driver, Name) -> + gen_server:call(?MODULE, {listen, Driver, Name}, infinity). + +accept(Driver, Listen) -> + gen_server:call(?MODULE, {accept, Driver, Listen}, infinity). + +connect(Driver, Ip, Port) -> + gen_server:call(?MODULE, {connect, Driver, Ip, Port}, infinity). + + +do_listen(Options) -> + {First,Last} = case application:get_env(kernel,inet_dist_listen_min) of + {ok,N} when is_integer(N) -> + case application:get_env(kernel, + inet_dist_listen_max) of + {ok,M} when is_integer(M) -> + {N,M}; + _ -> + {N,N} + end; + _ -> + {0,0} + end, + do_listen(First, Last, listen_options([{backlog,128}|Options])). + +do_listen(First,Last,_) when First > Last -> + {error,eaddrinuse}; +do_listen(First,Last,Options) -> + case gen_tcp:listen(First, Options) of + {error, eaddrinuse} -> + do_listen(First+1,Last,Options); + Other -> + Other + end. -accept(Listen) -> - gen_server:call(?MODULE, {accept, Listen}, infinity). +listen_options(Opts0) -> + Opts1 = + case application:get_env(kernel, inet_dist_use_interface) of + {ok, Ip} -> + [{ip, Ip} | Opts0]; + _ -> + Opts0 + end, + case application:get_env(kernel, inet_dist_listen_options) of + {ok,ListenOpts} -> + ListenOpts ++ Opts1; + _ -> + Opts1 + end. -connect(Ip, Port) -> - gen_server:call(?MODULE, {connect, Ip, Port}, infinity). +connect_options(Opts) -> + case application:get_env(kernel, inet_dist_connect_options) of + {ok,ConnectOpts} -> + lists:ukeysort(1, ConnectOpts ++ Opts); + _ -> + Opts + end. %%==================================================================== %% gen_server callbacks @@ -58,29 +108,35 @@ init([]) -> process_flag(priority, max), {ok, #state{}}. -handle_call({listen, Name}, _From, State) -> - case gen_tcp:listen(0, [{active, false}, {packet,?PPRE}]) of +handle_call({listen, Driver, Name}, _From, State) -> + case gen_tcp:listen(0, [{active, false}, {packet,?PPRE}, {ip, loopback}]) of {ok, Socket} -> - {ok, World} = gen_tcp:listen(0, [{active, false}, binary, {packet,?PPRE}]), + {ok, World} = do_listen([{active, false}, binary, {packet,?PPRE}, {reuseaddr, true}, + Driver:family()]), {ok, TcpAddress} = get_tcp_address(Socket), {ok, WorldTcpAddress} = get_tcp_address(World), {_,Port} = WorldTcpAddress#net_address.address, - {ok, Creation} = erl_epmd:register_node(Name, Port), - {reply, {ok, {Socket, TcpAddress, Creation}}, - State#state{listen={Socket, World}}}; + ErlEpmd = net_kernel:epmd_module(), + case ErlEpmd:register_node(Name, Port) of + {ok, Creation} -> + {reply, {ok, {Socket, TcpAddress, Creation}}, + State#state{listen={Socket, World}}}; + {error, _} = Error -> + {reply, Error, State} + end; Error -> {reply, Error, State} end; -handle_call({accept, Listen}, {From, _}, State = #state{listen={_, World}}) -> +handle_call({accept, _Driver, Listen}, {From, _}, State = #state{listen={_, World}}) -> Self = self(), ErtsPid = spawn_link(fun() -> accept_loop(Self, erts, Listen, From) end), WorldPid = spawn_link(fun() -> accept_loop(Self, world, World, Listen) end), {reply, ErtsPid, State#state{accept_loop={ErtsPid, WorldPid}}}; -handle_call({connect, Ip, Port}, {From, _}, State) -> +handle_call({connect, Driver, Ip, Port}, {From, _}, State) -> Me = self(), - Pid = spawn_link(fun() -> setup_proxy(Ip, Port, Me) end), + Pid = spawn_link(fun() -> setup_proxy(Driver, Ip, Port, Me) end), receive {Pid, go_ahead, LPort} -> Res = {ok, Socket} = try_connect(LPort), @@ -133,12 +189,18 @@ accept_loop(Proxy, erts = Type, Listen, Extra) -> Extra ! {accept,self(),Socket,inet,proxy}, receive {_Kernel, controller, Pid} -> + inet:setopts(Socket, [nodelay()]), ok = gen_tcp:controlling_process(Socket, Pid), flush_old_controller(Pid, Socket), Pid ! {self(), controller}; {_Kernel, unsupported_protocol} -> exit(unsupported_protocol) end; + {error, closed} -> + %% The listening socket is closed: the proxy process is + %% shutting down. Exit normally, to avoid generating a + %% spurious error report. + exit(normal); Error -> exit(Error) end, @@ -149,6 +211,7 @@ accept_loop(Proxy, world = Type, Listen, Extra) -> case gen_tcp:accept(Listen) of {ok, Socket} -> Opts = get_ssl_options(server), + wait_for_code_server(), case ssl:ssl_accept(Socket, Opts) of {ok, SslSocket} -> PairHandler = @@ -157,6 +220,11 @@ accept_loop(Proxy, world = Type, Listen, Extra) -> end), ok = ssl:controlling_process(SslSocket, PairHandler), flush_old_controller(PairHandler, SslSocket); + {error, {options, _}} = Error -> + %% Bad options: that's probably our fault. Let's log that. + error_logger:error_msg("Cannot accept TLS distribution connection: ~s~n", + [ssl:format_error(Error)]), + gen_tcp:close(Socket); _ -> gen_tcp:close(Socket) end; @@ -165,20 +233,50 @@ accept_loop(Proxy, world = Type, Listen, Extra) -> end, accept_loop(Proxy, Type, Listen, Extra). +wait_for_code_server() -> + %% This is an ugly hack. Upgrading a socket to TLS requires the + %% crypto module to be loaded. Loading the crypto module triggers + %% its on_load function, which calls code:priv_dir/1 to find the + %% directory where its NIF library is. However, distribution is + %% started earlier than the code server, so the code server is not + %% necessarily started yet, and code:priv_dir/1 might fail because + %% of that, if we receive an incoming connection on the + %% distribution port early enough. + %% + %% If the on_load function of a module fails, the module is + %% unloaded, and the function call that triggered loading it fails + %% with 'undef', which is rather confusing. + %% + %% Thus, the ssl_tls_dist_proxy process will terminate, and be + %% restarted by ssl_dist_sup. However, it won't have any memory + %% of being asked by net_kernel to listen for incoming + %% connections. Hence, the node will believe that it's open for + %% distribution, but it actually isn't. + %% + %% So let's avoid that by waiting for the code server to start. + case whereis(code_server) of + undefined -> + timer:sleep(10), + wait_for_code_server(); + Pid when is_pid(Pid) -> + ok + end. + try_connect(Port) -> - case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,?PPRE}]) of + case gen_tcp:connect({127,0,0,1}, Port, [{active, false}, {packet,?PPRE}, nodelay()]) of R = {ok, _S} -> R; {error, _R} -> try_connect(Port) end. -setup_proxy(Ip, Port, Parent) -> +setup_proxy(Driver, Ip, Port, Parent) -> process_flag(trap_exit, true), - Opts = get_ssl_options(client), - case ssl:connect(Ip, Port, [{active, true}, binary, {packet,?PPRE}] ++ Opts) of + Opts = connect_options(get_ssl_options(client)), + case ssl:connect(Ip, Port, [{active, true}, binary, {packet,?PPRE}, nodelay(), + Driver:family()] ++ Opts) of {ok, World} -> - {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, {ip, {127,0,0,1}}, binary, {packet,?PPRE}]), + {ok, ErtsL} = gen_tcp:listen(0, [{active, true}, {ip, loopback}, binary, {packet,?PPRE}]), {ok, #net_address{address={_,LPort}}} = get_tcp_address(ErtsL), Parent ! {self(), go_ahead, LPort}, case gen_tcp:accept(ErtsL) of @@ -188,29 +286,50 @@ setup_proxy(Ip, Port, Parent) -> Err -> Parent ! {self(), Err} end; + {error, {options, _}} = Err -> + %% Bad options: that's probably our fault. Let's log that. + error_logger:error_msg("Cannot open TLS distribution connection: ~s~n", + [ssl:format_error(Err)]), + Parent ! {self(), Err}; Err -> Parent ! {self(), Err} end. + +%% we may not always want the nodelay behaviour +%% %% for performance reasons + +nodelay() -> + case application:get_env(kernel, dist_nodelay) of + undefined -> + {nodelay, true}; + {ok, true} -> + {nodelay, true}; + {ok, false} -> + {nodelay, false}; + _ -> + {nodelay, true} + end. + setup_connection(World, ErtsListen) -> process_flag(trap_exit, true), {ok, TcpAddress} = get_tcp_address(ErtsListen), {_Addr,Port} = TcpAddress#net_address.address, - {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,?PPRE}]), - ssl:setopts(World, [{active,true}, {packet,?PPRE}]), + {ok, Erts} = gen_tcp:connect({127,0,0,1}, Port, [{active, true}, binary, {packet,?PPRE}, nodelay()]), + ssl:setopts(World, [{active,true}, {packet,?PPRE}, nodelay()]), loop_conn_setup(World, Erts). loop_conn_setup(World, Erts) -> receive {ssl, World, Data = <<$a, _/binary>>} -> gen_tcp:send(Erts, Data), - ssl:setopts(World, [{packet,?PPOST}]), - inet:setopts(Erts, [{packet,?PPOST}]), + ssl:setopts(World, [{packet,?PPOST}, nodelay()]), + inet:setopts(Erts, [{packet,?PPOST}, nodelay()]), loop_conn(World, Erts); {tcp, Erts, Data = <<$a, _/binary>>} -> ssl:send(World, Data), - ssl:setopts(World, [{packet,?PPOST}]), - inet:setopts(Erts, [{packet,?PPOST}]), + ssl:setopts(World, [{packet,?PPOST}, nodelay()]), + inet:setopts(Erts, [{packet,?PPOST}, nodelay()]), loop_conn(World, Erts); {ssl, World, Data = <<_, _/binary>>} -> gen_tcp:send(Erts, Data), @@ -227,7 +346,10 @@ loop_conn_setup(World, Erts) -> {tcp_closed, Erts} -> ssl:close(World); {ssl_closed, World} -> - gen_tcp:close(Erts) + gen_tcp:close(Erts); + {ssl_error, World, _} -> + + ssl:close(World) end. loop_conn(World, Erts) -> @@ -241,7 +363,9 @@ loop_conn(World, Erts) -> {tcp_closed, Erts} -> ssl:close(World); {ssl_closed, World} -> - gen_tcp:close(Erts) + gen_tcp:close(Erts); + {ssl_error, World, _} -> + ssl:close(World) end. get_ssl_options(Type) -> @@ -278,6 +402,18 @@ ssl_options(server, ["server_verify", Value|T]) -> [{verify, atomize(Value)} | ssl_options(server,T)]; ssl_options(client, ["client_verify", Value|T]) -> [{verify, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, ["server_verify_fun", Value|T]) -> + [{verify_fun, verify_fun(Value)} | ssl_options(server,T)]; +ssl_options(client, ["client_verify_fun", Value|T]) -> + [{verify_fun, verify_fun(Value)} | ssl_options(client,T)]; +ssl_options(server, ["server_crl_check", Value|T]) -> + [{crl_check, atomize(Value)} | ssl_options(server,T)]; +ssl_options(client, ["client_crl_check", Value|T]) -> + [{crl_check, atomize(Value)} | ssl_options(client,T)]; +ssl_options(server, ["server_crl_cache", Value|T]) -> + [{crl_cache, termify(Value)} | ssl_options(server,T)]; +ssl_options(client, ["client_crl_cache", Value|T]) -> + [{crl_cache, termify(Value)} | ssl_options(client,T)]; ssl_options(server, ["server_reuse_sessions", Value|T]) -> [{reuse_sessions, atomize(Value)} | ssl_options(server,T)]; ssl_options(client, ["client_reuse_sessions", Value|T]) -> @@ -302,14 +438,28 @@ ssl_options(server, ["server_dhfile", Value|T]) -> [{dhfile, Value} | ssl_options(server,T)]; ssl_options(server, ["server_fail_if_no_peer_cert", Value|T]) -> [{fail_if_no_peer_cert, atomize(Value)} | ssl_options(server,T)]; -ssl_options(_,_) -> - exit(malformed_ssl_dist_opt). +ssl_options(Type, Opts) -> + error(malformed_ssl_dist_opt, [Type, Opts]). atomize(List) when is_list(List) -> list_to_atom(List); atomize(Atom) when is_atom(Atom) -> Atom. +termify(String) when is_list(String) -> + {ok, Tokens, _} = erl_scan:string(String ++ "."), + {ok, Term} = erl_parse:parse_term(Tokens), + Term. + +verify_fun(Value) -> + case termify(Value) of + {Mod, Func, State} when is_atom(Mod), is_atom(Func) -> + Fun = fun Mod:Func/3, + {Fun, State}; + _ -> + error(malformed_ssl_dist_opt, [Value]) + end. + flush_old_controller(Pid, Socket) -> receive {tcp, Socket, Data} -> diff --git a/lib/ssl/src/ssl_v2.erl b/lib/ssl/src/ssl_v2.erl new file mode 100644 index 0000000000..37134cbe5d --- /dev/null +++ b/lib/ssl/src/ssl_v2.erl @@ -0,0 +1,38 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher +%% will send an sslv2 hello. +%%---------------------------------------------------------------------- + +-module(ssl_v2). + +-export([client_random/2]). + +client_random(ChallengeData, 32) -> + ChallengeData; +client_random(ChallengeData, N) when N > 32 -> + <<NewChallengeData:32/binary, _/binary>> = ChallengeData, + NewChallengeData; +client_random(ChallengeData, N) -> + Pad = list_to_binary(lists:duplicate(N, 0)), + <<Pad/binary, ChallengeData/binary>>. diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_v3.erl index 013c27ebb5..82d165f995 100644 --- a/lib/ssl/src/ssl_ssl3.erl +++ b/lib/ssl/src/ssl_v3.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -22,14 +23,14 @@ %% Purpose: Handles sslv3 encryption. %%---------------------------------------------------------------------- --module(ssl_ssl3). +-module(ssl_v3). -include("ssl_cipher.hrl"). -include("ssl_internal.hrl"). -include("ssl_record.hrl"). % MD5 and SHA -export([master_secret/3, finished/3, certificate_verify/3, - mac_hash/6, setup_keys/7, + mac_hash/6, setup_keys/7, suites/0]). -compile(inline). @@ -40,7 +41,7 @@ -spec master_secret(binary(), binary(), binary()) -> binary(). master_secret(PremasterSecret, ClientRandom, ServerRandom) -> - %% draft-ietf-tls-ssl-version3-00 - 6.2.2 + %% draft-ietf-tls-ssl-version3-00 - 6.2.2 %% key_block = %% MD5(master_secret + SHA(`A' + master_secret + %% ServerHello.random + @@ -62,7 +63,7 @@ finished(Role, MasterSecret, Handshake) -> %% opaque md5_hash[16]; %% opaque sha_hash[20]; %% } Finished; - %% + %% %% md5_hash MD5(master_secret + pad2 + %% MD5(handshake_messages + Sender + %% master_secret + pad1)); @@ -95,23 +96,23 @@ certificate_verify(sha, MasterSecret, Handshake) -> handshake_hash(?SHA, MasterSecret, undefined, Handshake). --spec mac_hash(integer(), binary(), integer(), integer(), integer(), binary()) -> binary(). +-spec mac_hash(integer(), binary(), integer(), integer(), integer(), binary()) -> binary(). mac_hash(Method, Mac_write_secret, Seq_num, Type, Length, Fragment) -> - %% draft-ietf-tls-ssl-version3-00 - 5.2.3.1 + %% draft-ietf-tls-ssl-version3-00 - 5.2.3.1 %% hash(MAC_write_secret + pad_2 + %% hash(MAC_write_secret + pad_1 + seq_num + %% SSLCompressed.type + SSLCompressed.length + %% SSLCompressed.fragment)); - Mac = mac_hash(Method, Mac_write_secret, - [<<?UINT64(Seq_num), ?BYTE(Type), + Mac = mac_hash(Method, Mac_write_secret, + [<<?UINT64(Seq_num), ?BYTE(Type), ?UINT16(Length)>>, Fragment]), Mac. --spec setup_keys(binary(), binary(), binary(), - integer(), integer(), term(), integer()) -> - {binary(), binary(), binary(), - binary(), binary(), binary()}. +-spec setup_keys(binary(), binary(), binary(), + integer(), integer(), term(), integer()) -> + {binary(), binary(), binary(), + binary(), binary(), binary()}. setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) -> KeyBlock = generate_keyblock(MasterSecret, ServerRandom, ClientRandom, @@ -130,10 +131,10 @@ setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) -> {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, ServerWriteKey, ClientIV, ServerIV}. --spec suites() -> [cipher_suite()]. +-spec suites() -> [ssl_cipher:cipher_suite()]. suites() -> - [ + [ ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, ?TLS_RSA_WITH_AES_256_CBC_SHA, @@ -142,11 +143,7 @@ suites() -> ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - %%?TLS_RSA_WITH_IDEA_CBC_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_RSA_WITH_DES_CBC_SHA + ?TLS_RSA_WITH_AES_128_CBC_SHA ]. %%-------------------------------------------------------------------- diff --git a/lib/ssl/src/tls.erl b/lib/ssl/src/tls.erl index bb02695c12..aa41cd1ba6 100644 --- a/lib/ssl/src/tls.erl +++ b/lib/ssl/src/tls.erl @@ -1,165 +1,62 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1999-2013. All Rights Reserved. +%% Copyright Ericsson AB 1999-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% %% -%%% Purpose : Main API module for SSL. +%%% Purpose : Reflect TLS specific API options (fairly simple wrapper at the moment) -module(tls). --export([start/0, start/1, stop/0, transport_accept/1, - transport_accept/2, ssl_accept/1, ssl_accept/2, ssl_accept/3, - cipher_suites/0, cipher_suites/1, suite_definition/1, - close/1, shutdown/2, - connect/3, connect/2, connect/4, connection_info/1, - controlling_process/2, listen/2, peername/1, peercert/1, - recv/2, recv/3, send/2, getopts/2, setopts/2, sockname/1, - versions/0, session_info/1, format_error/1, - renegotiate/1, prf/5, clear_pem_cache/0, random_bytes/1, negotiated_next_protocol/1]). - +-include("ssl_api.hrl"). -include("ssl_internal.hrl"). --include("ssl_record.hrl"). --include("ssl_cipher.hrl"). --include("ssl_handshake.hrl"). --include("ssl_srp.hrl"). - --include_lib("public_key/include/public_key.hrl"). - -%% Visible in API --export_type([connect_option/0, listen_option/0, ssl_option/0, transport_option/0, - erl_cipher_suite/0, %% From ssl_cipher.hrl - tls_atom_version/0, %% From ssl_internal.hrl - prf_random/0, sslsocket/0]). - --record(config, {ssl, %% SSL parameters - inet_user, %% User set inet options - emulated, %% #socket_option{} emulated - inet_ssl, %% inet options for internal ssl socket - cb %% Callback info - }). --type sslsocket() :: #sslsocket{}. --type connect_option() :: socket_connect_option() | ssl_option() | transport_option(). --type socket_connect_option() :: gen_tcp:connect_option(). --type listen_option() :: socket_listen_option() | ssl_option() | transport_option(). --type socket_listen_option() :: gen_tcp:listen_option(). - --type ssl_option() :: {verify, verify_type()} | - {verify_fun, {fun(), InitialUserState::term()}} | - {fail_if_no_peer_cert, boolean()} | {depth, integer()} | - {cert, Der::binary()} | {certfile, path()} | {key, Der::binary()} | - {keyfile, path()} | {password, string()} | {cacerts, [Der::binary()]} | - {cacertfile, path()} | {dh, Der::binary()} | {dhfile, path()} | - {user_lookup_fun, {fun(), InitialUserState::term()}} | - {psk_identity, string()} | - {srp_identity, {string(), string()}} | - {ciphers, ciphers()} | {ssl_imp, ssl_imp()} | {reuse_sessions, boolean()} | - {reuse_session, fun()} | {hibernate_after, integer()|undefined} | - {next_protocols_advertised, list(binary())} | - {client_preferred_next_protocols, binary(), client | server, list(binary())}. - --type verify_type() :: verify_none | verify_peer. --type path() :: string(). --type ciphers() :: [erl_cipher_suite()] | - string(). % (according to old API) --type ssl_imp() :: new | old. - --type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(), - ClosedTag::atom(), ErrTag::atom()}}. --type prf_random() :: client_random | server_random. +-export([connect/2, connect/3, listen/2, accept/1, accept/2, + handshake/1, handshake/2, handshake/3]). %%-------------------------------------------------------------------- --spec start() -> ok | {error, reason()}. --spec start(permanent | transient | temporary) -> ok | {error, reason()}. %% -%% Description: Utility function that starts the ssl, -%% crypto and public_key applications. Default type -%% is temporary. see application(3) +%% Description: Connect to an TLS server. %%-------------------------------------------------------------------- -start() -> - application:start(crypto), - application:start(asn1), - application:start(public_key), - application:start(ssl). -start(Type) -> - application:start(crypto, Type), - application:start(asn1), - application:start(public_key, Type), - application:start(ssl, Type). - -%%-------------------------------------------------------------------- --spec stop() -> ok. -%% -%% Description: Stops the ssl application. -%%-------------------------------------------------------------------- -stop() -> - application:stop(ssl). - -%%-------------------------------------------------------------------- -spec connect(host() | port(), [connect_option()]) -> {ok, #sslsocket{}} | {error, reason()}. --spec connect(host() | port(), [connect_option()] | inet:port_number(), - timeout() | list()) -> - {ok, #sslsocket{}} | {error, reason()}. --spec connect(host() | port(), inet:port_number(), list(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Connect to an ssl server. -%%-------------------------------------------------------------------- -connect(Socket, SslOptions) when is_port(Socket) -> - connect(Socket, SslOptions, infinity). +connect(Socket, Options) when is_port(Socket) -> + connect(Socket, Options, infinity). -connect(Socket, SslOptions0, Timeout) when is_port(Socket) -> - {Transport,_,_,_} = proplists:get_value(cb_info, SslOptions0, - {gen_tcp, tcp, tcp_closed, tcp_error}), - EmulatedOptions = emulated_options(), - {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), - try handle_options(SslOptions0 ++ SocketValues, client) of - {ok, #config{cb = CbInfo, ssl = SslOptions, emulated = EmOpts}} -> - - ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()), - case ssl_socket:peername(Transport, Socket) of - {ok, {Address, Port}} -> - tls_connection:connect(Address, Port, Socket, - {SslOptions, EmOpts}, - self(), CbInfo, Timeout); - {error, Error} -> - {error, Error} - end - catch - _:{error, Reason} -> - {error, Reason} - end; +-spec connect(host() | port(), [connect_option()] | inet:port_number(), + timeout() | list()) -> + {ok, #sslsocket{}} | {error, reason()}. +connect(Socket, SslOptions, Timeout) when is_port(Socket) -> + TLSOpts = [{protocol, tls} | SslOptions], + ssl:connect(Socket, TLSOpts, Timeout); connect(Host, Port, Options) -> connect(Host, Port, Options, infinity). +-spec connect(host() | port(), inet:port_number(), list(), timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. + connect(Host, Port, Options, Timeout) -> - try handle_options(Options, client) of - {ok, Config} -> - do_connect(Host,Port,Config,Timeout) - catch - throw:Error -> - Error - end. + TLSOpts = [{protocol, tls} | Options], + ssl:connect(Host, Port, TLSOpts, Timeout). %%-------------------------------------------------------------------- -spec listen(inet:port_number(), [listen_option()]) ->{ok, #sslsocket{}} | {error, reason()}. @@ -167,873 +64,49 @@ connect(Host, Port, Options, Timeout) -> %% %% Description: Creates an ssl listen socket. %%-------------------------------------------------------------------- -listen(_Port, []) -> - {error, nooptions}; -listen(Port, Options0) -> - try - {ok, Config} = handle_options(Options0, server), - #config{cb = {Transport, _, _, _}, inet_user = Options} = Config, - case Transport:listen(Port, Options) of - {ok, ListenSocket} -> - {ok, #sslsocket{pid = {ListenSocket, Config}}}; - Err = {error, _} -> - Err - end - catch - Error = {error, _} -> - Error - end. +listen(Port, Options) -> + TLSOpts = [{protocol, tls} | Options], + ssl:listen(Port, TLSOpts). + %%-------------------------------------------------------------------- --spec transport_accept(#sslsocket{}) -> {ok, #sslsocket{}} | - {error, reason()}. --spec transport_accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | - {error, reason()}. %% %% Description: Performs transport accept on an ssl listen socket %%-------------------------------------------------------------------- -transport_accept(ListenSocket) -> - transport_accept(ListenSocket, infinity). +-spec accept(#sslsocket{}) -> {ok, #sslsocket{}} | + {error, reason()}. +accept(ListenSocket) -> + accept(ListenSocket, infinity). -transport_accept(#sslsocket{pid = {ListenSocket, #config{cb = CbInfo, ssl = SslOpts}}}, Timeout) -> - - %% The setopt could have been invoked on the listen socket - %% and options should be inherited. - EmOptions = emulated_options(), - {Transport,_,_, _} = CbInfo, - {ok, SocketValues} = ssl_socket:getopts(Transport, ListenSocket, EmOptions), - ok = ssl_socket:setopts(Transport, ListenSocket, internal_inet_values()), - case Transport:accept(ListenSocket, Timeout) of - {ok, Socket} -> - ok = ssl_socket:setopts(Transport, ListenSocket, SocketValues), - {ok, Port} = ssl_socket:port(Transport, Socket), - ConnArgs = [server, "localhost", Port, Socket, - {SslOpts, socket_options(SocketValues)}, self(), CbInfo], - case ssl_connection_sup:start_child(ConnArgs) of - {ok, Pid} -> - tls_connection:socket_control(Socket, Pid, Transport); - {error, Reason} -> - {error, Reason} - end; - {error, Reason} -> - {error, Reason} - end. +-spec accept(#sslsocket{}, timeout()) -> {ok, #sslsocket{}} | + {error, reason()}. +accept(Socket, Timeout) -> + ssl:transport_accept(Socket, Timeout). %%-------------------------------------------------------------------- --spec ssl_accept(#sslsocket{}) -> ok | {error, reason()}. --spec ssl_accept(#sslsocket{} | port(), timeout()| [ssl_option() - | transport_option()]) -> - ok | {ok, #sslsocket{}} | {error, reason()}. --spec ssl_accept(port(), [ssl_option()| transport_option()], timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. %% %% Description: Performs accept on an ssl listen socket. e.i. performs %% ssl handshake. %%-------------------------------------------------------------------- -ssl_accept(ListenSocket) -> - ssl_accept(ListenSocket, infinity). - -ssl_accept(#sslsocket{} = Socket, Timeout) -> - tls_connection:handshake(Socket, Timeout); - -ssl_accept(ListenSocket, SslOptions) when is_port(ListenSocket) -> - ssl_accept(ListenSocket, SslOptions, infinity). - -ssl_accept(Socket, SslOptions, Timeout) when is_port(Socket) -> - {Transport,_,_,_} = - proplists:get_value(cb_info, SslOptions, {gen_tcp, tcp, tcp_closed, tcp_error}), - EmulatedOptions = emulated_options(), - {ok, SocketValues} = ssl_socket:getopts(Transport, Socket, EmulatedOptions), - try handle_options(SslOptions ++ SocketValues, server) of - {ok, #config{cb = CbInfo, ssl = SslOpts, emulated = EmOpts}} -> - ok = ssl_socket:setopts(Transport, Socket, internal_inet_values()), - {ok, Port} = ssl_socket:port(Transport, Socket), - tls_connection:ssl_accept(Port, Socket, - {SslOpts, EmOpts}, - self(), CbInfo, Timeout) - catch - Error = {error, _Reason} -> Error - end. - -%%-------------------------------------------------------------------- --spec close(#sslsocket{}) -> term(). -%% -%% Description: Close an ssl connection -%%-------------------------------------------------------------------- -close(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:close(Pid); -close(#sslsocket{pid = {ListenSocket, #config{cb={Transport,_, _, _}}}}) -> - Transport:close(ListenSocket). - -%%-------------------------------------------------------------------- --spec send(#sslsocket{}, iodata()) -> ok | {error, reason()}. -%% -%% Description: Sends data over the ssl connection -%%-------------------------------------------------------------------- -send(#sslsocket{pid = Pid}, Data) when is_pid(Pid) -> - tls_connection:send(Pid, Data); -send(#sslsocket{pid = {ListenSocket, #config{cb={Transport, _, _, _}}}}, Data) -> - Transport:send(ListenSocket, Data). %% {error,enotconn} - -%%-------------------------------------------------------------------- --spec recv(#sslsocket{}, integer()) -> {ok, binary()| list()} | {error, reason()}. --spec recv(#sslsocket{}, integer(), timeout()) -> {ok, binary()| list()} | {error, reason()}. -%% -%% Description: Receives data when active = false -%%-------------------------------------------------------------------- -recv(Socket, Length) -> - recv(Socket, Length, infinity). -recv(#sslsocket{pid = Pid}, Length, Timeout) when is_pid(Pid) -> - tls_connection:recv(Pid, Length, Timeout); -recv(#sslsocket{pid = {Listen, - #config{cb={Transport, _, _, _}}}}, _,_) when is_port(Listen)-> - Transport:recv(Listen, 0). %% {error,enotconn} - -%%-------------------------------------------------------------------- --spec controlling_process(#sslsocket{}, pid()) -> ok | {error, reason()}. -%% -%% Description: Changes process that receives the messages when active = true -%% or once. -%%-------------------------------------------------------------------- -controlling_process(#sslsocket{pid = Pid}, NewOwner) when is_pid(Pid), is_pid(NewOwner) -> - tls_connection:new_user(Pid, NewOwner); -controlling_process(#sslsocket{pid = {Listen, - #config{cb={Transport, _, _, _}}}}, - NewOwner) when is_port(Listen), - is_pid(NewOwner) -> - Transport:controlling_process(Listen, NewOwner). - -%%-------------------------------------------------------------------- --spec connection_info(#sslsocket{}) -> {ok, {tls_atom_version(), erl_cipher_suite()}} | - {error, reason()}. -%% -%% Description: Returns ssl protocol and cipher used for the connection -%%-------------------------------------------------------------------- -connection_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:info(Pid); -connection_info(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec peername(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. -%% -%% Description: same as inet:peername/1. -%%-------------------------------------------------------------------- -peername(#sslsocket{pid = Pid, fd = {Transport, Socket}}) when is_pid(Pid)-> - ssl_socket:peername(Transport, Socket); -peername(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}) -> - ssl_socket:peername(Transport, ListenSocket). %% Will return {error, enotconn} - -%%-------------------------------------------------------------------- --spec peercert(#sslsocket{}) ->{ok, DerCert::binary()} | {error, reason()}. -%% -%% Description: Returns the peercert. -%%-------------------------------------------------------------------- -peercert(#sslsocket{pid = Pid}) when is_pid(Pid) -> - case tls_connection:peer_certificate(Pid) of - {ok, undefined} -> - {error, no_peercert}; - Result -> - Result - end; -peercert(#sslsocket{pid = {Listen, _}}) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec suite_definition(cipher_suite()) -> erl_cipher_suite(). -%% -%% Description: Return erlang cipher suite definition. -%%-------------------------------------------------------------------- -suite_definition(S) -> - {KeyExchange, Cipher, Hash, _} = ssl_cipher:suite_definition(S), - {KeyExchange, Cipher, Hash}. - -%%-------------------------------------------------------------------- --spec negotiated_next_protocol(#sslsocket{}) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the next protocol that has been negotiated. If no -%% protocol has been negotiated will return {error, next_protocol_not_negotiated} -%%-------------------------------------------------------------------- -negotiated_next_protocol(#sslsocket{pid = Pid}) -> - tls_connection:negotiated_next_protocol(Pid). - --spec cipher_suites() -> [erl_cipher_suite()]. --spec cipher_suites(erlang | openssl | all) -> [erl_cipher_suite()] | [string()]. - -%% Description: Returns all supported cipher suites. -%%-------------------------------------------------------------------- -cipher_suites() -> - cipher_suites(erlang). - -cipher_suites(erlang) -> - Version = tls_record:highest_protocol_version([]), - [suite_definition(S) || S <- ssl_cipher:suites(Version)]; -cipher_suites(openssl) -> - Version = tls_record:highest_protocol_version([]), - [ssl_cipher:openssl_suite_name(S) || S <- ssl_cipher:suites(Version)]; -cipher_suites(all) -> - Version = tls_record:highest_protocol_version([]), - Supported = ssl_cipher:suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), - [suite_definition(S) || S <- Supported]. +-spec handshake(#sslsocket{}) -> ok | {error, reason()}. -%%-------------------------------------------------------------------- --spec getopts(#sslsocket{}, [gen_tcp:option_name()]) -> - {ok, [gen_tcp:option()]} | {error, reason()}. -%% -%% Description: Gets options -%%-------------------------------------------------------------------- -getopts(#sslsocket{pid = Pid}, OptionTags) when is_pid(Pid), is_list(OptionTags) -> - tls_connection:get_opts(Pid, OptionTags); -getopts(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}, - OptionTags) when is_list(OptionTags) -> - try ssl_socket:getopts(Transport, ListenSocket, OptionTags) of - {ok, _} = Result -> - Result; - {error, InetError} -> - {error, {options, {socket_options, OptionTags, InetError}}} - catch - _:_ -> - {error, {options, {socket_options, OptionTags}}} - end; -getopts(#sslsocket{}, OptionTags) -> - {error, {options, {socket_options, OptionTags}}}. - -%%-------------------------------------------------------------------- --spec setopts(#sslsocket{}, [gen_tcp:option()]) -> ok | {error, reason()}. -%% -%% Description: Sets options -%%-------------------------------------------------------------------- -setopts(#sslsocket{pid = Pid}, Options0) when is_pid(Pid), is_list(Options0) -> - try proplists:expand([{binary, [{mode, binary}]}, - {list, [{mode, list}]}], Options0) of - Options -> - tls_connection:set_opts(Pid, Options) - catch - _:_ -> - {error, {options, {not_a_proplist, Options0}}} - end; - -setopts(#sslsocket{pid = {ListenSocket, #config{cb = {Transport,_,_,_}}}}, Options) when is_list(Options) -> - try ssl_socket:setopts(Transport, ListenSocket, Options) of - ok -> - ok; - {error, InetError} -> - {error, {options, {socket_options, Options, InetError}}} - catch - _:Error -> - {error, {options, {socket_options, Options, Error}}} - end; -setopts(#sslsocket{}, Options) -> - {error, {options,{not_a_proplist, Options}}}. - -%%--------------------------------------------------------------- --spec shutdown(#sslsocket{}, read | write | read_write) -> ok | {error, reason()}. -%% -%% Description: Same as gen_tcp:shutdown/2 -%%-------------------------------------------------------------------- -shutdown(#sslsocket{pid = {Listen, #config{cb={Transport,_, _, _}}}}, - How) when is_port(Listen) -> - Transport:shutdown(Listen, How); -shutdown(#sslsocket{pid = Pid}, How) -> - tls_connection:shutdown(Pid, How). - -%%-------------------------------------------------------------------- --spec sockname(#sslsocket{}) -> {ok, {inet:ip_address(), inet:port_number()}} | {error, reason()}. -%% -%% Description: Same as inet:sockname/1 -%%-------------------------------------------------------------------- -sockname(#sslsocket{pid = {Listen, #config{cb={Transport,_, _, _}}}}) when is_port(Listen) -> - ssl_socket:sockname(Transport, Listen); - -sockname(#sslsocket{pid = Pid, fd = {Transport, Socket}}) when is_pid(Pid) -> - ssl_socket:sockname(Transport, Socket). - -%%--------------------------------------------------------------- --spec session_info(#sslsocket{}) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns list of session info currently [{session_id, session_id(), -%% {cipher_suite, cipher_suite()}] -%%-------------------------------------------------------------------- -session_info(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:session_info(Pid); -session_info(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> - {error, enotconn}. - -%%--------------------------------------------------------------- --spec versions() -> [{ssl_app, string()} | {supported, [tls_atom_version()]} | - {available, [tls_atom_version()]}]. -%% -%% Description: Returns a list of relevant versions. -%%-------------------------------------------------------------------- -versions() -> - Vsns = tls_record:supported_protocol_versions(), - SupportedVsns = [tls_record:protocol_version(Vsn) || Vsn <- Vsns], - AvailableVsns = ?ALL_SUPPORTED_VERSIONS, - [{ssl_app, ?VSN}, {supported, SupportedVsns}, {available, AvailableVsns}]. - - -%%--------------------------------------------------------------- --spec renegotiate(#sslsocket{}) -> ok | {error, reason()}. -%% -%% Description: Initiates a renegotiation. -%%-------------------------------------------------------------------- -renegotiate(#sslsocket{pid = Pid}) when is_pid(Pid) -> - tls_connection:renegotiation(Pid); -renegotiate(#sslsocket{pid = {Listen,_}}) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec prf(#sslsocket{}, binary() | 'master_secret', binary(), - binary() | prf_random(), non_neg_integer()) -> - {ok, binary()} | {error, reason()}. -%% -%% Description: use a ssl sessions TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf(#sslsocket{pid = Pid}, - Secret, Label, Seed, WantedLength) when is_pid(Pid) -> - tls_connection:prf(Pid, Secret, Label, Seed, WantedLength); -prf(#sslsocket{pid = {Listen,_}}, _,_,_,_) when is_port(Listen) -> - {error, enotconn}. - -%%-------------------------------------------------------------------- --spec clear_pem_cache() -> ok. -%% -%% Description: Clear the PEM cache -%%-------------------------------------------------------------------- -clear_pem_cache() -> - ssl_manager:clear_pem_cache(). - -%%--------------------------------------------------------------- --spec format_error({error, term()}) -> list(). -%% -%% Description: Creates error string. -%%-------------------------------------------------------------------- -format_error({error, Reason}) -> - format_error(Reason); -format_error(Reason) when is_list(Reason) -> - Reason; -format_error(closed) -> - "TLS connection is closed"; -format_error({tls_alert, Description}) -> - "TLS Alert: " ++ Description; -format_error({options,{FileType, File, Reason}}) when FileType == cacertfile; - FileType == certfile; - FileType == keyfile; - FileType == dhfile -> - Error = file_error_format(Reason), - file_desc(FileType) ++ File ++ ": " ++ Error; -format_error({options, {socket_options, Option, Error}}) -> - lists:flatten(io_lib:format("Invalid transport socket option ~p: ~s", [Option, format_error(Error)])); -format_error({options, {socket_options, Option}}) -> - lists:flatten(io_lib:format("Invalid socket option: ~p", [Option])); -format_error({options, Options}) -> - lists:flatten(io_lib:format("Invalid TLS option: ~p", [Options])); - -format_error(Error) -> - case inet:format_error(Error) of - "unknown POSIX" ++ _ -> - unexpected_format(Error); - Other -> - Other - end. - -%%-------------------------------------------------------------------- --spec random_bytes(integer()) -> binary(). - -%% -%% Description: Generates cryptographically secure random sequence if possible -%% fallbacks on pseudo random function -%%-------------------------------------------------------------------- -random_bytes(N) -> - try crypto:strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - crypto:rand_bytes(N) - end. +handshake(ListenSocket) -> + handshake(ListenSocket, infinity). -%%%-------------------------------------------------------------- -%%% Internal functions -%%%-------------------------------------------------------------------- -do_connect(Address, Port, - #config{cb=CbInfo, inet_user=UserOpts, ssl=SslOpts, - emulated=EmOpts,inet_ssl=SocketOpts}, - Timeout) -> - {Transport, _, _, _} = CbInfo, - try Transport:connect(Address, Port, SocketOpts, Timeout) of - {ok, Socket} -> - tls_connection:connect(Address, Port, Socket, {SslOpts,EmOpts}, - self(), CbInfo, Timeout); - {error, Reason} -> - {error, Reason} - catch - exit:{function_clause, _} -> - {error, {options, {cb_info, CbInfo}}}; - exit:badarg -> - {error, {options, {socket_options, UserOpts}}}; - exit:{badarg, _} -> - {error, {options, {socket_options, UserOpts}}} - end. -handle_options(Opts0, _Role) -> - Opts = proplists:expand([{binary, [{mode, binary}]}, - {list, [{mode, list}]}], Opts0), - ReuseSessionFun = fun(_, _, _, _) -> true end, - - DefaultVerifyNoneFun = - {fun(_,{bad_cert, _}, UserState) -> - {valid, UserState}; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, []}, - - VerifyNoneFun = handle_option(verify_fun, Opts, DefaultVerifyNoneFun), - - UserFailIfNoPeerCert = handle_option(fail_if_no_peer_cert, Opts, false), - UserVerifyFun = handle_option(verify_fun, Opts, undefined), - CaCerts = handle_option(cacerts, Opts, undefined), - - {Verify, FailIfNoPeerCert, CaCertDefault, VerifyFun} = - %% Handle 0, 1, 2 for backwards compatibility - case proplists:get_value(verify, Opts, verify_none) of - 0 -> - {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun}; - 1 -> - {verify_peer, false, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; - 2 -> - {verify_peer, true, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; - verify_none -> - {verify_none, false, - ca_cert_default(verify_none, VerifyNoneFun, CaCerts), VerifyNoneFun}; - verify_peer -> - {verify_peer, UserFailIfNoPeerCert, - ca_cert_default(verify_peer, UserVerifyFun, CaCerts), UserVerifyFun}; - Value -> - throw({error, {options, {verify, Value}}}) - end, - - CertFile = handle_option(certfile, Opts, <<>>), - - Versions = case handle_option(versions, Opts, []) of - [] -> - tls_record:supported_protocol_versions(); - Vsns -> - [tls_record:protocol_version(Vsn) || Vsn <- Vsns] - end, - - SSLOptions = #ssl_options{ - versions = Versions, - verify = validate_option(verify, Verify), - verify_fun = VerifyFun, - fail_if_no_peer_cert = FailIfNoPeerCert, - verify_client_once = handle_option(verify_client_once, Opts, false), - depth = handle_option(depth, Opts, 1), - cert = handle_option(cert, Opts, undefined), - certfile = CertFile, - key = handle_option(key, Opts, undefined), - keyfile = handle_option(keyfile, Opts, CertFile), - password = handle_option(password, Opts, ""), - cacerts = CaCerts, - cacertfile = handle_option(cacertfile, Opts, CaCertDefault), - dh = handle_option(dh, Opts, undefined), - dhfile = handle_option(dhfile, Opts, undefined), - user_lookup_fun = handle_option(user_lookup_fun, Opts, undefined), - psk_identity = handle_option(psk_identity, Opts, undefined), - srp_identity = handle_option(srp_identity, Opts, undefined), - ciphers = handle_option(ciphers, Opts, []), - %% Server side option - reuse_session = handle_option(reuse_session, Opts, ReuseSessionFun), - reuse_sessions = handle_option(reuse_sessions, Opts, true), - secure_renegotiate = handle_option(secure_renegotiate, Opts, false), - renegotiate_at = handle_option(renegotiate_at, Opts, ?DEFAULT_RENEGOTIATE_AT), - hibernate_after = handle_option(hibernate_after, Opts, undefined), - erl_dist = handle_option(erl_dist, Opts, false), - next_protocols_advertised = - handle_option(next_protocols_advertised, Opts, undefined), - next_protocol_selector = - make_next_protocol_selector( - handle_option(client_preferred_next_protocols, Opts, undefined)) - }, - - CbInfo = proplists:get_value(cb_info, Opts, {gen_tcp, tcp, tcp_closed, tcp_error}), - SslOptions = [versions, verify, verify_fun, - fail_if_no_peer_cert, verify_client_once, - depth, cert, certfile, key, keyfile, - password, cacerts, cacertfile, dh, dhfile, - user_lookup_fun, psk_identity, srp_identity, ciphers, - reuse_session, reuse_sessions, ssl_imp, - cb_info, renegotiate_at, secure_renegotiate, hibernate_after, - erl_dist, next_protocols_advertised, - client_preferred_next_protocols], - - SockOpts = lists:foldl(fun(Key, PropList) -> - proplists:delete(Key, PropList) - end, Opts, SslOptions), - - {SSLsock, Emulated} = emulated_options(SockOpts), - {ok, #config{ssl=SSLOptions, emulated=Emulated, inet_ssl=SSLsock, - inet_user=SockOpts, cb=CbInfo}}. - -handle_option(OptionName, Opts, Default) -> - validate_option(OptionName, - proplists:get_value(OptionName, Opts, Default)). - - -validate_option(versions, Versions) -> - validate_versions(Versions, Versions); -validate_option(verify, Value) - when Value == verify_none; Value == verify_peer -> - Value; -validate_option(verify_fun, undefined) -> - undefined; -%% Backwards compatibility -validate_option(verify_fun, Fun) when is_function(Fun) -> - {fun(_,{bad_cert, _} = Reason, OldFun) -> - case OldFun([Reason]) of - true -> - {valid, OldFun}; - false -> - {fail, Reason} - end; - (_,{extension, _}, UserState) -> - {unknown, UserState}; - (_, valid, UserState) -> - {valid, UserState}; - (_, valid_peer, UserState) -> - {valid, UserState} - end, Fun}; -validate_option(verify_fun, {Fun, _} = Value) when is_function(Fun) -> - Value; -validate_option(fail_if_no_peer_cert, Value) - when Value == true; Value == false -> - Value; -validate_option(verify_client_once, Value) - when Value == true; Value == false -> - Value; -validate_option(depth, Value) when is_integer(Value), - Value >= 0, Value =< 255-> - Value; -validate_option(cert, Value) when Value == undefined; - is_binary(Value) -> - Value; -validate_option(certfile, undefined = Value) -> - Value; -validate_option(certfile, Value) when is_binary(Value) -> - Value; -validate_option(certfile, Value) when is_list(Value) -> - list_to_binary(Value); - -validate_option(key, undefined) -> - undefined; -validate_option(key, {KeyType, Value}) when is_binary(Value), - KeyType == rsa; %% Backwards compatibility - KeyType == dsa; %% Backwards compatibility - KeyType == 'RSAPrivateKey'; - KeyType == 'DSAPrivateKey'; - KeyType == 'PrivateKeyInfo' -> - {KeyType, Value}; - -validate_option(keyfile, undefined) -> - <<>>; -validate_option(keyfile, Value) when is_binary(Value) -> - Value; -validate_option(keyfile, Value) when is_list(Value), Value =/= "" -> - list_to_binary(Value); -validate_option(password, Value) when is_list(Value) -> - Value; - -validate_option(cacerts, Value) when Value == undefined; - is_list(Value) -> - Value; -%% certfile must be present in some cases otherwhise it can be set -%% to the empty string. -validate_option(cacertfile, undefined) -> - <<>>; -validate_option(cacertfile, Value) when is_binary(Value) -> - Value; -validate_option(cacertfile, Value) when is_list(Value), Value =/= ""-> - list_to_binary(Value); -validate_option(dh, Value) when Value == undefined; - is_binary(Value) -> - Value; -validate_option(dhfile, undefined = Value) -> - Value; -validate_option(dhfile, Value) when is_binary(Value) -> - Value; -validate_option(dhfile, Value) when is_list(Value), Value =/= "" -> - list_to_binary(Value); -validate_option(psk_identity, undefined) -> - undefined; -validate_option(psk_identity, Identity) - when is_list(Identity), Identity =/= "", length(Identity) =< 65535 -> - list_to_binary(Identity); -validate_option(user_lookup_fun, undefined) -> - undefined; -validate_option(user_lookup_fun, {Fun, _} = Value) when is_function(Fun, 3) -> - Value; -validate_option(srp_identity, undefined) -> - undefined; -validate_option(srp_identity, {Username, Password}) - when is_list(Username), is_list(Password), Username =/= "", length(Username) =< 255 -> - {list_to_binary(Username), list_to_binary(Password)}; - -validate_option(ciphers, Value) when is_list(Value) -> - Version = tls_record:highest_protocol_version([]), - try cipher_suites(Version, Value) - catch - exit:_ -> - throw({error, {options, {ciphers, Value}}}); - error:_-> - throw({error, {options, {ciphers, Value}}}) - end; -validate_option(reuse_session, Value) when is_function(Value) -> - Value; -validate_option(reuse_sessions, Value) when Value == true; - Value == false -> - Value; - -validate_option(secure_renegotiate, Value) when Value == true; - Value == false -> - Value; -validate_option(renegotiate_at, Value) when is_integer(Value) -> - erlang:min(Value, ?DEFAULT_RENEGOTIATE_AT); - -validate_option(hibernate_after, undefined) -> - undefined; -validate_option(hibernate_after, Value) when is_integer(Value), Value >= 0 -> - Value; -validate_option(erl_dist,Value) when Value == true; - Value == false -> - Value; -validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols} = Value) - when is_list(PreferredProtocols) -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), - {Precedence, PreferredProtocols, ?NO_PROTOCOL} - end; -validate_option(client_preferred_next_protocols = Opt, {Precedence, PreferredProtocols, Default} = Value) - when is_list(PreferredProtocols), is_binary(Default), - byte_size(Default) > 0, byte_size(Default) < 256 -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(client_preferred_next_protocols, PreferredProtocols), - validate_npn_ordering(Precedence), - Value - end; - -validate_option(client_preferred_next_protocols, undefined) -> - undefined; -validate_option(next_protocols_advertised = Opt, Value) when is_list(Value) -> - case tls_record:highest_protocol_version([]) of - {3,0} -> - throw({error, {options, {not_supported_in_sslv3, {Opt, Value}}}}); - _ -> - validate_binary_list(next_protocols_advertised, Value), - Value - end; - -validate_option(next_protocols_advertised, undefined) -> - undefined; -validate_option(Opt, Value) -> - throw({error, {options, {Opt, Value}}}). - -validate_npn_ordering(client) -> - ok; -validate_npn_ordering(server) -> - ok; -validate_npn_ordering(Value) -> - throw({error, {options, {client_preferred_next_protocols, {invalid_precedence, Value}}}}). - -validate_binary_list(Opt, List) -> - lists:foreach( - fun(Bin) when is_binary(Bin), - byte_size(Bin) > 0, - byte_size(Bin) < 256 -> - ok; - (Bin) -> - throw({error, {options, {Opt, {invalid_protocol, Bin}}}}) - end, List). - -validate_versions([], Versions) -> - Versions; -validate_versions([Version | Rest], Versions) when Version == 'tlsv1.2'; - Version == 'tlsv1.1'; - Version == tlsv1; - Version == sslv3 -> - validate_versions(Rest, Versions); -validate_versions([Ver| _], Versions) -> - throw({error, {options, {Ver, {versions, Versions}}}}). - -validate_inet_option(mode, Value) - when Value =/= list, Value =/= binary -> - throw({error, {options, {mode,Value}}}); -validate_inet_option(packet, Value) - when not (is_atom(Value) orelse is_integer(Value)) -> - throw({error, {options, {packet,Value}}}); -validate_inet_option(packet_size, Value) - when not is_integer(Value) -> - throw({error, {options, {packet_size,Value}}}); -validate_inet_option(header, Value) - when not is_integer(Value) -> - throw({error, {options, {header,Value}}}); -validate_inet_option(active, Value) - when Value =/= true, Value =/= false, Value =/= once -> - throw({error, {options, {active,Value}}}); -validate_inet_option(_, _) -> - ok. - -%% The option cacerts overrides cacertsfile -ca_cert_default(_,_, [_|_]) -> - undefined; -ca_cert_default(verify_none, _, _) -> - undefined; -ca_cert_default(verify_peer, {Fun,_}, _) when is_function(Fun) -> - undefined; -%% Server that wants to verify_peer and has no verify_fun must have -%% some trusted certs. -ca_cert_default(verify_peer, undefined, _) -> - "". - -emulated_options() -> - [mode, packet, active, header, packet_size]. - -internal_inet_values() -> - [{packet_size,0},{packet, 0},{header, 0},{active, false},{mode,binary}]. - -socket_options(InetValues) -> - #socket_options{ - mode = proplists:get_value(mode, InetValues, lists), - header = proplists:get_value(header, InetValues, 0), - active = proplists:get_value(active, InetValues, active), - packet = proplists:get_value(packet, InetValues, 0), - packet_size = proplists:get_value(packet_size, InetValues) - }. - -emulated_options(Opts) -> - emulated_options(Opts, internal_inet_values(), #socket_options{}). - -emulated_options([{mode,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(mode,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{mode=Opt}); -emulated_options([{header,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(header,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{header=Opt}); -emulated_options([{active,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(active,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{active=Opt}); -emulated_options([{packet,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(packet,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{packet=Opt}); -emulated_options([{packet_size,Opt}|Opts], Inet, Emulated) -> - validate_inet_option(packet_size,Opt), - emulated_options(Opts, Inet, Emulated#socket_options{packet_size=Opt}); -emulated_options([Opt|Opts], Inet, Emulated) -> - emulated_options(Opts, [Opt|Inet], Emulated); -emulated_options([], Inet,Emulated) -> - {Inet, Emulated}. - -cipher_suites(Version, []) -> - ssl_cipher:suites(Version); -cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility - Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0], - cipher_suites(Version, Ciphers); -cipher_suites(Version, [{_,_,_}| _] = Ciphers0) -> - Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0], - cipher_suites(Version, Ciphers); - -cipher_suites(Version, [Cipher0 | _] = Ciphers0) when is_binary(Cipher0) -> - Supported0 = ssl_cipher:suites(Version) - ++ ssl_cipher:anonymous_suites() - ++ ssl_cipher:psk_suites(Version) - ++ ssl_cipher:srp_suites(), - Supported = ssl_cipher:filter_suites(Supported0), - case [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported)] of - [] -> - Supported; - Ciphers -> - Ciphers - end; -cipher_suites(Version, [Head | _] = Ciphers0) when is_list(Head) -> - %% Format: ["RC4-SHA","RC4-MD5"] - Ciphers = [ssl_cipher:openssl_suite(C) || C <- Ciphers0], - cipher_suites(Version, Ciphers); -cipher_suites(Version, Ciphers0) -> - %% Format: "RC4-SHA:RC4-MD5" - Ciphers = [ssl_cipher:openssl_suite(C) || C <- string:tokens(Ciphers0, ":")], - cipher_suites(Version, Ciphers). - -unexpected_format(Error) -> - lists:flatten(io_lib:format("Unexpected error: ~p", [Error])). +-spec handshake(#sslsocket{} | port(), timeout()| [ssl_option() + | transport_option()]) -> + ok | {ok, #sslsocket{}} | {error, reason()}. -file_error_format({error, Error})-> - case file:format_error(Error) of - "unknown POSIX error" -> - "decoding error"; - Str -> - Str - end; -file_error_format(_) -> - "decoding error". +handshake(#sslsocket{} = Socket, Timeout) -> + ssl:ssl_accept(Socket, Timeout); -file_desc(cacertfile) -> - "Invalid CA certificate file "; -file_desc(certfile) -> - "Invalid certificate file "; -file_desc(keyfile) -> - "Invalid key file "; -file_desc(dhfile) -> - "Invalid DH params file ". +handshake(ListenSocket, SslOptions) when is_port(ListenSocket) -> + handshake(ListenSocket, SslOptions, infinity). -detect(_Pred, []) -> - undefined; -detect(Pred, [H|T]) -> - case Pred(H) of - true -> - H; - _ -> - detect(Pred, T) - end. -make_next_protocol_selector(undefined) -> - undefined; -make_next_protocol_selector({client, AllProtocols, DefaultProtocol}) -> - fun(AdvertisedProtocols) -> - case detect(fun(PreferredProtocol) -> - lists:member(PreferredProtocol, AdvertisedProtocols) - end, AllProtocols) of - undefined -> - DefaultProtocol; - PreferredProtocol -> - PreferredProtocol - end - end; +-spec handshake(port(), [ssl_option()| transport_option()], timeout()) -> + {ok, #sslsocket{}} | {error, reason()}. -make_next_protocol_selector({server, AllProtocols, DefaultProtocol}) -> - fun(AdvertisedProtocols) -> - case detect(fun(PreferredProtocol) -> - lists:member(PreferredProtocol, AllProtocols) - end, - AdvertisedProtocols) of - undefined -> - DefaultProtocol; - PreferredProtocol -> - PreferredProtocol - end - end. +handshake(Socket, SslOptions, Timeout) when is_port(Socket) -> + ssl:ssl_accept(Socket, SslOptions, Timeout). diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 246fecf34a..9880befa94 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -27,266 +28,129 @@ -module(tls_connection). --behaviour(gen_fsm). +-behaviour(gen_statem). +-include("tls_connection.hrl"). -include("tls_handshake.hrl"). -include("ssl_alert.hrl"). -include("tls_record.hrl"). --include("ssl_cipher.hrl"). +-include("ssl_cipher.hrl"). +-include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_srp.hrl"). -include_lib("public_key/include/public_key.hrl"). %% Internal application API --export([send/2, recv/3, connect/7, ssl_accept/6, handshake/2, - socket_control/3, close/1, shutdown/2, - new_user/2, get_opts/2, set_opts/2, info/1, session_info/1, - peer_certificate/1, renegotiation/1, negotiated_next_protocol/1, prf/5]). - -%% Called by ssl_connection_sup --export([start_link/7]). - -%% gen_fsm callbacks --export([init/1, hello/2, certify/2, cipher/2, - abbreviated/2, connection/2, handle_event/3, - handle_sync_event/4, handle_info/3, terminate/3, code_change/4]). - --record(state, { - role, % client | server - user_application, % {MonitorRef, pid()} - transport_cb, % atom() - callback module - data_tag, % atom() - ex tcp. - close_tag, % atom() - ex tcp_closed - error_tag, % atom() - ex tcp_error - host, % string() | ipadress() - port, % integer() - socket, % socket() - ssl_options, % #ssl_options{} - socket_options, % #socket_options{} - connection_states, % #connection_states{} from ssl_record.hrl - tls_packets = [], % Not yet handled decode ssl/tls packets. - tls_record_buffer, % binary() buffer of incomplete records - tls_handshake_buffer, % binary() buffer of incomplete handshakes - tls_handshake_history, % tls_handshake_history() - tls_cipher_texts, % list() received but not deciphered yet - cert_db, % - session, % #session{} from tls_handshake.hrl - session_cache, % - session_cache_cb, % - negotiated_version, % tls_version() - client_certificate_requested = false, - key_algorithm, % atom as defined by cipher_suite - hashsign_algorithm, % atom as defined by cipher_suite - public_key_info, % PKIX: {Algorithm, PublicKey, PublicKeyParams} - private_key, % PKIX: #'RSAPrivateKey'{} - diffie_hellman_params, % PKIX: #'DHParameter'{} relevant for server side - diffie_hellman_keys, % {PublicKey, PrivateKey} - psk_identity, % binary() - server psk identity hint - srp_params, % #srp_user{} - srp_keys, % {PublicKey, PrivateKey} - premaster_secret, % - file_ref_db, % ets() - cert_db_ref, % ref() - bytes_to_read, % integer(), # bytes to read in passive mode - user_data_buffer, % binary() - log_alert, % boolean() - renegotiation, % {boolean(), From | internal | peer} - start_or_recv_from, % "gen_fsm From" - timer, % start_or_recv_timer - send_queue, % queue() - terminated = false, % - allow_renegotiate = true, - expecting_next_protocol_negotiation = false :: boolean(), - next_protocol = undefined :: undefined | binary(), - client_ecc % {Curves, PointFmt} - }). - --define(DEFAULT_DIFFIE_HELLMAN_PARAMS, - #'DHParameter'{prime = ?DEFAULT_DIFFIE_HELLMAN_PRIME, - base = ?DEFAULT_DIFFIE_HELLMAN_GENERATOR}). --define(WAIT_TO_ALLOW_RENEGOTIATION, 12000). - --type state_name() :: hello | abbreviated | certify | cipher | connection. --type gen_fsm_state_return() :: {next_state, state_name(), #state{}} | - {next_state, state_name(), #state{}, timeout()} | - {stop, term(), #state{}}. - -%%==================================================================== -%% Internal application API -%%==================================================================== - -%%-------------------------------------------------------------------- --spec send(pid(), iodata()) -> ok | {error, reason()}. -%% -%% Description: Sends data over the ssl connection -%%-------------------------------------------------------------------- -send(Pid, Data) -> - sync_send_all_state_event(Pid, {application_data, - %% iolist_to_binary should really - %% be called iodata_to_binary() - erlang:iolist_to_binary(Data)}). - -%%-------------------------------------------------------------------- --spec recv(pid(), integer(), timeout()) -> - {ok, binary() | list()} | {error, reason()}. -%% -%% Description: Receives data when active = false -%%-------------------------------------------------------------------- -recv(Pid, Length, Timeout) -> - sync_send_all_state_event(Pid, {recv, Length, Timeout}). -%%-------------------------------------------------------------------- --spec connect(host(), inet:port_number(), port(), {#ssl_options{}, #socket_options{}}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Connect to an ssl server. -%%-------------------------------------------------------------------- -connect(Host, Port, Socket, Options, User, CbInfo, Timeout) -> - try start_fsm(client, Host, Port, Socket, Options, User, CbInfo, - Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. -%%-------------------------------------------------------------------- --spec ssl_accept(inet:port_number(), port(), {#ssl_options{}, #socket_options{}}, - pid(), tuple(), timeout()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Performs accept on an ssl listen socket. e.i. performs -%% ssl handshake. -%%-------------------------------------------------------------------- -ssl_accept(Port, Socket, Opts, User, CbInfo, Timeout) -> - try start_fsm(server, "localhost", Port, Socket, Opts, User, - CbInfo, Timeout) - catch - exit:{noproc, _} -> - {error, ssl_not_started} - end. - -%%-------------------------------------------------------------------- --spec handshake(#sslsocket{}, timeout()) -> ok | {error, reason()}. -%% -%% Description: Starts ssl handshake. -%%-------------------------------------------------------------------- -handshake(#sslsocket{pid = Pid}, Timeout) -> - case sync_send_all_state_event(Pid, {start, Timeout}) of - connected -> - ok; - Error -> - Error - end. -%-------------------------------------------------------------------- --spec socket_control(port(), pid(), atom()) -> - {ok, #sslsocket{}} | {error, reason()}. -%% -%% Description: Set the ssl process to own the accept socket -%%-------------------------------------------------------------------- -socket_control(Socket, Pid, Transport) -> - case Transport:controlling_process(Socket, Pid) of - ok -> - {ok, ssl_socket:socket(Pid, Transport, Socket)}; - {error, Reason} -> - {error, Reason} - end. -%%-------------------------------------------------------------------- --spec close(pid()) -> ok | {error, reason()}. -%% -%% Description: Close an ssl connection -%%-------------------------------------------------------------------- -close(ConnectionPid) -> - case sync_send_all_state_event(ConnectionPid, close) of - {error, closed} -> - ok; - Other -> - Other - end. +%% Setup +-export([start_fsm/8, start_link/7, init/1]). -%%-------------------------------------------------------------------- --spec shutdown(pid(), atom()) -> ok | {error, reason()}. -%% -%% Description: Same as gen_tcp:shutdown/2 -%%-------------------------------------------------------------------- -shutdown(ConnectionPid, How) -> - sync_send_all_state_event(ConnectionPid, {shutdown, How}). +%% State transition handling +-export([next_record/1, next_event/3]). -%%-------------------------------------------------------------------- --spec new_user(pid(), pid()) -> ok | {error, reason()}. -%% -%% Description: Changes process that receives the messages when active = true -%% or once. -%%-------------------------------------------------------------------- -new_user(ConnectionPid, User) -> - sync_send_all_state_event(ConnectionPid, {new_user, User}). +%% Handshake handling +-export([renegotiate/2, send_handshake/2, + queue_handshake/2, queue_change_cipher/2, + reinit_handshake_data/1, handle_sni_extension/2]). -%%-------------------------------------------------------------------- --spec negotiated_next_protocol(pid()) -> {ok, binary()} | {error, reason()}. -%% -%% Description: Returns the negotiated protocol -%%-------------------------------------------------------------------- -negotiated_next_protocol(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, negotiated_next_protocol). +%% Alert and close handling +-export([send_alert/2, handle_own_alert/4, handle_close_alert/3, + handle_normal_shutdown/3, + close/5, alert_user/6, alert_user/9 + ]). -%%-------------------------------------------------------------------- --spec get_opts(pid(), list()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Same as inet:getopts/2 -%%-------------------------------------------------------------------- -get_opts(ConnectionPid, OptTags) -> - sync_send_all_state_event(ConnectionPid, {get_opts, OptTags}). -%%-------------------------------------------------------------------- --spec set_opts(pid(), list()) -> ok | {error, reason()}. -%% -%% Description: Same as inet:setopts/2 -%%-------------------------------------------------------------------- -set_opts(ConnectionPid, Options) -> - sync_send_all_state_event(ConnectionPid, {set_opts, Options}). +%% Data handling +-export([write_application_data/3, read_application_data/2, + passive_receive/2, next_record_if_active/1, handle_common_event/4]). -%%-------------------------------------------------------------------- --spec info(pid()) -> {ok, {atom(), tuple()}} | {error, reason()}. -%% -%% Description: Returns ssl protocol and cipher used for the connection -%%-------------------------------------------------------------------- -info(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, info). +%% gen_statem state functions +-export([init/3, error/3, downgrade/3, %% Initiation and take down states + hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states + connection/3]). +%% gen_statem callbacks +-export([terminate/3, code_change/4, format_status/2]). + +-define(GEN_STATEM_CB_MODE, state_functions). -%%-------------------------------------------------------------------- --spec session_info(pid()) -> {ok, list()} | {error, reason()}. -%% -%% Description: Returns info about the ssl session -%%-------------------------------------------------------------------- -session_info(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, session_info). +%%==================================================================== +%% Internal application API +%%==================================================================== +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_, Tracker} = Opts, + User, {CbModule, _,_, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = tls_connection_sup:start_child([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), + ok = ssl_connection:handshake(SslSocket, Timeout), + {ok, SslSocket} + catch + error:{badmatch, {error, _} = Error} -> + Error + end; -%%-------------------------------------------------------------------- --spec peer_certificate(pid()) -> {ok, binary()| undefined} | {error, reason()}. -%% -%% Description: Returns the peer cert -%%-------------------------------------------------------------------- -peer_certificate(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, peer_certificate). +start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_, Tracker} = Opts, + User, {CbModule, _,_, _} = CbInfo, + Timeout) -> + try + {ok, Pid} = tls_connection_sup:start_child_dist([Role, Host, Port, Socket, + Opts, User, CbInfo]), + {ok, SslSocket} = ssl_connection:socket_control(?MODULE, Socket, Pid, CbModule, Tracker), + ok = ssl_connection:handshake(SslSocket, Timeout), + {ok, SslSocket} + catch + error:{badmatch, {error, _} = Error} -> + Error + end. -%%-------------------------------------------------------------------- --spec renegotiation(pid()) -> ok | {error, reason()}. -%% -%% Description: Starts a renegotiation of the ssl session. -%%-------------------------------------------------------------------- -renegotiation(ConnectionPid) -> - sync_send_all_state_event(ConnectionPid, renegotiate). +send_handshake(Handshake, State) -> + send_handshake_flight(queue_handshake(Handshake, State)). + +queue_handshake(Handshake, #state{negotiated_version = Version, + tls_handshake_history = Hist0, + flight_buffer = Flight0, + connection_states = ConnectionStates0} = State0) -> + {BinHandshake, ConnectionStates, Hist} = + encode_handshake(Handshake, Version, ConnectionStates0, Hist0), + State0#state{connection_states = ConnectionStates, + tls_handshake_history = Hist, + flight_buffer = Flight0 ++ [BinHandshake]}. + +send_handshake_flight(#state{socket = Socket, + transport_cb = Transport, + flight_buffer = Flight} = State0) -> + Transport:send(Socket, Flight), + State0#state{flight_buffer = []}. + +queue_change_cipher(Msg, #state{negotiated_version = Version, + flight_buffer = Flight0, + connection_states = ConnectionStates0} = State0) -> + {BinChangeCipher, ConnectionStates} = + encode_change_cipher(Msg, Version, ConnectionStates0), + State0#state{connection_states = ConnectionStates, + flight_buffer = Flight0 ++ [BinChangeCipher]}. -%%-------------------------------------------------------------------- --spec prf(pid(), binary() | 'master_secret', binary(), - binary() | ssl:prf_random(), non_neg_integer()) -> - {ok, binary()} | {error, reason()} | {'EXIT', term()}. -%% -%% Description: use a ssl sessions TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> - sync_send_all_state_event(ConnectionPid, {prf, Secret, Label, Seed, WantedLength}). +send_alert(Alert, #state{negotiated_version = Version, + socket = Socket, + transport_cb = Transport, + connection_states = ConnectionStates0} = State0) -> + {BinMsg, ConnectionStates} = + ssl_alert:encode(Alert, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + State0#state{connection_states = ConnectionStates}. + +reinit_handshake_data(State) -> + %% premaster_secret, public_key_info and tls_handshake_info + %% are only needed during the handshake phase. + %% To reduce memory foot print of a connection reinitialize them. + State#state{ + premaster_secret = undefined, + public_key_info = undefined, + tls_handshake_history = ssl_handshake:init_handshake_history() + }. %%==================================================================== -%% ssl_connection_sup API +%% tls_connection_sup API %%==================================================================== %%-------------------------------------------------------------------- @@ -300,789 +164,218 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) -> start_link(Role, Host, Port, Socket, Options, User, CbInfo) -> {ok, proc_lib:spawn_link(?MODULE, init, [[Role, Host, Port, Socket, Options, User, CbInfo]])}. -init([Role, Host, Port, Socket, {SSLOpts0, _} = Options, User, CbInfo]) -> +init([Role, Host, Port, Socket, Options, User, CbInfo]) -> + process_flag(trap_exit, true), State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), - Handshake = tls_handshake:init_handshake_history(), - TimeStamp = calendar:datetime_to_gregorian_seconds({date(), time()}), - try ssl_init(SSLOpts0, Role) of - {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, Key, DHParams} -> - Session = State0#state.session, - State = State0#state{ - tls_handshake_history = Handshake, - session = Session#session{own_certificate = OwnCert, - time_stamp = TimeStamp}, - file_ref_db = FileRefHandle, - cert_db_ref = Ref, - cert_db = CertDbHandle, - session_cache = CacheHandle, - private_key = Key, - diffie_hellman_params = DHParams}, - gen_fsm:enter_loop(?MODULE, [], hello, State, get_timeout(State)) - catch - throw:Error -> - gen_fsm:enter_loop(?MODULE, [], error, {Error,State0}, get_timeout(State0)) + try + State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), + gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State) + catch throw:Error -> + gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error, State0}) end. %%-------------------------------------------------------------------- -%% Description:There should be one instance of this function for each -%% possible state name. Whenever a gen_fsm receives an event sent -%% using gen_fsm:send_event/2, the instance of this function with the -%% same name as the current state name StateName is called to handle -%% the event. It is also called if a timeout occurs. -%% - +%% State functions %%-------------------------------------------------------------------- --spec hello(start | #hello_request{} | #client_hello{} | #server_hello{} | term(), - #state{}) -> gen_fsm_state_return(). %%-------------------------------------------------------------------- -hello(start, #state{host = Host, port = Port, role = client, - ssl_options = SslOpts, - session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - transport_cb = Transport, socket = Socket, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} = State0) -> +-spec init(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- + +init({call, From}, {start, Timeout}, + #state{host = Host, port = Port, role = client, + ssl_options = SslOpts, + session = #session{own_certificate = Cert} = Session0, + transport_cb = Transport, socket = Socket, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}, + session_cache = Cache, + session_cache_cb = CacheCb + } = State0) -> + Timer = ssl_connection:start_or_recv_cancel_timer(Timeout, From), Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), Version = Hello#client_hello.client_version, - Handshake0 = tls_handshake:init_handshake_history(), + HelloVersion = tls_record:lowest_protocol_version(SslOpts#ssl_options.versions), + Handshake0 = ssl_handshake:init_handshake_history(), {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, Version, ConnectionStates0, Handshake0), + encode_handshake(Hello, HelloVersion, ConnectionStates0, Handshake0), Transport:send(Socket, BinMsg), State1 = State0#state{connection_states = ConnectionStates, negotiated_version = Version, %% Requested version session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake}, + tls_handshake_history = Handshake, + start_or_recv_from = From, + timer = Timer}, {Record, State} = next_record(State1), - next_state(hello, hello, Record, State); - -hello(start, #state{role = server} = State0) -> - {Record, State} = next_record(State0), - next_state(hello, hello, Record, State); + next_event(hello, Record, State); +init(Type, Event, State) -> + ssl_connection:init(Type, Event, State, ?MODULE). + +%%-------------------------------------------------------------------- +-spec error(gen_statem:event_type(), + {start, timeout()} | term(), #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- -hello(#hello_request{}, #state{role = client} = State0) -> - {Record, State} = next_record(State0), - next_state(hello, hello, Record, State); +error({call, From}, {start, _Timeout}, {Error, State}) -> + {stop_and_reply, normal, {reply, From, {error, Error}}, State}; +error({call, From}, Msg, State) -> + handle_call(Msg, From, error, State); +error(_, _, _) -> + {keep_state_and_data, [postpone]}. + +%%-------------------------------------------------------------------- +-spec hello(gen_statem:event_type(), + #hello_request{} | #client_hello{} | #server_hello{} | term(), + #state{}) -> + gen_statem:state_function_result(). +%%-------------------------------------------------------------------- +hello(internal, #client_hello{client_version = ClientVersion, + extensions = #hello_extensions{ec_point_formats = EcPointFormats, + elliptic_curves = EllipticCurves}} = Hello, + #state{connection_states = ConnectionStates0, + port = Port, session = #session{own_certificate = Cert} = Session0, + renegotiation = {Renegotiation, _}, + session_cache = Cache, + session_cache_cb = CacheCb, + negotiated_protocol = CurrentProtocol, + key_algorithm = KeyExAlg, + ssl_options = SslOpts} = State) -> -hello(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression} = Hello, - #state{session = #session{session_id = OldId}, - connection_states = ConnectionStates0, - role = client, + case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, + ConnectionStates0, Cert, KeyExAlg}, Renegotiation) of + #alert{} = Alert -> + handle_own_alert(Alert, ClientVersion, hello, State); + {Version, {Type, Session}, + ConnectionStates, Protocol0, ServerHelloExt, HashSign} -> + Protocol = case Protocol0 of + undefined -> CurrentProtocol; + _ -> Protocol0 + end, + + ssl_connection:hello(internal, {common_client_hello, Type, ServerHelloExt}, + State#state{connection_states = ConnectionStates, + negotiated_version = Version, + hashsign_algorithm = HashSign, + session = Session, + client_ecc = {EllipticCurves, EcPointFormats}, + negotiated_protocol = Protocol}, ?MODULE) + end; +hello(internal, #server_hello{} = Hello, + #state{connection_states = ConnectionStates0, negotiated_version = ReqVersion, + role = client, renegotiation = {Renegotiation, _}, - ssl_options = SslOptions} = State0) -> + ssl_options = SslOptions} = State) -> case tls_handshake:hello(Hello, SslOptions, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - handle_own_alert(Alert, ReqVersion, hello, State0); - {Version, NewId, ConnectionStates, NextProtocol} -> - {KeyAlgorithm, _, _, _} = - ssl_cipher:suite_definition(CipherSuite), - - PremasterSecret = make_premaster_secret(ReqVersion, KeyAlgorithm), - - NewNextProtocol = case NextProtocol of - undefined -> - State0#state.next_protocol; - _ -> - NextProtocol - end, - - State = State0#state{key_algorithm = KeyAlgorithm, - hashsign_algorithm = default_hashsign(Version, KeyAlgorithm), - negotiated_version = Version, - connection_states = ConnectionStates, - premaster_secret = PremasterSecret, - expecting_next_protocol_negotiation = NextProtocol =/= undefined, - next_protocol = NewNextProtocol}, - - case ssl_session:is_new(OldId, NewId) of - true -> - handle_new_session(NewId, CipherSuite, Compression, - State#state{connection_states = ConnectionStates}); - false -> - handle_resumed_session(NewId, State#state{connection_states = ConnectionStates}) - end + handle_own_alert(Alert, ReqVersion, hello, State); + {Version, NewId, ConnectionStates, ProtoExt, Protocol} -> + ssl_connection:handle_session(Hello, + Version, NewId, ConnectionStates, ProtoExt, Protocol, State) end; +hello(info, Event, State) -> + handle_info(Event, hello, State); +hello(Type, Event, State) -> + ssl_connection:hello(Type, Event, State, ?MODULE). -hello(Hello = #client_hello{client_version = ClientVersion}, - State = #state{connection_states = ConnectionStates0, - port = Port, session = #session{own_certificate = Cert} = Session0, - renegotiation = {Renegotiation, _}, - session_cache = Cache, - session_cache_cb = CacheCb, - ssl_options = SslOpts}) -> - case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb, - ConnectionStates0, Cert}, Renegotiation) of - {Version, {Type, Session}, ConnectionStates, ProtocolsToAdvertise, - EcPointFormats, EllipticCurves} -> - do_server_hello(Type, ProtocolsToAdvertise, - EcPointFormats, EllipticCurves, - State#state{connection_states = ConnectionStates, - negotiated_version = Version, - session = Session, - client_ecc = {EllipticCurves, EcPointFormats}}); - #alert{} = Alert -> - handle_own_alert(Alert, ClientVersion, hello, State) - end; - -hello(timeout, State) -> - { next_state, hello, State, hibernate }; - -hello(Msg, State) -> - handle_unexpected_message(Msg, hello, State). %%-------------------------------------------------------------------- --spec abbreviated(#hello_request{} | #finished{} | term(), - #state{}) -> gen_fsm_state_return(). +-spec abbreviated(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -abbreviated(#hello_request{}, State0) -> - {Record, State} = next_record(State0), - next_state(abbreviated, hello, Record, State); - -abbreviated(#finished{verify_data = Data} = Finished, - #state{role = server, - negotiated_version = Version, - tls_handshake_history = Handshake, - session = #session{master_secret = MasterSecret}, - connection_states = ConnectionStates0} = - State) -> - case tls_handshake:verify_connection(Version, Finished, client, - get_current_connection_state_prf(ConnectionStates0, write), - MasterSecret, Handshake) of - verified -> - ConnectionStates = tls_record:set_client_verify_data(current_both, Data, ConnectionStates0), - next_state_connection(abbreviated, - ack_connection(State#state{connection_states = ConnectionStates})); - #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State) - end; - -abbreviated(#finished{verify_data = Data} = Finished, - #state{role = client, tls_handshake_history = Handshake0, - session = #session{master_secret = MasterSecret}, - negotiated_version = Version, - connection_states = ConnectionStates0} = State) -> - case tls_handshake:verify_connection(Version, Finished, server, - get_pending_connection_state_prf(ConnectionStates0, write), - MasterSecret, Handshake0) of - verified -> - ConnectionStates1 = tls_record:set_server_verify_data(current_read, Data, ConnectionStates0), - {ConnectionStates, Handshake} = - finalize_handshake(State#state{connection_states = ConnectionStates1}, abbreviated), - next_state_connection(abbreviated, - ack_connection(State#state{tls_handshake_history = Handshake, - connection_states = - ConnectionStates})); - #alert{} = Alert -> - handle_own_alert(Alert, Version, abbreviated, State) - end; - -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -abbreviated(#next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> - {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), - next_state(abbreviated, abbreviated, Record, State); - -abbreviated(timeout, State) -> - { next_state, abbreviated, State, hibernate }; - -abbreviated(Msg, State) -> - handle_unexpected_message(Msg, abbreviated, State). +abbreviated(info, Event, State) -> + handle_info(Event, abbreviated, State); +abbreviated(Type, Event, State) -> + ssl_connection:abbreviated(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- --spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} | - #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(), - #state{}) -> gen_fsm_state_return(). +-spec certify(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -certify(#hello_request{}, State0) -> - {Record, State} = next_record(State0), - next_state(certify, hello, Record, State); - -certify(#certificate{asn1_certificates = []}, - #state{role = server, negotiated_version = Version, - ssl_options = #ssl_options{verify = verify_peer, - fail_if_no_peer_cert = true}} = - State) -> - Alert = ?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE), - handle_own_alert(Alert, Version, certify, State); - -certify(#certificate{asn1_certificates = []}, - #state{role = server, - ssl_options = #ssl_options{verify = verify_peer, - fail_if_no_peer_cert = false}} = - State0) -> - {Record, State} = next_record(State0#state{client_certificate_requested = false}), - next_state(certify, certify, Record, State); - -certify(#certificate{} = Cert, - #state{negotiated_version = Version, - role = Role, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - ssl_options = Opts} = State) -> - case tls_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth, - Opts#ssl_options.verify, - Opts#ssl_options.verify_fun, Role) of - {PeerCert, PublicKeyInfo} -> - handle_peer_cert(PeerCert, PublicKeyInfo, - State#state{client_certificate_requested = false}); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) - end; - -certify(#server_key_exchange{} = KeyExchangeMsg, - #state{role = client, negotiated_version = Version, - key_algorithm = Alg} = State0) - when Alg == dhe_dss; Alg == dhe_rsa; - Alg == ecdhe_rsa; Alg == ecdhe_ecdsa; - Alg == dh_anon; Alg == ecdh_anon; - Alg == psk; Alg == dhe_psk; Alg == rsa_psk; - Alg == srp_dss; Alg == srp_rsa; Alg == srp_anon -> - case handle_server_key(KeyExchangeMsg, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, certify, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify(#server_key_exchange{} = Msg, - #state{role = client, key_algorithm = rsa} = State) -> - handle_unexpected_message(Msg, certify_server_keyexchange, State); - -certify(#certificate_request{}, State0) -> - {Record, State} = next_record(State0#state{client_certificate_requested = true}), - next_state(certify, certify, Record, State); - -%% PSK and RSA_PSK might bypass the Server-Key-Exchange -certify(#server_hello_done{}, - #state{session = #session{master_secret = undefined}, - negotiated_version = Version, - psk_identity = PSKIdentity, - premaster_secret = undefined, - role = client, - key_algorithm = Alg} = State0) - when Alg == psk -> - case server_psk_master_secret(PSKIdentity, State0) of - #state{} = State -> - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify(#server_hello_done{}, - #state{session = #session{master_secret = undefined}, - ssl_options = SslOpts, - negotiated_version = Version, - psk_identity = PSKIdentity, - premaster_secret = undefined, - role = client, - key_algorithm = Alg} = State0) - when Alg == rsa_psk -> - case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - PremasterSecret = make_premaster_secret(Version, rsa), - Len = byte_size(PSK), - RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>, - State1 = State0#state{premaster_secret = PremasterSecret}, - State = master_from_premaster_secret(RealPMS, State1), - client_certify_and_key_exchange(State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end; - -%% Master secret was determined with help of server-key exchange msg -certify(#server_hello_done{}, - #state{session = #session{master_secret = MasterSecret} = Session, - connection_states = ConnectionStates0, - negotiated_version = Version, - premaster_secret = undefined, - role = client} = State0) -> - case tls_handshake:master_secret(Version, Session, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - State = State0#state{connection_states = ConnectionStates}, - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -%% Master secret is calculated from premaster_secret -certify(#server_hello_done{}, - #state{session = Session0, - connection_states = ConnectionStates0, - negotiated_version = Version, - premaster_secret = PremasterSecret, - role = client} = State0) -> - case tls_handshake:master_secret(Version, PremasterSecret, - ConnectionStates0, client) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State = State0#state{connection_states = ConnectionStates, - session = Session}, - client_certify_and_key_exchange(State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify(#client_key_exchange{} = Msg, - #state{role = server, - client_certificate_requested = true, - ssl_options = #ssl_options{fail_if_no_peer_cert = true}} = State) -> - %% We expect a certificate here - handle_unexpected_message(Msg, certify_client_key_exchange, State); - -certify(#client_key_exchange{exchange_keys = Keys}, - State = #state{key_algorithm = KeyAlg, negotiated_version = Version}) -> - try - certify_client_key_exchange(tls_handshake:decode_client_key(Keys, KeyAlg, Version), State) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State) - end; - - -certify(timeout, State) -> - { next_state, certify, State, hibernate }; - -certify(Msg, State) -> - handle_unexpected_message(Msg, certify, State). - -certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS}, - #state{negotiated_version = Version, - connection_states = ConnectionStates0, - session = Session0, - private_key = Key} = State0) -> - PremasterSecret = tls_handshake:decrypt_premaster_secret(EncPMS, Key), - case tls_handshake:master_secret(Version, PremasterSecret, - ConnectionStates0, server) of - {MasterSecret, ConnectionStates} -> - Session = Session0#session{master_secret = MasterSecret}, - State1 = State0#state{connection_states = ConnectionStates, - session = Session}, - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_diffie_hellman_public{dh_public = ClientPublicDhKey}, - #state{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{} = Params, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> - case dh_master_secret(Params, ClientPublicDhKey, ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_ec_diffie_hellman_public{dh_public = ClientPublicEcDhPoint}, - #state{negotiated_version = Version, - diffie_hellman_keys = ECDHKey} = State0) -> - case ec_dh_master_secret(ECDHKey, #'ECPoint'{point = ClientPublicEcDhPoint}, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_psk_identity{identity = ClientPSKIdentity}, - #state{negotiated_version = Version} = State0) -> - case server_psk_master_secret(ClientPSKIdentity, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_dhe_psk_identity{ - identity = ClientPSKIdentity, - dh_public = ClientPublicDhKey}, - #state{negotiated_version = Version, - diffie_hellman_params = #'DHParameter'{prime = P, - base = G}, - diffie_hellman_keys = {_, ServerDhPrivateKey}} = State0) -> - case dhe_psk_master_secret(ClientPSKIdentity, P, G, ClientPublicDhKey, ServerDhPrivateKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_rsa_psk_identity{ - identity = PskIdentity, - exchange_keys = - #encrypted_premaster_secret{premaster_secret= EncPMS}}, - #state{negotiated_version = Version, - private_key = Key} = State0) -> - PremasterSecret = tls_handshake:decrypt_premaster_secret(EncPMS, Key), - case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end; - -certify_client_key_exchange(#client_srp_public{srp_a = ClientPublicKey}, - #state{negotiated_version = Version, - srp_params = - #srp_user{prime = Prime, - verifier = Verifier} - } = State0) -> - case server_srp_master_secret(Verifier, Prime, ClientPublicKey, State0) of - #state{} = State1 -> - {Record, State} = next_record(State1), - next_state(certify, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. +certify(info, Event, State) -> + handle_info(Event, certify, State); +certify(Type, Event, State) -> + ssl_connection:certify(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- --spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(), - #state{}) -> gen_fsm_state_return(). +-spec cipher(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -cipher(#hello_request{}, State0) -> - {Record, State} = next_record(State0), - next_state(cipher, hello, Record, State); - -cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashSign}, - #state{role = server, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - session = #session{master_secret = MasterSecret}, - hashsign_algorithm = ConnectionHashSign, - tls_handshake_history = Handshake - } = State0) -> - HashSign = case CertHashSign of - {_, _} -> CertHashSign; - _ -> ConnectionHashSign - end, - case tls_handshake:certificate_verify(Signature, PublicKeyInfo, - Version, HashSign, MasterSecret, Handshake) of - valid -> - {Record, State} = next_record(State0), - next_state(cipher, cipher, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State0) - end; - -%% client must send a next protocol message if we are expecting it -cipher(#finished{}, #state{role = server, expecting_next_protocol_negotiation = true, - next_protocol = undefined, negotiated_version = Version} = State0) -> - handle_own_alert(?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), Version, cipher, State0); - -cipher(#finished{verify_data = Data} = Finished, - #state{negotiated_version = Version, - host = Host, - port = Port, - role = Role, - session = #session{master_secret = MasterSecret} - = Session0, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> - case tls_handshake:verify_connection(Version, Finished, - opposite_role(Role), - get_current_connection_state_prf(ConnectionStates0, read), - MasterSecret, Handshake0) of - verified -> - Session = register_session(Role, Host, Port, Session0), - cipher_role(Role, Data, Session, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, cipher, State) - end; - -%% only allowed to send next_protocol message after change cipher spec -%% & before finished message and it is not allowed during renegotiation -cipher(#next_protocol{selected_protocol = SelectedProtocol}, - #state{role = server, expecting_next_protocol_negotiation = true} = State0) -> - {Record, State} = next_record(State0#state{next_protocol = SelectedProtocol}), - next_state(cipher, cipher, Record, State); - -cipher(timeout, State) -> - { next_state, cipher, State, hibernate }; - -cipher(Msg, State) -> - handle_unexpected_message(Msg, cipher, State). +cipher(info, Event, State) -> + handle_info(Event, cipher, State); +cipher(Type, Event, State) -> + ssl_connection:cipher(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- --spec connection(#hello_request{} | #client_hello{} | term(), - #state{}) -> gen_fsm_state_return(). +-spec connection(gen_statem:event_type(), + #hello_request{} | #client_hello{}| term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -connection(#hello_request{}, #state{host = Host, port = Port, - socket = Socket, - session = #session{own_certificate = Cert} = Session0, - session_cache = Cache, session_cache_cb = CacheCb, - ssl_options = SslOpts, - negotiated_version = Version, - transport_cb = Transport, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}, - tls_handshake_history = Handshake0} = State0) -> +connection(info, Event, State) -> + handle_info(Event, connection, State); +connection(internal, #hello_request{}, + #state{role = client, host = Host, port = Port, + session = #session{own_certificate = Cert} = Session0, + session_cache = Cache, session_cache_cb = CacheCb, + ssl_options = SslOpts, + connection_states = ConnectionStates0, + renegotiation = {Renegotiation, _}} = State0) -> Hello = tls_handshake:client_hello(Host, Port, ConnectionStates0, SslOpts, Cache, CacheCb, Renegotiation, Cert), - - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Hello, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - {Record, State} = next_record(State0#state{connection_states = - ConnectionStates, - session = Session0#session{session_id = Hello#client_hello.session_id}, - tls_handshake_history = Handshake}), - next_state(connection, hello, Record, State); -connection(#client_hello{} = Hello, #state{role = server, allow_renegotiate = true} = State) -> + State1 = send_handshake(Hello, State0), + {Record, State} = + next_record( + State1#state{session = Session0#session{session_id + = Hello#client_hello.session_id}}), + next_event(hello, Record, State); +connection(internal, #client_hello{} = Hello, + #state{role = server, allow_renegotiate = true} = State0) -> %% Mitigate Computational DoS attack %% http://www.educatedguesswork.org/2011/10/ssltls_and_computational_dos.html %% http://www.thc.org/thc-ssl-dos/ Rather than disabling client %% initiated renegotiation we will disallow many client initiated %% renegotiations immediately after each other. erlang:send_after(?WAIT_TO_ALLOW_RENEGOTIATION, self(), allow_renegotiate), - hello(Hello, State#state{allow_renegotiate = false}); - -connection(#client_hello{}, #state{role = server, allow_renegotiate = false, - connection_states = ConnectionStates0, - socket = Socket, transport_cb = Transport, - negotiated_version = Version} = State0) -> + {Record, State} = next_record(State0#state{allow_renegotiate = false, + renegotiation = {true, peer}}), + next_event(hello, Record, State, [{next_event, internal, Hello}]); +connection(internal, #client_hello{}, + #state{role = server, allow_renegotiate = false} = State0) -> Alert = ?ALERT_REC(?WARNING, ?NO_RENEGOTIATION), - {BinMsg, ConnectionStates} = - encode_alert(Alert, Version, ConnectionStates0), - Transport:send(Socket, BinMsg), - next_state_connection(connection, State0#state{connection_states = ConnectionStates}); - -connection(timeout, State) -> - {next_state, connection, State, hibernate}; + State1 = send_alert(Alert, State0), + {Record, State} = ssl_connection:prepare_connection(State1, ?MODULE), + next_event(connection, Record, State); +connection(Type, Event, State) -> + ssl_connection:connection(Type, Event, State, ?MODULE). -connection(Msg, State) -> - handle_unexpected_message(Msg, connection, State). - -%%-------------------------------------------------------------------- -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:send_all_state_event/2, this function is called to handle -%% the event. Not currently used! %%-------------------------------------------------------------------- -handle_event(_Event, StateName, State) -> - {next_state, StateName, State, get_timeout(State)}. - +-spec downgrade(gen_statem:event_type(), term(), #state{}) -> + gen_statem:state_function_result(). %%-------------------------------------------------------------------- -%% Description: Whenever a gen_fsm receives an event sent using -%% gen_fsm:sync_send_all_state_event/2,3, this function is called to handle -%% the event. -%%-------------------------------------------------------------------- -handle_sync_event({application_data, Data}, From, connection, State) -> - %% We should look into having a worker process to do this to - %% parallize send and receive decoding and not block the receiver - %% if sending is overloading the socket. - try - write_application_data(Data, From, State) - catch throw:Error -> - {reply, Error, connection, State, get_timeout(State)} - end; -handle_sync_event({application_data, Data}, From, StateName, - #state{send_queue = Queue} = State) -> - %% In renegotiation priorities handshake, send data when handshake is finished - {next_state, StateName, - State#state{send_queue = queue:in({From, Data}, Queue)}, - get_timeout(State)}; - -handle_sync_event({start, Timeout}, StartFrom, hello, State) -> - Timer = start_or_recv_cancel_timer(Timeout, StartFrom), - hello(start, State#state{start_or_recv_from = StartFrom, - timer = Timer}); - -%% The two clauses below could happen if a server upgrades a socket in -%% active mode. Note that in this case we are lucky that -%% controlling_process has been evalueated before receiving handshake -%% messages from client. The server should put the socket in passive -%% mode before telling the client that it is willing to upgrade -%% and before calling ssl:ssl_accept/2. These clauses are -%% here to make sure it is the users problem and not owers if -%% they upgrade an active socket. -handle_sync_event({start,_}, _, connection, State) -> - {reply, connected, connection, State, get_timeout(State)}; -handle_sync_event({start,_}, _From, error, {Error, State = #state{}}) -> - {stop, {shutdown, Error}, {error, Error}, State}; - -handle_sync_event({start, Timeout}, StartFrom, StateName, State) -> - Timer = start_or_recv_cancel_timer(Timeout, StartFrom), - {next_state, StateName, State#state{start_or_recv_from = StartFrom, - timer = Timer}, get_timeout(State)}; - -handle_sync_event(close, _, StateName, State) -> - %% Run terminate before returning - %% so that the reuseaddr inet-option will work - %% as intended. - (catch terminate(user_close, StateName, State)), - {stop, normal, ok, State#state{terminated = true}}; - -handle_sync_event({shutdown, How0}, _, StateName, - #state{transport_cb = Transport, - negotiated_version = Version, - connection_states = ConnectionStates, - socket = Socket} = State) -> - case How0 of - How when How == write; How == both -> - Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - {BinMsg, _} = - encode_alert(Alert, Version, ConnectionStates), - Transport:send(Socket, BinMsg); - _ -> - ok - end, - - case Transport:shutdown(Socket, How0) of - ok -> - {reply, ok, StateName, State, get_timeout(State)}; - Error -> - {stop, normal, Error, State} - end; - -handle_sync_event({recv, N, Timeout}, RecvFrom, connection = StateName, State0) -> - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - passive_receive(State0#state{bytes_to_read = N, - start_or_recv_from = RecvFrom, timer = Timer}, StateName); - -%% Doing renegotiate wait with handling request until renegotiate is -%% finished. Will be handled by next_state_is_connection/2. -handle_sync_event({recv, N, Timeout}, RecvFrom, StateName, State) -> - Timer = start_or_recv_cancel_timer(Timeout, RecvFrom), - {next_state, StateName, State#state{bytes_to_read = N, start_or_recv_from = RecvFrom, - timer = Timer}, - get_timeout(State)}; - -handle_sync_event({new_user, User}, _From, StateName, - State =#state{user_application = {OldMon, _}}) -> - NewMon = erlang:monitor(process, User), - erlang:demonitor(OldMon, [flush]), - {reply, ok, StateName, State#state{user_application = {NewMon,User}}, - get_timeout(State)}; - -handle_sync_event({get_opts, OptTags}, _From, StateName, - #state{socket = Socket, - transport_cb = Transport, - socket_options = SockOpts} = State) -> - OptsReply = get_socket_opts(Transport, Socket, OptTags, SockOpts, []), - {reply, OptsReply, StateName, State, get_timeout(State)}; - -handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = undefined} = State) -> - {reply, {error, next_protocol_not_negotiated}, StateName, State, get_timeout(State)}; -handle_sync_event(negotiated_next_protocol, _From, StateName, #state{next_protocol = NextProtocol} = State) -> - {reply, {ok, NextProtocol}, StateName, State, get_timeout(State)}; - -handle_sync_event({set_opts, Opts0}, _From, StateName, - #state{socket_options = Opts1, - socket = Socket, - transport_cb = Transport, - user_data_buffer = Buffer} = State0) -> - {Reply, Opts} = set_socket_opts(Transport, Socket, Opts0, Opts1, []), - State1 = State0#state{socket_options = Opts}, - if - Opts#socket_options.active =:= false -> - {reply, Reply, StateName, State1, get_timeout(State1)}; - Buffer =:= <<>>, Opts1#socket_options.active =:= false -> - %% Need data, set active once - {Record, State2} = next_record_if_active(State1), - case next_state(StateName, StateName, Record, State2) of - {next_state, StateName, State, Timeout} -> - {reply, Reply, StateName, State, Timeout}; - {stop, Reason, State} -> - {stop, Reason, State} - end; - Buffer =:= <<>> -> - %% Active once already set - {reply, Reply, StateName, State1, get_timeout(State1)}; - true -> - case read_application_data(<<>>, State1) of - Stop = {stop,_,_} -> - Stop; - {Record, State2} -> - case next_state(StateName, StateName, Record, State2) of - {next_state, StateName, State, Timeout} -> - {reply, Reply, StateName, State, Timeout}; - {stop, Reason, State} -> - {stop, Reason, State} - end - end - end; - -handle_sync_event(renegotiate, From, connection, State) -> - renegotiate(State#state{renegotiation = {true, From}}); - -handle_sync_event(renegotiate, _, StateName, State) -> - {reply, {error, already_renegotiating}, StateName, State, get_timeout(State)}; - -handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName, - #state{connection_states = ConnectionStates, - negotiated_version = Version} = State) -> - ConnectionState = - tls_record:current_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{master_secret = MasterSecret, - client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Reply = try - SecretToUse = case Secret of - _ when is_binary(Secret) -> Secret; - master_secret -> MasterSecret - end, - SeedToUse = lists:reverse( - lists:foldl(fun(X, Acc) when is_binary(X) -> [X|Acc]; - (client_random, Acc) -> [ClientRandom|Acc]; - (server_random, Acc) -> [ServerRandom|Acc] - end, [], Seed)), - tls_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength) - catch - exit:_ -> {error, badarg}; - error:Reason -> {error, Reason} - end, - {reply, Reply, StateName, State, get_timeout(State)}; - -handle_sync_event(info, _, StateName, - #state{negotiated_version = Version, - session = #session{cipher_suite = Suite}} = State) -> - - AtomVersion = tls_record:protocol_version(Version), - {reply, {ok, {AtomVersion, ssl:suite_definition(Suite)}}, - StateName, State, get_timeout(State)}; - -handle_sync_event(session_info, _, StateName, - #state{session = #session{session_id = Id, - cipher_suite = Suite}} = State) -> - {reply, [{session_id, Id}, - {cipher_suite, ssl:suite_definition(Suite)}], - StateName, State, get_timeout(State)}; - -handle_sync_event(peer_certificate, _, StateName, - #state{session = #session{peer_certificate = Cert}} - = State) -> - {reply, {ok, Cert}, StateName, State, get_timeout(State)}. +downgrade(Type, Event, State) -> + ssl_connection:downgrade(Type, Event, State, ?MODULE). %%-------------------------------------------------------------------- -%% Description: This function is called by a gen_fsm when it receives any -%% other message than a synchronous or asynchronous event -%% (or a system message). +%% Event handling functions called by state functions to handle +%% common or unexpected events for the state. %%-------------------------------------------------------------------- - -%% raw data from TCP, unpack records +handle_call(Event, From, StateName, State) -> + ssl_connection:handle_call(Event, From, StateName, State, ?MODULE). + +%% raw data from socket, unpack records handle_info({Protocol, _, Data}, StateName, #state{data_tag = Protocol} = State0) -> case next_tls_record(Data, State0) of {Record, State} -> - next_state(StateName, StateName, Record, State); + next_event(StateName, Record, State); #alert{} = Alert -> handle_normal_shutdown(Alert, StateName, State0), - {stop, {shutdown, own_alert}, State0} + {stop, {shutdown, own_alert}} end; - handle_info({CloseTag, Socket}, StateName, #state{socket = Socket, close_tag = CloseTag, negotiated_version = Version} = State) -> @@ -1101,1194 +394,215 @@ handle_info({CloseTag, Socket}, StateName, ok end, handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, {shutdown, transport_closed}, State}; - -handle_info({ErrorTag, Socket, econnaborted}, StateName, - #state{socket = Socket, transport_cb = Transport, - start_or_recv_from = StartFrom, role = Role, - error_tag = ErrorTag} = State) when StateName =/= connection -> - alert_user(Transport, Socket, StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role), - {stop, normal, State}; - -handle_info({ErrorTag, Socket, Reason}, StateName, #state{socket = Socket, - error_tag = ErrorTag} = State) -> - Report = io_lib:format("SSL: Socket error: ~p ~n", [Reason]), - error_logger:info_report(Report), - handle_normal_shutdown(?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), StateName, State), - {stop, normal, State}; - -handle_info({'DOWN', MonitorRef, _, _, _}, _, - State = #state{user_application={MonitorRef,_Pid}}) -> - {stop, normal, State}; - -handle_info(allow_renegotiate, StateName, State) -> - {next_state, StateName, State#state{allow_renegotiate = true}, get_timeout(State)}; - -handle_info({cancel_start_or_recv, StartFrom}, StateName, - #state{renegotiation = {false, first}} = State) when StateName =/= connection -> - gen_fsm:reply(StartFrom, {error, timeout}), - {stop, {shutdown, user_timeout}, State#state{timer = undefined}}; - -handle_info({cancel_start_or_recv, RecvFrom}, StateName, #state{start_or_recv_from = RecvFrom} = State) -> - gen_fsm:reply(RecvFrom, {error, timeout}), - {next_state, StateName, State#state{start_or_recv_from = undefined, - bytes_to_read = undefined, - timer = undefined}, get_timeout(State)}; + {stop, {shutdown, transport_closed}}; +handle_info(Msg, StateName, State) -> + ssl_connection:handle_info(Msg, StateName, State). -handle_info({cancel_start_or_recv, _RecvFrom}, StateName, State) -> - {next_state, StateName, State#state{timer = undefined}, get_timeout(State)}; +handle_common_event(internal, #alert{} = Alert, StateName, + #state{negotiated_version = Version} = State) -> + handle_own_alert(Alert, Version, StateName, State); -handle_info(Msg, StateName, State) -> - Report = io_lib:format("SSL: Got unexpected info: ~p ~n", [Msg]), - error_logger:info_report(Report), - {next_state, StateName, State, get_timeout(State)}. +%%% TLS record protocol level handshake messages +handle_common_event(internal, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, + StateName, #state{protocol_buffers = + #protocol_buffers{tls_handshake_buffer = Buf0} = Buffers, + negotiated_version = Version, + ssl_options = Options} = State0) -> + try + {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0, Options), + State = + State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_handshake_buffer = Buf}}, + Events = tls_handshake_events(Packets), + case StateName of + connection -> + ssl_connection:hibernate_after(StateName, State, Events); + _ -> + {next_state, StateName, State, Events} + end + catch throw:#alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State0) + end; +%%% TLS record protocol level application data messages +handle_common_event(internal, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, {application_data, Data}}]}; +%%% TLS record protocol level change cipher messages +handle_common_event(internal, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = Data}, StateName, State) -> + {next_state, StateName, State, [{next_event, internal, #change_cipher_spec{type = Data}}]}; +%%% TLS record protocol level Alert messages +handle_common_event(internal, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, StateName, + #state{negotiated_version = Version} = State) -> + case decode_alerts(EncAlerts) of + Alerts = [_|_] -> + handle_alerts(Alerts, {next_state, StateName, State}); + #alert{} = Alert -> + handle_own_alert(Alert, Version, StateName, State) + end; +%% Ignore unknown TLS record level protocol messages +handle_common_event(internal, #ssl_tls{type = _Unknown}, StateName, State) -> + {next_state, StateName, State}. %%-------------------------------------------------------------------- -%% Description:This function is called by a gen_fsm when it is about -%% to terminate. It should be the opposite of Module:init/1 and do any -%% necessary cleaning up. When it returns, the gen_fsm terminates with -%% Reason. The return value is ignored. +%% gen_statem callbacks %%-------------------------------------------------------------------- -terminate(_, _, #state{terminated = true}) -> - %% Happens when user closes the connection using ssl:close/1 - %% we want to guarantee that Transport:close has been called - %% when ssl:close/1 returns. - ok; +terminate(Reason, StateName, State) -> + catch ssl_connection:terminate(Reason, StateName, State). -terminate({shutdown, transport_closed}, StateName, #state{send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_unrecv_data(StateName, State), - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate); - -terminate({shutdown, own_alert}, _StateName, #state{send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate); - -terminate(Reason, connection, #state{negotiated_version = Version, - connection_states = ConnectionStates, - transport_cb = Transport, - socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), - BinAlert = terminate_alert(Reason, Version, ConnectionStates), - Transport:send(Socket, BinAlert), - workaround_transport_delivery_problems(Socket, Transport); - -terminate(_Reason, _StateName, #state{transport_cb = Transport, - socket = Socket, send_queue = SendQueue, - renegotiation = Renegotiate} = State) -> - handle_trusted_certs_db(State), - notify_senders(SendQueue), - notify_renegotiater(Renegotiate), - Transport:close(Socket). +format_status(Type, Data) -> + ssl_connection:format_status(Type, Data). %%-------------------------------------------------------------------- %% code_change(OldVsn, StateName, State, Extra) -> {ok, StateName, NewState} %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- -code_change(_OldVsn, StateName, State, _Extra) -> - {ok, StateName, State}. +code_change(_OldVsn, StateName, State0, {Direction, From, To}) -> + State = convert_state(State0, Direction, From, To), + {?GEN_STATEM_CB_MODE, StateName, State}; +code_change(_OldVsn, StateName, State, _) -> + {?GEN_STATEM_CB_MODE, StateName, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = false},_} = Opts, - User, {CbModule, _,_, _} = CbInfo, - Timeout) -> - try - {ok, Pid} = ssl_connection_sup:start_child([Role, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = socket_control(Socket, Pid, CbModule), - ok = handshake(SslSocket, Timeout), - {ok, SslSocket} - catch - error:{badmatch, {error, _} = Error} -> - Error - end; - -start_fsm(Role, Host, Port, Socket, {#ssl_options{erl_dist = true},_} = Opts, - User, {CbModule, _,_, _} = CbInfo, - Timeout) -> - try - {ok, Pid} = ssl_connection_sup:start_child_dist([Role, Host, Port, Socket, - Opts, User, CbInfo]), - {ok, SslSocket} = socket_control(Socket, Pid, CbModule), - ok = handshake(SslSocket, Timeout), - {ok, SslSocket} - catch - error:{badmatch, {error, _} = Error} -> - Error - end. - -ssl_init(SslOpts, Role) -> - - init_manager_name(SslOpts#ssl_options.erl_dist), - - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} = init_certificates(SslOpts, Role), - PrivateKey = - init_private_key(PemCacheHandle, SslOpts#ssl_options.key, SslOpts#ssl_options.keyfile, - SslOpts#ssl_options.password, Role), - DHParams = init_diffie_hellman(PemCacheHandle, SslOpts#ssl_options.dh, SslOpts#ssl_options.dhfile, Role), - {ok, CertDbRef, CertDbHandle, FileRefHandle, CacheHandle, OwnCert, PrivateKey, DHParams}. - -init_manager_name(false) -> - put(ssl_manager, ssl_manager); -init_manager_name(true) -> - put(ssl_manager, ssl_manager_dist). - -init_certificates(#ssl_options{cacerts = CaCerts, - cacertfile = CACertFile, - certfile = CertFile, - cert = Cert}, Role) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle} = - try - Certs = case CaCerts of - undefined -> - CACertFile; - _ -> - {der, CaCerts} - end, - {ok, _, _, _, _, _} = ssl_manager:connection_init(Certs, Role) - catch - _:Reason -> - file_error(CACertFile, {cacertfile, Reason}) - end, - init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, Role). - -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, <<>>, _) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined}; - -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, CertFile, client) -> - try - %% Ignoring potential proxy-certificates see: - %% http://dev.globus.org/wiki/Security/ProxyFileFormat - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, OwnCert} - catch _Error:_Reason -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheHandle, undefined} - end; - -init_certificates(undefined, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, CertFile, server) -> - try - [OwnCert|_] = ssl_certificate:file_to_certificats(CertFile, PemCacheHandle), - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, OwnCert} - catch - _:Reason -> - file_error(CertFile, {certfile, Reason}) - end; -init_certificates(Cert, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, _, _) -> - {ok, CertDbRef, CertDbHandle, FileRefHandle, PemCacheHandle, CacheRef, Cert}. - -init_private_key(_, undefined, <<>>, _Password, _Client) -> - undefined; -init_private_key(DbHandle, undefined, KeyFile, Password, _) -> - try - {ok, List} = ssl_manager:cache_pem_file(KeyFile, DbHandle), - [PemEntry] = [PemEntry || PemEntry = {PKey, _ , _} <- List, - PKey =:= 'RSAPrivateKey' orelse - PKey =:= 'DSAPrivateKey' orelse - PKey =:= 'ECPrivateKey' orelse - PKey =:= 'PrivateKeyInfo' - ], - private_key(public_key:pem_entry_decode(PemEntry, Password)) - catch - _:Reason -> - file_error(KeyFile, {keyfile, Reason}) - end; +encode_handshake(Handshake, Version, ConnectionStates0, Hist0) -> + Frag = tls_handshake:encode_handshake(Handshake, Version), + Hist = ssl_handshake:update_handshake_history(Hist0, Frag), + {Encoded, ConnectionStates} = + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), + {Encoded, ConnectionStates, Hist}. -%% First two clauses are for backwards compatibility -init_private_key(_,{rsa, PrivateKey}, _, _,_) -> - init_private_key('RSAPrivateKey', PrivateKey); -init_private_key(_,{dsa, PrivateKey},_,_,_) -> - init_private_key('DSAPrivateKey', PrivateKey); -init_private_key(_,{ec, PrivateKey},_,_,_) -> - init_private_key('ECPrivateKey', PrivateKey); -init_private_key(_,{Asn1Type, PrivateKey},_,_,_) -> - private_key(init_private_key(Asn1Type, PrivateKey)). - -init_private_key(Asn1Type, PrivateKey) -> - public_key:der_decode(Asn1Type, PrivateKey). - -private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = - #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'rsaEncryption'}, - privateKey = Key}) -> - public_key:der_decode('RSAPrivateKey', iolist_to_binary(Key)); - -private_key(#'PrivateKeyInfo'{privateKeyAlgorithm = - #'PrivateKeyInfo_privateKeyAlgorithm'{algorithm = ?'id-dsa'}, - privateKey = Key}) -> - public_key:der_decode('DSAPrivateKey', iolist_to_binary(Key)); - -private_key(Key) -> - Key. - --spec(file_error(_,_) -> no_return()). -file_error(File, Throw) -> - case Throw of - {Opt,{badmatch, {error, {badmatch, Error}}}} -> - throw({options, {Opt, binary_to_list(File), Error}}); - _ -> - throw(Throw) - end. - -init_diffie_hellman(_,Params, _,_) when is_binary(Params)-> - public_key:der_decode('DHParameter', Params); -init_diffie_hellman(_,_,_, client) -> - undefined; -init_diffie_hellman(_,_,undefined, _) -> - ?DEFAULT_DIFFIE_HELLMAN_PARAMS; -init_diffie_hellman(DbHandle,_, DHParamFile, server) -> - try - {ok, List} = ssl_manager:cache_pem_file(DHParamFile,DbHandle), - case [Entry || Entry = {'DHParameter', _ , _} <- List] of - [Entry] -> - public_key:pem_entry_decode(Entry); - [] -> - ?DEFAULT_DIFFIE_HELLMAN_PARAMS - end - catch - _:Reason -> - file_error(DHParamFile, {dhfile, Reason}) - end. - -sync_send_all_state_event(FsmPid, Event) -> - try gen_fsm:sync_send_all_state_event(FsmPid, Event, infinity) - catch - exit:{noproc, _} -> - {error, closed}; - exit:{normal, _} -> - {error, closed}; - exit:{{shutdown, _},_} -> - {error, closed} - end. - -%% We do currently not support cipher suites that use fixed DH. -%% If we want to implement that we should add a code -%% here to extract DH parameters form cert. -handle_peer_cert(PeerCert, PublicKeyInfo, - #state{session = Session} = State0) -> - State1 = State0#state{session = - Session#session{peer_certificate = PeerCert}, - public_key_info = PublicKeyInfo}, - State2 = case PublicKeyInfo of - {?'id-ecPublicKey', #'ECPoint'{point = _ECPoint} = PublicKey, PublicKeyParams} -> - ECDHKey = public_key:generate_key(PublicKeyParams), - State3 = State1#state{diffie_hellman_keys = ECDHKey}, - ec_dh_master_secret(ECDHKey, PublicKey, State3); - - _ -> State1 - end, - {Record, State} = next_record(State2), - next_state(certify, certify, Record, State). - -certify_client(#state{client_certificate_requested = true, role = client, - connection_states = ConnectionStates0, - transport_cb = Transport, - negotiated_version = Version, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - session = #session{own_certificate = OwnCert}, - socket = Socket, - tls_handshake_history = Handshake0} = State) -> - Certificate = tls_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client), - {BinCert, ConnectionStates, Handshake} = - encode_handshake(Certificate, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinCert), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; -certify_client(#state{client_certificate_requested = false} = State) -> - State. - -verify_client_cert(#state{client_certificate_requested = true, role = client, - connection_states = ConnectionStates0, - transport_cb = Transport, - negotiated_version = Version, - socket = Socket, - private_key = PrivateKey, - session = #session{master_secret = MasterSecret, - own_certificate = OwnCert}, - hashsign_algorithm = HashSign, - tls_handshake_history = Handshake0} = State) -> - - %%TODO: for TLS 1.2 we can choose a different/stronger HashSign combination for this. - case tls_handshake:client_certificate_verify(OwnCert, MasterSecret, - Version, HashSign, PrivateKey, Handshake0) of - #certificate_verify{} = Verified -> - {BinVerified, ConnectionStates, Handshake} = - encode_handshake(Verified, Version, - ConnectionStates0, Handshake0), - Transport:send(Socket, BinVerified), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - ignore -> - State; - #alert{} = Alert -> - throw(Alert) - end; -verify_client_cert(#state{client_certificate_requested = false} = State) -> - State. - -do_server_hello(Type, NextProtocolsToSend, - EcPointFormats, EllipticCurves, - #state{negotiated_version = Version, - session = #session{session_id = SessId}, - connection_states = ConnectionStates0, - renegotiation = {Renegotiation, _}} - = State0) when is_atom(Type) -> - - ServerHello = - tls_handshake:server_hello(SessId, Version, - ConnectionStates0, Renegotiation, - NextProtocolsToSend, EcPointFormats, EllipticCurves), - State = server_hello(ServerHello, - State0#state{expecting_next_protocol_negotiation = - NextProtocolsToSend =/= undefined}), - case Type of - new -> - new_server_hello(ServerHello, State); - resumed -> - resumed_server_hello(State) - end. - -new_server_hello(#server_hello{cipher_suite = CipherSuite, - compression_method = Compression, - session_id = SessionId}, - #state{session = Session0, - negotiated_version = Version} = State0) -> - try server_certify_and_key_exchange(State0) of - #state{} = State1 -> - State2 = server_hello_done(State1), - Session = - Session0#session{session_id = SessionId, - cipher_suite = CipherSuite, - compression_method = Compression}, - {Record, State} = next_record(State2#state{session = Session}), - next_state(hello, certify, Record, State) - catch - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - -resumed_server_hello(#state{session = Session, - connection_states = ConnectionStates0, - negotiated_version = Version} = State0) -> - - case tls_handshake:master_secret(Version, Session, - ConnectionStates0, server) of - {_, ConnectionStates1} -> - State1 = State0#state{connection_states = ConnectionStates1, - session = Session}, - {ConnectionStates, Handshake} = - finalize_handshake(State1, abbreviated), - State2 = State1#state{connection_states = - ConnectionStates, - tls_handshake_history = Handshake}, - {Record, State} = next_record(State2), - next_state(hello, abbreviated, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - -handle_new_session(NewId, CipherSuite, Compression, #state{session = Session0} = State0) -> - Session = Session0#session{session_id = NewId, - cipher_suite = CipherSuite, - compression_method = Compression}, - {Record, State} = next_record(State0#state{session = Session}), - next_state(hello, certify, Record, State). - -handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, - negotiated_version = Version, - host = Host, port = Port, - session_cache = Cache, - session_cache_cb = CacheCb} = State0) -> - Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}), - case tls_handshake:master_secret(Version, Session, - ConnectionStates0, client) of - {_, ConnectionStates} -> - {Record, State} = - next_record(State0#state{ - connection_states = ConnectionStates, - session = Session}), - next_state(hello, abbreviated, Record, State); - #alert{} = Alert -> - handle_own_alert(Alert, Version, hello, State0) - end. - - -client_certify_and_key_exchange(#state{negotiated_version = Version} = - State0) -> - try do_client_certify_and_key_exchange(State0) of - State1 = #state{} -> - {ConnectionStates, Handshake} = finalize_handshake(State1, certify), - State2 = State1#state{connection_states = ConnectionStates, - %% Reinitialize - client_certificate_requested = false, - tls_handshake_history = Handshake}, - {Record, State} = next_record(State2), - next_state(certify, cipher, Record, State) - catch - throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, certify, State0) - end. +encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> + ssl_record:encode_change_cipher_spec(Version, ConnectionStates). -do_client_certify_and_key_exchange(State0) -> - State1 = certify_client(State0), - State2 = key_exchange(State1), - verify_client_cert(State2). +decode_alerts(Bin) -> + ssl_alert:decode(Bin). -server_certify_and_key_exchange(State0) -> - State1 = certify_server(State0), - State2 = key_exchange(State1), - request_client_cert(State2). - -server_hello(ServerHello, #state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> - CipherSuite = ServerHello#server_hello.cipher_suite, - {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite), - {BinMsg, ConnectionStates1, Handshake1} = - encode_handshake(ServerHello, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates1, - tls_handshake_history = Handshake1, - key_algorithm = KeyAlgorithm, - hashsign_algorithm = default_hashsign(Version, KeyAlgorithm)}. - -server_hello_done(#state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> +initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions, Tracker}, User, + {CbModule, DataTag, CloseTag, ErrorTag}) -> + #ssl_options{beast_mitigation = BeastMitigation} = SSLOptions, + ConnectionStates = ssl_record:init_connection_states(Role, BeastMitigation), - HelloDone = tls_handshake:server_hello_done(), + SessionCacheCb = case application:get_env(ssl, session_cb) of + {ok, Cb} when is_atom(Cb) -> + Cb; + _ -> + ssl_session_cache + end, - {BinHelloDone, ConnectionStates, Handshake} = - encode_handshake(HelloDone, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinHelloDone), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}. - -certify_server(#state{key_algorithm = Algo} = State) - when Algo == dh_anon; Algo == ecdh_anon; Algo == psk; Algo == dhe_psk; Algo == srp_anon -> - State; - -certify_server(#state{transport_cb = Transport, - socket = Socket, - negotiated_version = Version, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - session = #session{own_certificate = OwnCert}} = State) -> - case tls_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of - CertMsg = #certificate{} -> - {BinCertMsg, ConnectionStates, Handshake} = - encode_handshake(CertMsg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinCertMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake - }; - Alert = #alert{} -> - throw(Alert) - end. - -key_exchange(#state{role = server, key_algorithm = rsa} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == dhe_dss; - Algo == dhe_rsa; - Algo == dh_anon -> - DHKeys = public_key:generate_key(Params), - ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {dh, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = DHKeys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, private_key = Key, key_algorithm = Algo} = State) - when Algo == ecdh_ecdsa; Algo == ecdh_rsa -> - State#state{diffie_hellman_keys = Key}; -key_exchange(#state{role = server, key_algorithm = Algo, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == ecdhe_ecdsa; Algo == ecdhe_rsa; - Algo == ecdh_anon -> - - ECDHKeys = public_key:generate_key(select_curve(State)), - ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {ecdh, ECDHKeys, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake1} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = ECDHKeys, - tls_handshake_history = Handshake1}; - -key_exchange(#state{role = server, key_algorithm = psk, - ssl_options = #ssl_options{psk_identity = undefined}} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = dhe_psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - diffie_hellman_params = #'DHParameter'{} = Params, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - DHKeys = public_key:generate_key(Params), - ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - diffie_hellman_keys = DHKeys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = rsa_psk, - ssl_options = #ssl_options{psk_identity = undefined}} = State) -> - State; -key_exchange(#state{role = server, key_algorithm = rsa_psk, - ssl_options = #ssl_options{psk_identity = PskIdentityHint}, - hashsign_algorithm = HashSignAlgo, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) -> - ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {psk, PskIdentityHint, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = server, key_algorithm = Algo, - ssl_options = #ssl_options{user_lookup_fun = LookupFun}, - hashsign_algorithm = HashSignAlgo, - session = #session{srp_username = Username}, - private_key = PrivateKey, - connection_states = ConnectionStates0, - negotiated_version = Version, - tls_handshake_history = Handshake0, - socket = Socket, - transport_cb = Transport - } = State) - when Algo == srp_dss; - Algo == srp_rsa; - Algo == srp_anon -> - SrpParams = handle_srp_identity(Username, LookupFun), - Keys = case generate_srp_server_keys(SrpParams, 0) of - Alert = #alert{} -> - throw(Alert); - Keys0 = {_,_} -> - Keys0 - end, - ConnectionState = - tls_record:pending_connection_state(ConnectionStates0, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Msg = tls_handshake:key_exchange(server, Version, {srp, Keys, SrpParams, - HashSignAlgo, ClientRandom, - ServerRandom, - PrivateKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - srp_params = SrpParams, - srp_keys = Keys, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = rsa, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - premaster_secret = PremasterSecret, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - diffie_hellman_keys = {DhPubKey, _}, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == dhe_dss; - Algorithm == dhe_rsa; - Algorithm == dh_anon -> - Msg = tls_handshake:key_exchange(client, Version, {dh, DhPubKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - diffie_hellman_keys = Keys, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == ecdhe_ecdsa; Algorithm == ecdhe_rsa; - Algorithm == ecdh_ecdsa; Algorithm == ecdh_rsa; - Algorithm == ecdh_anon -> - Msg = tls_handshake:key_exchange(client, Version, {ecdh, Keys}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - key_algorithm = psk, - negotiated_version = Version, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = tls_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - key_algorithm = dhe_psk, - negotiated_version = Version, - diffie_hellman_keys = {DhPubKey, _}, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = tls_handshake:key_exchange(client, Version, {dhe_psk, SslOpts#ssl_options.psk_identity, DhPubKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - ssl_options = SslOpts, - connection_states = ConnectionStates0, - key_algorithm = rsa_psk, - public_key_info = PublicKeyInfo, - negotiated_version = Version, - premaster_secret = PremasterSecret, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) -> - Msg = rsa_psk_key_exchange(Version, SslOpts#ssl_options.psk_identity, PremasterSecret, PublicKeyInfo), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}; - -key_exchange(#state{role = client, - connection_states = ConnectionStates0, - key_algorithm = Algorithm, - negotiated_version = Version, - srp_keys = {ClientPubKey, _}, - socket = Socket, transport_cb = Transport, - tls_handshake_history = Handshake0} = State) - when Algorithm == srp_dss; - Algorithm == srp_rsa; - Algorithm == srp_anon -> - Msg = tls_handshake:key_exchange(client, Version, {srp, ClientPubKey}), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}. - -rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - tls_handshake:key_exchange(client, Version, - {premaster_secret, PremasterSecret, - PublicKeyInfo}); -rsa_key_exchange(_, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). - -rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Algorithm, _, _}) - when Algorithm == ?rsaEncryption; - Algorithm == ?md2WithRSAEncryption; - Algorithm == ?md5WithRSAEncryption; - Algorithm == ?sha1WithRSAEncryption; - Algorithm == ?sha224WithRSAEncryption; - Algorithm == ?sha256WithRSAEncryption; - Algorithm == ?sha384WithRSAEncryption; - Algorithm == ?sha512WithRSAEncryption - -> - tls_handshake:key_exchange(client, Version, - {psk_premaster_secret, PskIdentity, PremasterSecret, - PublicKeyInfo}); -rsa_psk_key_exchange(_, _, _, _) -> - throw (?ALERT_REC(?FATAL,?HANDSHAKE_FAILURE)). - -request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer}, - connection_states = ConnectionStates0, - cert_db = CertDbHandle, - cert_db_ref = CertDbRef, - tls_handshake_history = Handshake0, - negotiated_version = Version, - socket = Socket, - transport_cb = Transport} = State) -> - Msg = tls_handshake:certificate_request(ConnectionStates0, CertDbHandle, CertDbRef), - {BinMsg, ConnectionStates, Handshake} = - encode_handshake(Msg, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{client_certificate_requested = true, - connection_states = ConnectionStates, - tls_handshake_history = Handshake}; -request_client_cert(#state{ssl_options = #ssl_options{verify = verify_none}} = - State) -> - State. + Monitor = erlang:monitor(process, User), -finalize_handshake(State, StateName) -> - ConnectionStates0 = cipher_protocol(State), - - ConnectionStates = - tls_record:activate_pending_connection_state(ConnectionStates0, - write), - - State1 = State#state{connection_states = ConnectionStates}, - State2 = next_protocol(State1), - finished(State2, StateName). - -next_protocol(#state{role = server} = State) -> - State; -next_protocol(#state{next_protocol = undefined} = State) -> - State; -next_protocol(#state{expecting_next_protocol_negotiation = false} = State) -> - State; -next_protocol(#state{transport_cb = Transport, socket = Socket, - negotiated_version = Version, - next_protocol = NextProtocol, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0} = State) -> - NextProtocolMessage = tls_handshake:next_protocol(NextProtocol), - {BinMsg, ConnectionStates, Handshake} = encode_handshake(NextProtocolMessage, Version, ConnectionStates0, Handshake0), - Transport:send(Socket, BinMsg), - State#state{connection_states = ConnectionStates, - tls_handshake_history = Handshake}. + #state{socket_options = SocketOptions, + ssl_options = SSLOptions, + session = #session{is_resumable = new}, + transport_cb = CbModule, + data_tag = DataTag, + close_tag = CloseTag, + error_tag = ErrorTag, + role = Role, + host = Host, + port = Port, + socket = Socket, + connection_states = ConnectionStates, + protocol_buffers = #protocol_buffers{}, + user_application = {Monitor, User}, + user_data_buffer = <<>>, + session_cache_cb = SessionCacheCb, + renegotiation = {false, first}, + allow_renegotiate = SSLOptions#ssl_options.client_renegotiation, + start_or_recv_from = undefined, + protocol_cb = ?MODULE, + tracker = Tracker, + flight_buffer = [] + }. -cipher_protocol(#state{connection_states = ConnectionStates0, - socket = Socket, - negotiated_version = Version, - transport_cb = Transport}) -> - {BinChangeCipher, ConnectionStates} = - encode_change_cipher(#change_cipher_spec{}, - Version, ConnectionStates0), - Transport:send(Socket, BinChangeCipher), - ConnectionStates. - -finished(#state{role = Role, socket = Socket, negotiated_version = Version, - transport_cb = Transport, - session = Session, - connection_states = ConnectionStates0, - tls_handshake_history = Handshake0}, StateName) -> - MasterSecret = Session#session.master_secret, - Finished = tls_handshake:finished(Version, Role, - get_current_connection_state_prf(ConnectionStates0, write), - MasterSecret, Handshake0), - ConnectionStates1 = save_verify_data(Role, Finished, ConnectionStates0, StateName), - {BinFinished, ConnectionStates, Handshake} = - encode_handshake(Finished, Version, ConnectionStates1, Handshake0), - Transport:send(Socket, BinFinished), - {ConnectionStates, Handshake}. - -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) -> - tls_record:set_client_verify_data(current_write, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, cipher) -> - tls_record:set_server_verify_data(current_both, Data, ConnectionStates); -save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - tls_record:set_client_verify_data(current_both, Data, ConnectionStates); -save_verify_data(server, #finished{verify_data = Data}, ConnectionStates, abbreviated) -> - tls_record:set_server_verify_data(current_write, Data, ConnectionStates). - -handle_server_key(#server_key_exchange{exchange_keys = Keys}, - #state{key_algorithm = KeyAlg, - negotiated_version = Version} = State) -> - Params = tls_handshake:decode_server_key(Keys, KeyAlg, Version), - HashSign = connection_hashsign(Params#server_key_params.hashsign, State), - case HashSign of - {_, SignAlgo} when SignAlgo == anon; SignAlgo == ecdh_anon -> - server_master_secret(Params#server_key_params.params, State); - _ -> - verify_server_key(Params, HashSign, State) - end. -verify_server_key(#server_key_params{params = Params, - params_bin = EncParams, - signature = Signature}, - HashSign = {HashAlgo, _}, - #state{negotiated_version = Version, - public_key_info = PubKeyInfo, - connection_states = ConnectionStates} = State) -> - ConnectionState = - tls_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - Hash = tls_handshake:server_key_exchange_hash(HashAlgo, - <<ClientRandom/binary, - ServerRandom/binary, - EncParams/binary>>), - case tls_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of - true -> - server_master_secret(Params, State); - false -> - ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) +update_ssl_options_from_sni(OrigSSLOptions, SNIHostname) -> + SSLOption = + case OrigSSLOptions#ssl_options.sni_fun of + undefined -> + proplists:get_value(SNIHostname, + OrigSSLOptions#ssl_options.sni_hosts); + SNIFun -> + SNIFun(SNIHostname) + end, + case SSLOption of + undefined -> + undefined; + _ -> + ssl:handle_options(SSLOption, OrigSSLOptions) end. -server_master_secret(#server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}, - State) -> - dh_master_secret(P, G, ServerPublicDhKey, undefined, State); - -server_master_secret(#server_ecdh_params{curve = ECCurve, public = ECServerPubKey}, - State) -> - ECDHKeys = public_key:generate_key(ECCurve), - ec_dh_master_secret(ECDHKeys, #'ECPoint'{point = ECServerPubKey}, State#state{diffie_hellman_keys = ECDHKeys}); - -server_master_secret(#server_psk_params{ - hint = IdentityHint}, - State) -> - %% store for later use - State#state{psk_identity = IdentityHint}; - -server_master_secret(#server_dhe_psk_params{ - hint = IdentityHint, - dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = ServerPublicDhKey}}, - State) -> - dhe_psk_master_secret(IdentityHint, P, G, ServerPublicDhKey, undefined, State); - -server_master_secret(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, - State) -> - client_srp_master_secret(G, N, S, B, undefined, State). - -master_from_premaster_secret(PremasterSecret, - #state{session = Session, - negotiated_version = Version, role = Role, - connection_states = ConnectionStates0} = State) -> - case tls_handshake:master_secret(Version, PremasterSecret, - ConnectionStates0, Role) of - {MasterSecret, ConnectionStates} -> - State#state{ - session = - Session#session{master_secret = MasterSecret}, - connection_states = ConnectionStates}; +next_tls_record(Data, #state{protocol_buffers = #protocol_buffers{tls_record_buffer = Buf0, + tls_cipher_texts = CT0} = Buffers} = State0) -> + case tls_record:get_tls_records(Data, Buf0) of + {Records, Buf1} -> + CT1 = CT0 ++ Records, + next_record(State0#state{protocol_buffers = + Buffers#protocol_buffers{tls_record_buffer = Buf1, + tls_cipher_texts = CT1}}); #alert{} = Alert -> Alert end. -dh_master_secret(#'DHParameter'{} = Params, OtherPublicDhKey, MyPrivateKey, State) -> - PremasterSecret = - public_key:compute_key(OtherPublicDhKey, MyPrivateKey, Params), - master_from_premaster_secret(PremasterSecret, State). - -dh_master_secret(Prime, Base, PublicDhKey, undefined, State) -> - Keys = {_, PrivateDhKey} = crypto:generate_key(dh, [Prime, Base]), - dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State#state{diffie_hellman_keys = Keys}); - -dh_master_secret(Prime, Base, PublicDhKey, PrivateDhKey, State) -> - PremasterSecret = - crypto:compute_key(dh, PublicDhKey, PrivateDhKey, [Prime, Base]), - master_from_premaster_secret(PremasterSecret, State). - -ec_dh_master_secret(ECDHKeys, ECPoint, State) -> - PremasterSecret = - public_key:compute_key(ECPoint, ECDHKeys), - master_from_premaster_secret(PremasterSecret, State). - -handle_psk_identity(_PSKIdentity, LookupFun) - when LookupFun == undefined -> - error; -handle_psk_identity(PSKIdentity, {Fun, UserState}) -> - Fun(psk, PSKIdentity, UserState). - -server_psk_master_secret(ClientPSKIdentity, - #state{ssl_options = SslOpts} = State) -> - case handle_psk_identity(ClientPSKIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - Len = byte_size(PSK), - PremasterSecret = <<?UINT16(Len), 0:(Len*8), ?UINT16(Len), PSK/binary>>, - master_from_premaster_secret(PremasterSecret, State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, undefined, State) -> - Keys = {_, PrivateDhKey} = - crypto:generate_key(dh, [Prime, Base]), - dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey, - State#state{diffie_hellman_keys = Keys}); - -dhe_psk_master_secret(PSKIdentity, Prime, Base, PublicDhKey, PrivateDhKey, - #state{ssl_options = SslOpts} = State) -> - case handle_psk_identity(PSKIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - DHSecret = - crypto:compute_key(dh, PublicDhKey, PrivateDhKey, - [Prime, Base]), - DHLen = erlang:byte_size(DHSecret), - Len = erlang:byte_size(PSK), - PremasterSecret = <<?UINT16(DHLen), DHSecret/binary, ?UINT16(Len), PSK/binary>>, - master_from_premaster_secret(PremasterSecret, State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -server_rsa_psk_master_secret(PskIdentity, PremasterSecret, - #state{ssl_options = SslOpts} = State) -> - case handle_psk_identity(PskIdentity, SslOpts#ssl_options.user_lookup_fun) of - {ok, PSK} when is_binary(PSK) -> - Len = byte_size(PSK), - RealPMS = <<?UINT16(48), PremasterSecret/binary, ?UINT16(Len), PSK/binary>>, - master_from_premaster_secret(RealPMS, State); - #alert{} = Alert -> - Alert; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -generate_srp_server_keys(_SrpParams, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_server_keys(SrpParams = - #srp_user{generator = Generator, prime = Prime, - verifier = Verifier}, N) -> - case crypto:generate_key(srp, {host, [Verifier, Generator, Prime, '6a']}) of - error -> - generate_srp_server_keys(SrpParams, N+1); - Keys -> - Keys - end. - -generate_srp_client_keys(_Generator, _Prime, 10) -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); -generate_srp_client_keys(Generator, Prime, N) -> - - case crypto:generate_key(srp, {user, [Generator, Prime, '6a']}) of - error -> - generate_srp_client_keys(Generator, Prime, N+1); - Keys -> - Keys - end. - -handle_srp_identity(Username, {Fun, UserState}) -> - case Fun(srp, Username, UserState) of - {ok, {SRPParams, Salt, DerivedKey}} - when is_atom(SRPParams), is_binary(Salt), is_binary(DerivedKey) -> - {Generator, Prime} = ssl_srp_primes:get_srp_params(SRPParams), - Verifier = crypto:mod_pow(Generator, DerivedKey, Prime), - #srp_user{generator = Generator, prime = Prime, - salt = Salt, verifier = Verifier}; +next_record(#state{protocol_buffers = + #protocol_buffers{tls_packets = [], tls_cipher_texts = [CT | Rest]} + = Buffers, + connection_states = ConnStates0, + ssl_options = #ssl_options{padding_check = Check}} = State) -> + case tls_record:decode_cipher_text(CT, ConnStates0, Check) of + {Plain, ConnStates} -> + {Plain, State#state{protocol_buffers = + Buffers#protocol_buffers{tls_cipher_texts = Rest}, + connection_states = ConnStates}}; #alert{} = Alert -> - throw(Alert); - _ -> - throw(?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER)) - end. - -server_srp_master_secret(Verifier, Prime, ClientPub, State = #state{srp_keys = ServerKeys}) -> - case crypto:compute_key(srp, ClientPub, ServerKeys, {host, [Verifier, Prime, '6a']}) of - error -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); - PremasterSecret -> - master_from_premaster_secret(PremasterSecret, State) - end. - -client_srp_master_secret(_Generator, _Prime, _Salt, _ServerPub, #alert{} = Alert, _State) -> - Alert; -client_srp_master_secret(Generator, Prime, Salt, ServerPub, undefined, State) -> - Keys = generate_srp_client_keys(Generator, Prime, 0), - client_srp_master_secret(Generator, Prime, Salt, ServerPub, Keys, State#state{srp_keys = Keys}); - -client_srp_master_secret(Generator, Prime, Salt, ServerPub, ClientKeys, - #state{ssl_options = SslOpts} = State) -> - case ssl_srp_primes:check_srp_params(Generator, Prime) of - ok -> - {Username, Password} = SslOpts#ssl_options.srp_identity, - DerivedKey = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, Password])]), - case crypto:compute_key(srp, ServerPub, ClientKeys, {user, [DerivedKey, Prime, Generator, '6a']}) of - error -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); - PremasterSecret -> - master_from_premaster_secret(PremasterSecret, State) - end; - _ -> - ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER) - end. - -cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) -> - ConnectionStates = tls_record:set_server_verify_data(current_both, Data, ConnectionStates0), - next_state_connection(cipher, ack_connection(State#state{session = Session, - connection_states = ConnectionStates})); - -cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0} = State) -> - ConnectionStates1 = tls_record:set_client_verify_data(current_read, Data, ConnectionStates0), - {ConnectionStates, Handshake} = - finalize_handshake(State#state{connection_states = ConnectionStates1, - session = Session}, cipher), - next_state_connection(cipher, ack_connection(State#state{connection_states = - ConnectionStates, - session = Session, - tls_handshake_history = - Handshake})). -encode_alert(#alert{} = Alert, Version, ConnectionStates) -> - tls_record:encode_alert_record(Alert, Version, ConnectionStates). - -encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) -> - tls_record:encode_change_cipher_spec(Version, ConnectionStates). - -encode_handshake(HandshakeRec, Version, ConnectionStates0, Handshake0) -> - Frag = tls_handshake:encode_handshake(HandshakeRec, Version), - Handshake1 = tls_handshake:update_handshake_history(Handshake0, Frag), - {E, ConnectionStates1} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), - {E, ConnectionStates1, Handshake1}. - -encode_packet(Data, #socket_options{packet=Packet}) -> - case Packet of - 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); - 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); - 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); - _ -> Data - end. - -encode_size_packet(Bin, Size, Max) -> - Len = erlang:byte_size(Bin), - case Len > Max of - true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); - false -> <<Len:Size, Bin/binary>> - end. + {Alert, State} + end; +next_record(#state{protocol_buffers = #protocol_buffers{tls_packets = [], tls_cipher_texts = []}, + socket = Socket, + transport_cb = Transport} = State) -> + ssl_socket:setopts(Transport, Socket, [{active,once}]), + {no_record, State}; +next_record(State) -> + {no_record, State}. -decode_alerts(Bin) -> - decode_alerts(Bin, []). +next_record_if_active(State = + #state{socket_options = + #socket_options{active = false}}) -> + {no_record ,State}; -decode_alerts(<<?BYTE(Level), ?BYTE(Description), Rest/binary>>, Acc) -> - A = ?ALERT_REC(Level, Description), - decode_alerts(Rest, [A | Acc]); -decode_alerts(<<>>, Acc) -> - lists:reverse(Acc, []). +next_record_if_active(State) -> + next_record(State). passive_receive(State0 = #state{user_data_buffer = Buffer}, StateName) -> case Buffer of <<>> -> {Record, State} = next_record(State0), - next_state(StateName, StateName, Record, State); + next_event(StateName, Record, State); _ -> - case read_application_data(<<>>, State0) of - Stop = {stop, _, _} -> - Stop; - {Record, State} -> - next_state(StateName, StateName, Record, State) - end + {Record, State} = read_application_data(<<>>, State0), + next_event(StateName, Record, State) + end. + +next_event(StateName, Record, State) -> + next_event(StateName, Record, State, []). + +next_event(connection = StateName, no_record, State0, Actions) -> + case next_record_if_active(State0) of + {no_record, State} -> + ssl_connection:hibernate_after(StateName, State, Actions); + {#ssl_tls{} = Record, State} -> + {next_state, StateName, State, [{next_event, internal, {tls_record, Record}} | Actions]}; + {#alert{} = Alert, State} -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} + end; +next_event(StateName, Record, State, Actions) -> + case Record of + no_record -> + {next_state, StateName, State, Actions}; + #ssl_tls{} = Record -> + {next_state, StateName, State, [{next_event, internal, {tls_record, Record}} | Actions]}; + #alert{} = Alert -> + {next_state, StateName, State, [{next_event, internal, Alert} | Actions]} end. read_application_data(Data, #state{user_application = {_Mon, Pid}, @@ -2298,7 +612,8 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, bytes_to_read = BytesToRead, start_or_recv_from = RecvFrom, timer = Timer, - user_data_buffer = Buffer0} = State0) -> + user_data_buffer = Buffer0, + tracker = Tracker} = State0) -> Buffer1 = if Buffer0 =:= <<>> -> Data; Data =:= <<>> -> Buffer0; @@ -2306,7 +621,7 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, end, case get_data(SOpts, BytesToRead, Buffer1) of {ok, ClientData, Buffer} -> % Send data - SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom), + SocketOpt = deliver_app_data(Transport, Socket, SOpts, ClientData, Pid, RecvFrom, Tracker), cancel_timer(Timer), State = State0#state{user_data_buffer = Buffer, start_or_recv_from = undefined, @@ -2327,43 +642,10 @@ read_application_data(Data, #state{user_application = {_Mon, Pid}, {passive, Buffer} -> next_record_if_active(State0#state{user_data_buffer = Buffer}); {error,_Reason} -> %% Invalid packet in packet mode - deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom), + deliver_packet_error(Transport, Socket, SOpts, Buffer1, Pid, RecvFrom, Tracker), {stop, normal, State0} end. -write_application_data(Data0, From, #state{socket = Socket, - negotiated_version = Version, - transport_cb = Transport, - connection_states = ConnectionStates0, - send_queue = SendQueue, - socket_options = SockOpts, - ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> - Data = encode_packet(Data0, SockOpts), - - case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of - true -> - renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue), - renegotiation = {true, internal}}); - false -> - {Msgs, ConnectionStates} = tls_record:encode_data(Data, Version, ConnectionStates0), - Result = Transport:send(Socket, Msgs), - {reply, Result, - connection, State#state{connection_states = ConnectionStates}, get_timeout(State)} - end. - -time_to_renegotiate(_Data, #connection_states{current_write = - #connection_state{sequence_number = Num}}, RenegotiateAt) -> - - %% We could do test: - %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), - %% but we chose to have a some what lower renegotiateAt and a much cheaper test - is_time_to_renegotiate(Num, RenegotiateAt). - -is_time_to_renegotiate(N, M) when N < M-> - false; -is_time_to_renegotiate(_,_) -> - true. - %% Picks ClientData get_data(_, _, <<>>) -> {more, <<>>}; @@ -2410,8 +692,8 @@ decode_packet(Type, Buffer, PacketOpts) -> %% HTTP headers using the {packet, httph} option, we don't do any automatic %% switching of states. deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packet=Type}, - Data, Pid, From) -> - send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data)), + Data, Pid, From, Tracker) -> + send_or_reply(Active, Pid, From, format_reply(Transport, Socket, SOpts, Data, Tracker)), SO = case Data of {P, _, _, _} when ((P =:= http_request) or (P =:= http_response)), ((Type =:= http) or (Type =:= http_bin)) -> @@ -2431,19 +713,21 @@ deliver_app_data(Transport, Socket, SOpts = #socket_options{active=Active, packe end. format_reply(_, _,#socket_options{active = false, mode = Mode, packet = Packet, - header = Header}, Data) -> + header = Header}, Data, _) -> {ok, do_format_reply(Mode, Packet, Header, Data)}; format_reply(Transport, Socket, #socket_options{active = _, mode = Mode, packet = Packet, - header = Header}, Data) -> - {ssl, ssl_socket:socket(self(), Transport, Socket), do_format_reply(Mode, Packet, Header, Data)}. + header = Header}, Data, Tracker) -> + {ssl, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker), + do_format_reply(Mode, Packet, Header, Data)}. -deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From) -> - send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data)). +deliver_packet_error(Transport, Socket, SO= #socket_options{active = Active}, Data, Pid, From, Tracker) -> + send_or_reply(Active, Pid, From, format_packet_error(Transport, Socket, SO, Data, Tracker)). -format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data) -> +format_packet_error(_, _,#socket_options{active = false, mode = Mode}, Data, _) -> {error, {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}; -format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data) -> - {ssl_error, ssl_socket:socket(self(), Transport, Socket), {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. +format_packet_error(Transport, Socket, #socket_options{active = _, mode = Mode}, Data, Tracker) -> + {ssl_error, ssl_socket:socket(self(), Transport, Socket, ?MODULE, Tracker), + {invalid_packet, do_format_reply(Mode, raw, 0, Data)}}. do_format_reply(binary, _, N, Data) when N > 0 -> % Header mode header(N, Data); @@ -2458,7 +742,7 @@ do_format_reply(list, _,_, Data) -> binary_to_list(Data). header(0, <<>>) -> - []; + <<>>; header(_, <<>>) -> []; header(0, Binary) -> @@ -2468,7 +752,7 @@ header(N, Binary) -> [ByteN | header(N-1, NewBinary)]. send_or_reply(false, _Pid, From, Data) when From =/= undefined -> - gen_fsm:reply(From, Data); + gen_statem:reply(From, Data); %% Can happen when handling own alert or tcp error/close and there is %% no outstanding gen_fsm sync events send_or_reply(false, no_pid, _, _) -> @@ -2476,363 +760,157 @@ send_or_reply(false, no_pid, _, _) -> send_or_reply(_, Pid, _From, Data) -> send_user(Pid, Data). -opposite_role(client) -> - server; -opposite_role(server) -> - client. - send_user(Pid, Msg) -> Pid ! Msg. -handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet]} = State) -> - FsmReturn = {next_state, StateName, State#state{tls_packets = []}}, - Handle(Packet, FsmReturn); - -handle_tls_handshake(Handle, StateName, #state{tls_packets = [Packet | Packets]} = State0) -> - FsmReturn = {next_state, StateName, State0#state{tls_packets = Packets}}, - case Handle(Packet, FsmReturn) of - {next_state, NextStateName, State, _Timeout} -> - handle_tls_handshake(Handle, NextStateName, State); - {stop, _,_} = Stop -> - Stop +tls_handshake_events([]) -> + throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE, malformed_handshake)); +tls_handshake_events(Packets) -> + lists:map(fun(Packet) -> + {next_event, internal, {handshake, Packet}} + end, Packets). + +write_application_data(Data0, From, + #state{socket = Socket, + negotiated_version = Version, + transport_cb = Transport, + connection_states = ConnectionStates0, + socket_options = SockOpts, + ssl_options = #ssl_options{renegotiate_at = RenegotiateAt}} = State) -> + Data = encode_packet(Data0, SockOpts), + + case time_to_renegotiate(Data, ConnectionStates0, RenegotiateAt) of + true -> + renegotiate(State#state{renegotiation = {true, internal}}, + [{next_event, {call, From}, {application_data, Data0}}]); + false -> + {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0), + Result = Transport:send(Socket, Msgs), + ssl_connection:hibernate_after(connection, State#state{connection_states = ConnectionStates}, + [{reply, From, Result}]) end. -next_state(Current,_, #alert{} = Alert, #state{negotiated_version = Version} = State) -> - handle_own_alert(Alert, Version, Current, State); - -next_state(_,Next, no_record, State) -> - {next_state, Next, State, get_timeout(State)}; - -next_state(_,Next, #ssl_tls{type = ?ALERT, fragment = EncAlerts}, State) -> - Alerts = decode_alerts(EncAlerts), - handle_alerts(Alerts, {next_state, Next, State, get_timeout(State)}); - -next_state(Current, Next, #ssl_tls{type = ?HANDSHAKE, fragment = Data}, - State0 = #state{tls_handshake_buffer = Buf0, negotiated_version = Version}) -> - Handle = - fun({#hello_request{} = Packet, _}, {next_state, connection = SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Starts new handshake (renegotiation) - Hs0 = tls_handshake:init_handshake_history(), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs0, - renegotiation = {true, peer}}); - ({#hello_request{} = Packet, _}, {next_state, SName, State}) -> - %% This message should not be included in handshake - %% message hashes. Already in negotiation so it will be ignored! - ?MODULE:SName(Packet, State); - ({#client_hello{} = Packet, Raw}, {next_state, connection = SName, State}) -> - Version = Packet#client_hello.client_version, - Hs0 = tls_handshake:init_handshake_history(), - Hs1 = tls_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1, - renegotiation = {true, peer}}); - ({Packet, Raw}, {next_state, SName, State = #state{tls_handshake_history=Hs0}}) -> - Hs1 = tls_handshake:update_handshake_history(Hs0, Raw), - ?MODULE:SName(Packet, State#state{tls_handshake_history=Hs1}); - (_, StopState) -> StopState - end, - try - {Packets, Buf} = tls_handshake:get_tls_handshake(Version,Data,Buf0), - State = State0#state{tls_packets = Packets, tls_handshake_buffer = Buf}, - handle_tls_handshake(Handle, Next, State) - catch throw:#alert{} = Alert -> - handle_own_alert(Alert, Version, Current, State0) - end; - -next_state(_, StateName, #ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, State0) -> - case read_application_data(Data, State0) of - Stop = {stop,_,_} -> - Stop; - {Record, State} -> - next_state(StateName, StateName, Record, State) - end; -next_state(Current, Next, #ssl_tls{type = ?CHANGE_CIPHER_SPEC, fragment = <<1>>} = - _ChangeCipher, - #state{connection_states = ConnectionStates0} = State0) -> - ConnectionStates1 = - tls_record:activate_pending_connection_state(ConnectionStates0, read), - {Record, State} = next_record(State0#state{connection_states = ConnectionStates1}), - next_state(Current, Next, Record, State); -next_state(Current, Next, #ssl_tls{type = _Unknown}, State0) -> - %% Ignore unknown type - {Record, State} = next_record(State0), - next_state(Current, Next, Record, State). - -next_tls_record(Data, #state{tls_record_buffer = Buf0, - tls_cipher_texts = CT0} = State0) -> - case tls_record:get_tls_records(Data, Buf0) of - {Records, Buf1} -> - CT1 = CT0 ++ Records, - next_record(State0#state{tls_record_buffer = Buf1, - tls_cipher_texts = CT1}); - #alert{} = Alert -> - Alert +encode_packet(Data, #socket_options{packet=Packet}) -> + case Packet of + 1 -> encode_size_packet(Data, 8, (1 bsl 8) - 1); + 2 -> encode_size_packet(Data, 16, (1 bsl 16) - 1); + 4 -> encode_size_packet(Data, 32, (1 bsl 32) - 1); + _ -> Data end. -next_record(#state{tls_packets = [], tls_cipher_texts = [], socket = Socket, - transport_cb = Transport} = State) -> - ssl_socket:setopts(Transport, Socket, [{active,once}]), - {no_record, State}; -next_record(#state{tls_packets = [], tls_cipher_texts = [CT | Rest], - connection_states = ConnStates0} = State) -> - case tls_record:decode_cipher_text(CT, ConnStates0) of - {Plain, ConnStates} -> - {Plain, State#state{tls_cipher_texts = Rest, connection_states = ConnStates}}; - #alert{} = Alert -> - {Alert, State} - end; -next_record(State) -> - {no_record, State}. - -next_record_if_active(State = - #state{socket_options = - #socket_options{active = false}}) -> - {no_record ,State}; - -next_record_if_active(State) -> - next_record(State). - -next_state_connection(StateName, #state{send_queue = Queue0, - negotiated_version = Version, - socket = Socket, - transport_cb = Transport, - connection_states = ConnectionStates0 - } = State) -> - %% Send queued up data that was queued while renegotiating - case queue:out(Queue0) of - {{value, {From, Data}}, Queue} -> - {Msgs, ConnectionStates} = - tls_record:encode_data(Data, Version, ConnectionStates0), - Result = Transport:send(Socket, Msgs), - gen_fsm:reply(From, Result), - next_state_connection(StateName, - State#state{connection_states = ConnectionStates, - send_queue = Queue}); - {empty, Queue0} -> - next_state_is_connection(StateName, State) +encode_size_packet(Bin, Size, Max) -> + Len = erlang:byte_size(Bin), + case Len > Max of + true -> throw({error, {badarg, {packet_to_large, Len, Max}}}); + false -> <<Len:Size, Bin/binary>> end. -%% In next_state_is_connection/1: clear tls_handshake, -%% premaster_secret and public_key_info (only needed during handshake) -%% to reduce memory foot print of a connection. -next_state_is_connection(_, State = - #state{start_or_recv_from = RecvFrom, - socket_options = - #socket_options{active = false}}) when RecvFrom =/= undefined -> - passive_receive(State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = tls_handshake:init_handshake_history()}, connection); - -next_state_is_connection(StateName, State0) -> - {Record, State} = next_record_if_active(State0), - next_state(StateName, connection, Record, State#state{premaster_secret = undefined, - public_key_info = undefined, - tls_handshake_history = tls_handshake:init_handshake_history()}). - -register_session(client, Host, Port, #session{is_resumable = new} = Session0) -> - Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Host, Port, Session), - Session; -register_session(server, _, Port, #session{is_resumable = new} = Session0) -> - Session = Session0#session{is_resumable = true}, - ssl_manager:register_session(Port, Session), - Session; -register_session(_, _, _, Session) -> - Session. %% Already registered - -invalidate_session(client, Host, Port, Session) -> - ssl_manager:invalidate_session(Host, Port, Session); -invalidate_session(server, _, Port, Session) -> - ssl_manager:invalidate_session(Port, Session). - -initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User, - {CbModule, DataTag, CloseTag, ErrorTag}) -> - ConnectionStates = tls_record:init_connection_states(Role), - - SessionCacheCb = case application:get_env(ssl, session_cb) of - {ok, Cb} when is_atom(Cb) -> - Cb; - _ -> - ssl_session_cache - end, +time_to_renegotiate(_Data, + #connection_states{current_write = + #connection_state{sequence_number = Num}}, + RenegotiateAt) -> - Monitor = erlang:monitor(process, User), - - #state{socket_options = SocketOptions, - %% We do not want to save the password in the state so that - %% could be written in the clear into error logs. - ssl_options = SSLOptions#ssl_options{password = undefined}, - session = #session{is_resumable = new}, - transport_cb = CbModule, - data_tag = DataTag, - close_tag = CloseTag, - error_tag = ErrorTag, - role = Role, - host = Host, - port = Port, - socket = Socket, - connection_states = ConnectionStates, - tls_handshake_buffer = <<>>, - tls_record_buffer = <<>>, - tls_cipher_texts = [], - user_application = {Monitor, User}, - user_data_buffer = <<>>, - log_alert = true, - session_cache_cb = SessionCacheCb, - renegotiation = {false, first}, - start_or_recv_from = undefined, - send_queue = queue:new() - }. + %% We could do test: + %% is_time_to_renegotiate((erlang:byte_size(_Data) div ?MAX_PLAIN_TEXT_LENGTH) + 1, RenegotiateAt), + %% but we chose to have a some what lower renegotiateAt and a much cheaper test + is_time_to_renegotiate(Num, RenegotiateAt). -get_socket_opts(_,_,[], _, Acc) -> - {ok, Acc}; -get_socket_opts(Transport, Socket, [mode | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, - [{mode, SockOpts#socket_options.mode} | Acc]); -get_socket_opts(Transport, Socket, [packet | Tags], SockOpts, Acc) -> - case SockOpts#socket_options.packet of - {Type, headers} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]); - Type -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [{packet, Type} | Acc]) - end; -get_socket_opts(Transport, Socket, [header | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, - [{header, SockOpts#socket_options.header} | Acc]); -get_socket_opts(Transport, Socket, [active | Tags], SockOpts, Acc) -> - get_socket_opts(Transport, Socket, Tags, SockOpts, - [{active, SockOpts#socket_options.active} | Acc]); -get_socket_opts(Transport, Socket, [Tag | Tags], SockOpts, Acc) -> - try ssl_socket:getopts(Transport, Socket, [Tag]) of - {ok, [Opt]} -> - get_socket_opts(Transport, Socket, Tags, SockOpts, [Opt | Acc]); - {error, Error} -> - {error, {options, {socket_options, Tag, Error}}} - catch - %% So that inet behavior does not crash our process - _:Error -> {error, {options, {socket_options, Tag, Error}}} - end; -get_socket_opts(_, _,Opts, _,_) -> - {error, {options, {socket_options, Opts, function_clause}}}. - -set_socket_opts(_,_, [], SockOpts, []) -> - {ok, SockOpts}; -set_socket_opts(Transport, Socket, [], SockOpts, Other) -> - %% Set non emulated options - try ssl_socket:setopts(Transport, Socket, Other) of - ok -> - {ok, SockOpts}; - {error, InetError} -> - {{error, {options, {socket_options, Other, InetError}}}, SockOpts} - catch - _:Error -> - %% So that inet behavior does not crash our process - {{error, {options, {socket_options, Other, Error}}}, SockOpts} - end; +is_time_to_renegotiate(N, M) when N < M-> + false; +is_time_to_renegotiate(_,_) -> + true. +renegotiate(#state{role = client} = State, Actions) -> + %% Handle same way as if server requested + %% the renegotiation + Hs0 = ssl_handshake:init_handshake_history(), + {next_state, connection, State#state{tls_handshake_history = Hs0}, + [{next_event, internal, #hello_request{}} | Actions]}; -set_socket_opts(Transport,Socket, [{mode, Mode}| Opts], SockOpts, Other) when Mode == list; Mode == binary -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{mode = Mode}, Other); -set_socket_opts(_, _, [{mode, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport,Socket, [{packet, Packet}| Opts], SockOpts, Other) when Packet == raw; - Packet == 0; - Packet == 1; - Packet == 2; - Packet == 4; - Packet == asn1; - Packet == cdr; - Packet == sunrm; - Packet == fcgi; - Packet == tpkt; - Packet == line; - Packet == http; - Packet == httph; - Packet == http_bin; - Packet == httph_bin -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{packet = Packet}, Other); -set_socket_opts(_, _, [{packet, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{header, Header}| Opts], SockOpts, Other) when is_integer(Header) -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{header = Header}, Other); -set_socket_opts(_, _, [{header, _} = Opt| _], SockOpts, _) -> - {{error,{options, {socket_options, Opt}}}, SockOpts}; -set_socket_opts(Transport, Socket, [{active, Active}| Opts], SockOpts, Other) when Active == once; - Active == true; - Active == false -> - set_socket_opts(Transport, Socket, Opts, - SockOpts#socket_options{active = Active}, Other); -set_socket_opts(_, _, [{active, _} = Opt| _], SockOpts, _) -> - {{error, {options, {socket_options, Opt}} }, SockOpts}; -set_socket_opts(Transport, Socket, [Opt | Opts], SockOpts, Other) -> - set_socket_opts(Transport, Socket, Opts, SockOpts, [Opt | Other]). +renegotiate(#state{role = server, + socket = Socket, + transport_cb = Transport, + negotiated_version = Version, + connection_states = ConnectionStates0} = State0, Actions) -> + HelloRequest = ssl_handshake:hello_request(), + Frag = tls_handshake:encode_handshake(HelloRequest, Version), + Hs0 = ssl_handshake:init_handshake_history(), + {BinMsg, ConnectionStates} = + ssl_record:encode_handshake(Frag, Version, ConnectionStates0), + Transport:send(Socket, BinMsg), + State1 = State0#state{connection_states = + ConnectionStates, + tls_handshake_history = Hs0}, + {Record, State} = next_record(State1), + next_event(hello, Record, State, Actions). handle_alerts([], Result) -> Result; -handle_alerts(_, {stop, _, _} = Stop) -> - %% If it is a fatal alert immediately close +handle_alerts(_, {stop,_} = Stop) -> Stop; -handle_alerts([Alert | Alerts], {next_state, StateName, State, _Timeout}) -> - handle_alerts(Alerts, handle_alert(Alert, StateName, State)). - +handle_alerts([Alert | Alerts], {next_state, StateName, State}) -> + handle_alerts(Alerts, handle_alert(Alert, StateName, State)); +handle_alerts([Alert | Alerts], {next_state, StateName, State, _Actions}) -> + handle_alerts(Alerts, handle_alert(Alert, StateName, State)). handle_alert(#alert{level = ?FATAL} = Alert, StateName, #state{socket = Socket, transport_cb = Transport, - start_or_recv_from = From, host = Host, + ssl_options = SslOpts, start_or_recv_from = From, host = Host, port = Port, session = Session, user_application = {_Mon, Pid}, - log_alert = Log, role = Role, socket_options = Opts} = State) -> + role = Role, socket_options = Opts, tracker = Tracker}) -> invalidate_session(Role, Host, Port, Session), - log_alert(Log, StateName, Alert), - alert_user(Transport, Socket, StateName, Opts, Pid, From, Alert, Role), - {stop, normal, State}; + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role), + {stop, normal}; handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert, StateName, State) -> handle_normal_shutdown(Alert, StateName, State), - {stop, {shutdown, peer_close}, State}; + {stop, {shutdown, peer_close}}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{log_alert = Log, renegotiation = {true, internal}} = State) -> - log_alert(Log, StateName, Alert), + #state{ssl_options = SslOpts, renegotiation = {true, internal}} = State) -> + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), handle_normal_shutdown(Alert, StateName, State), - {stop, {shutdown, peer_close}, State}; + {stop, {shutdown, peer_close}}; handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName, - #state{log_alert = Log, renegotiation = {true, From}} = State0) -> - log_alert(Log, StateName, Alert), - gen_fsm:reply(From, {error, renegotiation_rejected}), + #state{ssl_options = SslOpts, renegotiation = {true, From}} = State0) -> + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), + gen_statem:reply(From, {error, renegotiation_rejected}), {Record, State} = next_record(State0), - next_state(StateName, connection, Record, State); + %% Go back to connection! + next_event(connection, Record, State); -handle_alert(#alert{level = ?WARNING, description = ?USER_CANCELED} = Alert, StateName, - #state{log_alert = Log} = State0) -> - log_alert(Log, StateName, Alert), +%% Gracefully log and ignore all other warning alerts +handle_alert(#alert{level = ?WARNING} = Alert, StateName, + #state{ssl_options = SslOpts} = State0) -> + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), {Record, State} = next_record(State0), - next_state(StateName, StateName, Record, State). + next_event(StateName, Record, State). -alert_user(Transport, Socket, connection, Opts, Pid, From, Alert, Role) -> - alert_user(Transport,Socket, Opts#socket_options.active, Pid, From, Alert, Role); -alert_user(Transport, Socket,_, _, _, From, Alert, Role) -> - alert_user(Transport, Socket, From, Alert, Role). +alert_user(Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role) -> + alert_user(Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role); +alert_user(Transport, Tracker, Socket,_, _, _, From, Alert, Role) -> + alert_user(Transport, Tracker, Socket, From, Alert, Role). -alert_user(Transport, Socket, From, Alert, Role) -> - alert_user(Transport, Socket, false, no_pid, From, Alert, Role). +alert_user(Transport, Tracker, Socket, From, Alert, Role) -> + alert_user(Transport, Tracker, Socket, false, no_pid, From, Alert, Role). -alert_user(_,_, false = Active, Pid, From, Alert, Role) -> +alert_user(_, _, _, false = Active, Pid, From, Alert, Role) when From =/= undefined -> %% If there is an outstanding ssl_accept | recv %% From will be defined and send_or_reply will %% send the appropriate error message. ReasonCode = ssl_alert:reason_code(Alert, Role), send_or_reply(Active, Pid, From, {error, ReasonCode}); -alert_user(Transport, Socket, Active, Pid, From, Alert, Role) -> +alert_user(Transport, Tracker, Socket, Active, Pid, From, Alert, Role) -> case ssl_alert:reason_code(Alert, Role) of closed -> send_or_reply(Active, Pid, From, - {ssl_closed, ssl_socket:socket(self(), Transport, Socket)}); + {ssl_closed, ssl_socket:socket(self(), + Transport, Socket, ?MODULE, Tracker)}); ReasonCode -> send_or_reply(Active, Pid, From, - {ssl_error, ssl_socket:socket(self(), Transport, Socket), ReasonCode}) + {ssl_error, ssl_socket:socket(self(), + Transport, Socket, ?MODULE, Tracker), ReasonCode}) end. log_alert(true, Info, Alert) -> @@ -2845,240 +923,119 @@ handle_own_alert(Alert, Version, StateName, #state{transport_cb = Transport, socket = Socket, connection_states = ConnectionStates, - log_alert = Log} = State) -> + ssl_options = SslOpts} = State) -> try %% Try to tell the other side {BinMsg, _} = - encode_alert(Alert, Version, ConnectionStates), - Transport:send(Socket, BinMsg), - workaround_transport_delivery_problems(Socket, Transport) + ssl_alert:encode(Alert, Version, ConnectionStates), + Transport:send(Socket, BinMsg) catch _:_ -> %% Can crash if we are in a uninitialized state ignore end, try %% Try to tell the local user - log_alert(Log, StateName, Alert), + log_alert(SslOpts#ssl_options.log_alert, StateName, Alert), handle_normal_shutdown(Alert,StateName, State) catch _:_ -> ok end, - {stop, {shutdown, own_alert}, State}. + {stop, {shutdown, own_alert}}. handle_normal_shutdown(Alert, _, #state{socket = Socket, transport_cb = Transport, start_or_recv_from = StartFrom, + tracker = Tracker, role = Role, renegotiation = {false, first}}) -> - alert_user(Transport, Socket, StartFrom, Alert, Role); + alert_user(Transport, Tracker,Socket, StartFrom, Alert, Role); handle_normal_shutdown(Alert, StateName, #state{socket = Socket, socket_options = Opts, transport_cb = Transport, user_application = {_Mon, Pid}, + tracker = Tracker, start_or_recv_from = RecvFrom, role = Role}) -> - alert_user(Transport, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role). - -handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) -> - Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE), - handle_own_alert(Alert, Version, {Info, Msg}, State). - -make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), - <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; -make_premaster_secret(_, _) -> - undefined. - -ack_connection(#state{renegotiation = {true, Initiater}} = State) - when Initiater == internal; - Initiater == peer -> - State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {true, From}} = State) -> - gen_fsm:reply(From, ok), - State#state{renegotiation = undefined}; -ack_connection(#state{renegotiation = {false, first}, - start_or_recv_from = StartFrom, - timer = Timer} = State) when StartFrom =/= undefined -> - gen_fsm:reply(StartFrom, connected), - cancel_timer(Timer), - State#state{renegotiation = undefined, start_or_recv_from = undefined, timer = undefined}; -ack_connection(State) -> - State. - -renegotiate(#state{role = client} = State) -> - %% Handle same way as if server requested - %% the renegotiation - Hs0 = tls_handshake:init_handshake_history(), - connection(#hello_request{}, State#state{tls_handshake_history = Hs0}); -renegotiate(#state{role = server, - socket = Socket, - transport_cb = Transport, - negotiated_version = Version, - connection_states = ConnectionStates0} = State0) -> - HelloRequest = tls_handshake:hello_request(), - Frag = tls_handshake:encode_handshake(HelloRequest, Version), - Hs0 = tls_handshake:init_handshake_history(), - {BinMsg, ConnectionStates} = - tls_record:encode_handshake(Frag, Version, ConnectionStates0), - Transport:send(Socket, BinMsg), - {Record, State} = next_record(State0#state{connection_states = - ConnectionStates, - tls_handshake_history = Hs0}), - next_state(connection, hello, Record, State#state{allow_renegotiate = true}). - -notify_senders(SendQueue) -> - lists:foreach(fun({From, _}) -> - gen_fsm:reply(From, {error, closed}) - end, queue:to_list(SendQueue)). - -notify_renegotiater({true, From}) when not is_atom(From) -> - gen_fsm:reply(From, {error, closed}); -notify_renegotiater(_) -> - ok. - -terminate_alert(Reason, Version, ConnectionStates) when Reason == normal; - Reason == user_close -> - {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates), - BinAlert; -terminate_alert({shutdown, _}, Version, ConnectionStates) -> - {BinAlert, _} = encode_alert(?ALERT_REC(?WARNING, ?CLOSE_NOTIFY), - Version, ConnectionStates), - BinAlert; - -terminate_alert(_, Version, ConnectionStates) -> - {BinAlert, _} = encode_alert(?ALERT_REC(?FATAL, ?INTERNAL_ERROR), - Version, ConnectionStates), - BinAlert. - -workaround_transport_delivery_problems(Socket, gen_tcp = Transport) -> - %% Standard trick to try to make sure all - %% data sent to the tcp port is really delivered to the - %% peer application before tcp port is closed so that the peer will - %% get the correct TLS alert message and not only a transport close. - ssl_socket:setopts(Transport, Socket, [{active, false}]), - Transport:shutdown(Socket, write), - %% Will return when other side has closed or after 30 s - %% e.g. we do not want to hang if something goes wrong - %% with the network but we want to maximise the odds that - %% peer application gets all data sent on the tcp connection. - Transport:recv(Socket, 0, 30000); -workaround_transport_delivery_problems(Socket, Transport) -> - Transport:close(Socket). + alert_user(Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role). -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = undefined}}) -> - infinity; -get_timeout(#state{ssl_options=#ssl_options{hibernate_after = HibernateAfter}}) -> - HibernateAfter. - -handle_trusted_certs_db(#state{ssl_options = #ssl_options{cacertfile = <<>>}}) -> - %% No trusted certs specified - ok; -handle_trusted_certs_db(#state{cert_db_ref = Ref, - cert_db = CertDb, - ssl_options = #ssl_options{cacertfile = undefined}}) -> - %% Certs provided as DER directly can not be shared - %% with other connections and it is safe to delete them when the connection ends. - ssl_pkix_db:remove_trusted_certs(Ref, CertDb); -handle_trusted_certs_db(#state{file_ref_db = undefined}) -> - %% Something went wrong early (typically cacertfile does not exist) so there is nothing to handle - ok; -handle_trusted_certs_db(#state{cert_db_ref = Ref, - file_ref_db = RefDb, - ssl_options = #ssl_options{cacertfile = File}}) -> - case ssl_pkix_db:ref_count(Ref, RefDb, -1) of - 0 -> - ssl_manager:clean_cert_db(Ref, File); +handle_close_alert(Data, StateName, State0) -> + case next_tls_record(Data, State0) of + {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> + [Alert|_] = decode_alerts(EncAlerts), + handle_normal_shutdown(Alert, StateName, State); _ -> ok end. -get_current_connection_state_prf(CStates, Direction) -> - CS = tls_record:current_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. -get_pending_connection_state_prf(CStates, Direction) -> - CS = tls_record:pending_connection_state(CStates, Direction), - CS#connection_state.security_parameters#security_parameters.prf_algorithm. - -connection_hashsign(HashSign = {_, _}, _State) -> - HashSign; -connection_hashsign(_, #state{hashsign_algorithm = HashSign}) -> - HashSign. - -%% RFC 5246, Sect. 7.4.1.4.1. Signature Algorithms -%% If the client does not send the signature_algorithms extension, the -%% server MUST do the following: -%% -%% - If the negotiated key exchange algorithm is one of (RSA, DHE_RSA, -%% DH_RSA, RSA_PSK, ECDH_RSA, ECDHE_RSA), behave as if client had -%% sent the value {sha1,rsa}. -%% -%% - If the negotiated key exchange algorithm is one of (DHE_DSS, -%% DH_DSS), behave as if the client had sent the value {sha1,dsa}. -%% -%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA, -%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}. - -default_hashsign(_Version = {Major, Minor}, KeyExchange) - when Major == 3 andalso Minor >= 3 andalso - (KeyExchange == rsa orelse - KeyExchange == dhe_rsa orelse - KeyExchange == dh_rsa orelse - KeyExchange == ecdhe_rsa orelse - KeyExchange == srp_rsa) -> - {sha, rsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == rsa; - KeyExchange == dhe_rsa; - KeyExchange == dh_rsa; - KeyExchange == ecdhe_rsa; - KeyExchange == srp_rsa -> - {md5sha, rsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == ecdhe_ecdsa; - KeyExchange == ecdh_ecdsa; - KeyExchange == ecdh_rsa -> - {sha, ecdsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == dhe_dss; - KeyExchange == dh_dss; - KeyExchange == srp_dss -> - {sha, dsa}; -default_hashsign(_Version, KeyExchange) - when KeyExchange == dh_anon; - KeyExchange == ecdh_anon; - KeyExchange == psk; - KeyExchange == dhe_psk; - KeyExchange == rsa_psk; - KeyExchange == srp_anon -> - {null, anon}. - -start_or_recv_cancel_timer(infinity, _RecvFrom) -> - undefined; -start_or_recv_cancel_timer(Timeout, RecvFrom) -> - erlang:send_after(Timeout, self(), {cancel_start_or_recv, RecvFrom}). - cancel_timer(undefined) -> ok; cancel_timer(Timer) -> erlang:cancel_timer(Timer), ok. -handle_unrecv_data(StateName, #state{socket = Socket, transport_cb = Transport} = State) -> - ssl_socket:setopts(Transport, Socket, [{active, false}]), - case Transport:recv(Socket, 0, 0) of - {error, closed} -> - ok; - {ok, Data} -> - handle_close_alert(Data, StateName, State) - end. - -handle_close_alert(Data, StateName, State0) -> - case next_tls_record(Data, State0) of - {#ssl_tls{type = ?ALERT, fragment = EncAlerts}, State} -> - [Alert|_] = decode_alerts(EncAlerts), - handle_normal_shutdown(Alert, StateName, State); - _ -> - ok - end. +invalidate_session(client, Host, Port, Session) -> + ssl_manager:invalidate_session(Host, Port, Session); +invalidate_session(server, _, Port, Session) -> + ssl_manager:invalidate_session(Port, Session). -select_curve(#state{client_ecc = {[Curve|_], _}}) -> - {namedCurve, Curve}; -select_curve(_) -> - {namedCurve, ?secp256k1}. +%% User closes or recursive call! +close({close, Timeout}, Socket, Transport = gen_tcp, _,_) -> + ssl_socket:setopts(Transport, Socket, [{active, false}]), + Transport:shutdown(Socket, write), + _ = Transport:recv(Socket, 0, Timeout), + ok; +%% Peer closed socket +close({shutdown, transport_closed}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + close({close, 0}, Socket, Transport, ConnectionStates, Check); +%% We generate fatal alert +close({shutdown, own_alert}, Socket, Transport = gen_tcp, ConnectionStates, Check) -> + %% Standard trick to try to make sure all + %% data sent to the tcp port is really delivered to the + %% peer application before tcp port is closed so that the peer will + %% get the correct TLS alert message and not only a transport close. + %% Will return when other side has closed or after timout millisec + %% e.g. we do not want to hang if something goes wrong + %% with the network but we want to maximise the odds that + %% peer application gets all data sent on the tcp connection. + close({close, ?DEFAULT_TIMEOUT}, Socket, Transport, ConnectionStates, Check); +close(downgrade, _,_,_,_) -> + ok; +%% Other +close(_, Socket, Transport, _,_) -> + Transport:close(Socket). + +convert_state(#state{ssl_options = Options} = State, up, "5.3.5", "5.3.6") -> + State#state{ssl_options = convert_options_partial_chain(Options, up)}; +convert_state(#state{ssl_options = Options} = State, down, "5.3.6", "5.3.5") -> + State#state{ssl_options = convert_options_partial_chain(Options, down)}. + +convert_options_partial_chain(Options, up) -> + {Head, Tail} = lists:split(5, tuple_to_list(Options)), + list_to_tuple(Head ++ [{partial_chain, fun(_) -> unknown_ca end}] ++ Tail); +convert_options_partial_chain(Options, down) -> + list_to_tuple(proplists:delete(partial_chain, tuple_to_list(Options))). + +handle_sni_extension(#client_hello{extensions = HelloExtensions}, State0) -> + case HelloExtensions#hello_extensions.sni of + undefined -> + State0; + #sni{hostname = Hostname} -> + NewOptions = update_ssl_options_from_sni(State0#state.ssl_options, Hostname), + case NewOptions of + undefined -> + State0; + _ -> + {ok, Ref, CertDbHandle, FileRefHandle, CacheHandle, CRLDbHandle, OwnCert, Key, DHParams} = + ssl_config:init(NewOptions, State0#state.role), + State0#state{ + session = State0#state.session#session{own_certificate = OwnCert}, + file_ref_db = FileRefHandle, + cert_db_ref = Ref, + cert_db = CertDbHandle, + crl_db = CRLDbHandle, + session_cache = CacheHandle, + private_key = Key, + diffie_hellman_params = DHParams, + ssl_options = NewOptions, + sni_hostname = Hostname + } + end + end; +handle_sni_extension(_, State) -> + State. diff --git a/lib/ssl/src/tls_connection.hrl b/lib/ssl/src/tls_connection.hrl new file mode 100644 index 0000000000..0af2258932 --- /dev/null +++ b/lib/ssl/src/tls_connection.hrl @@ -0,0 +1,39 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: SSL/TLS specific state +%%---------------------------------------------------------------------- + +-ifndef(tls_connection). +-define(tls_connection, true). + +-include("ssl_connection.hrl"). +-include("tls_record.hrl"). + +-record(protocol_buffers, { + tls_packets = [], %% :: [#ssl_tls{}], % Not yet handled decode SSL/TLS packets. + tls_record_buffer = <<>>, %% :: binary(), % Buffer of incomplete records + tls_handshake_buffer = <<>>, %% :: binary(), % Buffer of incomplete handshakes + tls_cipher_texts = [] %%:: [binary()] + }). + +-endif. % -ifdef(tls_connection). diff --git a/lib/ssl/src/ssl_connection_sup.erl b/lib/ssl/src/tls_connection_sup.erl index fb1c6e11a6..d5b228dc94 100644 --- a/lib/ssl/src/ssl_connection_sup.erl +++ b/lib/ssl/src/tls_connection_sup.erl @@ -1,27 +1,28 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% %% %%---------------------------------------------------------------------- -%% Purpose: The top supervisor for the ftp hangs under inets_sup. +%% Purpose: Supervisor for a SSL/TLS connection %%---------------------------------------------------------------------- --module(ssl_connection_sup). +-module(tls_connection_sup). -behaviour(supervisor). @@ -59,7 +60,7 @@ init(_O) -> StartFunc = {tls_connection, start_link, []}, Restart = temporary, % E.g. should not be restarted Shutdown = 4000, - Modules = [tls_connection], + Modules = [tls_connection, ssl_connection], Type = worker, ChildSpec = {Name, StartFunc, Restart, Shutdown, Type, Modules}, diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl index 51fd2e1dc9..566b7db332 100644 --- a/lib/ssl/src/tls_handshake.erl +++ b/lib/ssl/src/tls_handshake.erl @@ -1,53 +1,41 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% %%---------------------------------------------------------------------- -%% Purpose: Help funtions for handling the SSL-handshake protocol +%% Purpose: Help funtions for handling the TLS (specific parts of) +%%% SSL/TLS/DTLS handshake protocol %%---------------------------------------------------------------------- -module(tls_handshake). -include("tls_handshake.hrl"). -include("tls_record.hrl"). --include("ssl_cipher.hrl"). -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). --include("ssl_srp.hrl"). +-include("ssl_cipher.hrl"). -include_lib("public_key/include/public_key.hrl"). --export([master_secret/4, client_hello/8, server_hello/7, hello/4, - hello_request/0, certify/7, certificate/4, - client_certificate_verify/6, certificate_verify/6, verify_signature/5, - certificate_request/3, key_exchange/3, server_key_exchange_hash/2, - finished/5, verify_connection/6, get_tls_handshake/3, - decode_client_key/3, decode_server_key/3, server_hello_done/0, - encode_handshake/2, init_handshake_history/0, update_handshake_history/2, - decrypt_premaster_secret/2, prf/5, next_protocol/1]). - --export([dec_hello_extensions/2]). - --type tls_handshake() :: #client_hello{} | #server_hello{} | - #server_hello_done{} | #certificate{} | #certificate_request{} | - #client_key_exchange{} | #finished{} | #certificate_verify{} | - #hello_request{} | #next_protocol{}. +-export([client_hello/8, hello/4, + get_tls_handshake/4, encode_handshake/2, decode_handshake/4]). --define(NAMED_CURVE_TYPE, 3). +-type tls_handshake() :: #client_hello{} | ssl_handshake:ssl_handshake(). %%==================================================================== %% Internal application API @@ -61,1767 +49,239 @@ %%-------------------------------------------------------------------- client_hello(Host, Port, ConnectionStates, #ssl_options{versions = Versions, - ciphers = UserSuites + ciphers = UserSuites, + fallback = Fallback } = SslOpts, Cache, CacheCb, Renegotiation, OwnCert) -> Version = tls_record:highest_protocol_version(Versions), - Pending = tls_record:pending_connection_state(ConnectionStates, read), + Pending = ssl_record:pending_connection_state(ConnectionStates, read), SecParams = Pending#connection_state.security_parameters, - Ciphers = available_suites(UserSuites, Version), - SRP = srp_user(SslOpts), - {EcPointFormats, EllipticCurves} = default_ecc_extensions(Version), - - Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), - + AvailableCipherSuites = ssl_handshake:available_suites(UserSuites, Version), + Extensions = ssl_handshake:client_hello_extensions(Host, Version, + AvailableCipherSuites, + SslOpts, ConnectionStates, Renegotiation), + CipherSuites = + case Fallback of + true -> + [?TLS_FALLBACK_SCSV | ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation)]; + false -> + ssl_handshake:cipher_suites(AvailableCipherSuites, Renegotiation) + end, + Id = ssl_session:client_id({Host, Port, SslOpts}, Cache, CacheCb, OwnCert), #client_hello{session_id = Id, client_version = Version, - cipher_suites = cipher_suites(Ciphers, Renegotiation), - compression_methods = tls_record:compressions(), + cipher_suites = CipherSuites, + compression_methods = ssl_record:compressions(), random = SecParams#security_parameters.client_random, - - renegotiation_info = - renegotiation_info(client, ConnectionStates, Renegotiation), - srp = SRP, - hash_signs = default_hash_signs(), - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = - encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, Renegotiation) - }. - -encode_protocol(Protocol, Acc) -> - Len = byte_size(Protocol), - <<Acc/binary, ?BYTE(Len), Protocol/binary>>. - -encode_protocols_advertised_on_server(undefined) -> - undefined; - -encode_protocols_advertised_on_server(Protocols) -> - #next_protocol_negotiation{extension_data = lists:foldl(fun encode_protocol/2, <<>>, Protocols)}. - -%%-------------------------------------------------------------------- --spec server_hello(session_id(), tls_version(), #connection_states{}, - boolean(), [binary()] | undefined, - #ec_point_formats{} | undefined, - #elliptic_curves{} | undefined) -> #server_hello{}. -%% -%% Description: Creates a server hello message. -%%-------------------------------------------------------------------- -server_hello(SessionId, Version, ConnectionStates, Renegotiation, - ProtocolsAdvertisedOnServer, EcPointFormats, EllipticCurves) -> - Pending = tls_record:pending_connection_state(ConnectionStates, read), - SecParams = Pending#connection_state.security_parameters, - #server_hello{server_version = Version, - cipher_suite = SecParams#security_parameters.cipher_suite, - compression_method = - SecParams#security_parameters.compression_algorithm, - random = SecParams#security_parameters.server_random, - session_id = SessionId, - renegotiation_info = - renegotiation_info(server, ConnectionStates, Renegotiation), - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = encode_protocols_advertised_on_server(ProtocolsAdvertisedOnServer) + extensions = Extensions }. %%-------------------------------------------------------------------- --spec hello_request() -> #hello_request{}. -%% -%% Description: Creates a hello request message sent by server to -%% trigger renegotiation. -%%-------------------------------------------------------------------- -hello_request() -> - #hello_request{}. - -%%-------------------------------------------------------------------- -spec hello(#server_hello{} | #client_hello{}, #ssl_options{}, #connection_states{} | {inet:port_number(), #session{}, db_handle(), - atom(), #connection_states{}, binary()}, + atom(), #connection_states{}, + binary() | undefined, ssl_cipher:key_algo()}, boolean()) -> - {tls_version(), session_id(), #connection_states{}, binary() | undefined}| - {tls_version(), {resumed | new, #session{}}, #connection_states{}, [binary()] | undefined, - [oid()] | undefined, [oid()] | undefined} | - #alert{}. + {tls_record:tls_version(), session_id(), + #connection_states{}, alpn | npn, binary() | undefined}| + {tls_record:tls_version(), {resumed | new, #session{}}, + #connection_states{}, binary() | undefined, + #hello_extensions{}, {ssl_cipher:hash(), ssl_cipher:sign_algo()} | undefined} | + #alert{}. %% %% Description: Handles a recieved hello message %%-------------------------------------------------------------------- -hello(#server_hello{cipher_suite = CipherSuite, server_version = Version, - compression_method = Compression, random = Random, - session_id = SessionId, renegotiation_info = Info, - hash_signs = _HashSigns} = Hello, - #ssl_options{secure_renegotiate = SecureRenegotation, next_protocol_selector = NextProtocolSelector, - versions = SupportedVersions}, +hello(#server_hello{server_version = Version, random = Random, + cipher_suite = CipherSuite, + compression_method = Compression, + session_id = SessionId, extensions = HelloExt}, + #ssl_options{versions = SupportedVersions} = SslOpt, ConnectionStates0, Renegotiation) -> - %%TODO: select hash and signature algorigthm case tls_record:is_acceptable_version(Version, SupportedVersions) of true -> - case handle_renegotiation_info(client, Info, ConnectionStates0, - Renegotiation, SecureRenegotation, []) of - {ok, ConnectionStates1} -> - ConnectionStates = - hello_pending_connection_states(client, Version, CipherSuite, Random, - Compression, ConnectionStates1), - case handle_next_protocol(Hello, NextProtocolSelector, Renegotiation) of - #alert{} = Alert -> - Alert; - Protocol -> - {Version, SessionId, ConnectionStates, Protocol} - end; - #alert{} = Alert -> - Alert - end; + handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation); false -> ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) end; -hello(#client_hello{client_version = ClientVersion} = Hello, +hello(#client_hello{client_version = ClientVersion, + cipher_suites = CipherSuites} = Hello, #ssl_options{versions = Versions} = SslOpts, - {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) -> - %% TODO: select hash and signature algorithm - Version = select_version(ClientVersion, Versions), - case tls_record:is_acceptable_version(Version, Versions) of - true -> - %% TODO: need to take supported Curves into Account when selecting the CipherSuite.... - %% if whe have an ECDSA cert with an unsupported curve, we need to drop ECDSA ciphers - {Type, #session{cipher_suite = CipherSuite} = Session1} - = select_session(Hello, Port, Session0, Version, - SslOpts, Cache, CacheCb, Cert), - case CipherSuite of - no_suite -> - ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY); - _ -> - try handle_hello_extensions(Hello, Version, SslOpts, Session1, ConnectionStates0, Renegotiation) of - {Session, ConnectionStates, ProtocolsToAdvertise, ECPointFormats, EllipticCurves} -> - {Version, {Type, Session}, ConnectionStates, - ProtocolsToAdvertise, ECPointFormats, EllipticCurves} - catch throw:Alert -> - Alert - end + Info, Renegotiation) -> + Version = ssl_handshake:select_version(tls_record, ClientVersion, Versions), + case ssl_cipher:is_fallback(CipherSuites) of + true -> + Highest = tls_record:highest_protocol_version(Versions), + case tls_record:is_higher(Highest, Version) of + true -> + ?ALERT_REC(?FATAL, ?INAPPROPRIATE_FALLBACK); + false -> + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) end; false -> - ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) - end. - -%%-------------------------------------------------------------------- --spec certify(#certificate{}, db_handle(), certdb_ref(), integer() | nolimit, - verify_peer | verify_none, {fun(), term}, - client | server) -> {der_cert(), public_key_info()} | #alert{}. -%% -%% Description: Handles a certificate handshake message -%%-------------------------------------------------------------------- -certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef, - MaxPathLen, _Verify, VerifyFunAndState, Role) -> - [PeerCert | _] = ASN1Certs, - - ValidationFunAndState = - case VerifyFunAndState of - undefined -> - {fun(OtpCert, ExtensionOrVerifyResult, SslState) -> - ssl_certificate:validate_extension(OtpCert, - ExtensionOrVerifyResult, SslState) - end, Role}; - {Fun, UserState0} -> - {fun(OtpCert, {extension, _} = Extension, {SslState, UserState}) -> - case ssl_certificate:validate_extension(OtpCert, - Extension, - SslState) of - {valid, NewSslState} -> - {valid, {NewSslState, UserState}}; - {fail, Reason} -> - apply_user_fun(Fun, OtpCert, Reason, UserState, - SslState); - {unknown, _} -> - apply_user_fun(Fun, OtpCert, - Extension, UserState, SslState) - end; - (OtpCert, VerifyResult, {SslState, UserState}) -> - apply_user_fun(Fun, OtpCert, VerifyResult, UserState, - SslState) - end, {Role, UserState0}} - end, - - try - {TrustedErlCert, CertPath} = - ssl_certificate:trusted_cert_and_path(ASN1Certs, CertDbHandle, CertDbRef), - case public_key:pkix_path_validation(TrustedErlCert, - CertPath, - [{max_path_length, - MaxPathLen}, - {verify_fun, ValidationFunAndState}]) of - {ok, {PublicKeyInfo,_}} -> - {PeerCert, PublicKeyInfo}; - {error, Reason} -> - path_validation_alert(Reason) - end - catch - error:_ -> - %% ASN-1 decode of certificate somehow failed - ?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN) - end. - -%%-------------------------------------------------------------------- --spec certificate(der_cert(), db_handle(), certdb_ref(), client | server) -> #certificate{} | #alert{}. -%% -%% Description: Creates a certificate message. -%%-------------------------------------------------------------------- -certificate(OwnCert, CertDbHandle, CertDbRef, client) -> - Chain = - case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of - {ok, CertChain} -> - CertChain; - {error, _} -> - %% If no suitable certificate is available, the client - %% SHOULD send a certificate message containing no - %% certificates. (chapter 7.4.6. RFC 4346) - [] - end, - #certificate{asn1_certificates = Chain}; - -certificate(OwnCert, CertDbHandle, CertDbRef, server) -> - case ssl_certificate:certificate_chain(OwnCert, CertDbHandle, CertDbRef) of - {ok, Chain} -> - #certificate{asn1_certificates = Chain}; - {error, _} -> - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR) - end. - -%%-------------------------------------------------------------------- --spec client_certificate_verify(undefined | der_cert(), binary(), - tls_version(), term(), private_key(), - tls_handshake_history()) -> - #certificate_verify{} | ignore | #alert{}. -%% -%% Description: Creates a certificate_verify message, called by the client. -%%-------------------------------------------------------------------- -client_certificate_verify(undefined, _, _, _, _, _) -> - ignore; -client_certificate_verify(_, _, _, _, undefined, _) -> - ignore; -client_certificate_verify(OwnCert, MasterSecret, Version, - {HashAlgo, SignAlgo}, - PrivateKey, {Handshake, _}) -> - case public_key:pkix_is_fixed_dh_cert(OwnCert) of - true -> - ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); - false -> - Hashes = - calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), - Signed = digitally_signed(Version, Hashes, HashAlgo, PrivateKey), - #certificate_verify{signature = Signed, hashsign_algorithm = {HashAlgo, SignAlgo}} - end. - -%%-------------------------------------------------------------------- --spec certificate_verify(binary(), public_key_info(), tls_version(), term(), - binary(), tls_handshake_history()) -> valid | #alert{}. -%% -%% Description: Checks that the certificate_verify message is valid. -%%-------------------------------------------------------------------- -certificate_verify(Signature, PublicKeyInfo, Version, - HashSign = {HashAlgo, _}, MasterSecret, {_, Handshake}) -> - Hash = calc_certificate_verify(Version, HashAlgo, MasterSecret, Handshake), - case verify_signature(Version, Hash, HashSign, Signature, PublicKeyInfo) of - true -> - valid; - _ -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE) - end. - -%%-------------------------------------------------------------------- --spec verify_signature(tls_version(), binary(), {term(), term()}, binary(), - public_key_info()) -> true | false. -%% -%% Description: Checks that a public_key signature is valid. -%%-------------------------------------------------------------------- -verify_signature(_Version, _Hash, {_HashAlgo, anon}, _Signature, _) -> - true; -verify_signature({3, Minor}, Hash, {HashAlgo, rsa}, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) - when Minor >= 3 -> - public_key:verify({digest, Hash}, HashAlgo, Signature, PubKey); -verify_signature(_Version, Hash, _HashAlgo, Signature, {?rsaEncryption, PubKey, _PubKeyParams}) -> - case public_key:decrypt_public(Signature, PubKey, - [{rsa_pad, rsa_pkcs1_padding}]) of - Hash -> true; - _ -> false - end; -verify_signature(_Version, Hash, {HashAlgo, dsa}, Signature, {?'id-dsa', PublicKey, PublicKeyParams}) -> - public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}); -verify_signature(_Version, Hash, {HashAlgo, ecdsa}, Signature, {?'id-ecPublicKey', PublicKey, PublicKeyParams}) -> - public_key:verify({digest, Hash}, HashAlgo, Signature, {PublicKey, PublicKeyParams}). - -%%-------------------------------------------------------------------- --spec certificate_request(#connection_states{}, db_handle(), certdb_ref()) -> - #certificate_request{}. -%% -%% Description: Creates a certificate_request message, called by the server. -%%-------------------------------------------------------------------- -certificate_request(ConnectionStates, CertDbHandle, CertDbRef) -> - #connection_state{security_parameters = - #security_parameters{cipher_suite = CipherSuite}} = - tls_record:pending_connection_state(ConnectionStates, read), - Types = certificate_types(CipherSuite), - HashSigns = default_hash_signs(), - Authorities = certificate_authorities(CertDbHandle, CertDbRef), - #certificate_request{ - certificate_types = Types, - hashsign_algorithms = HashSigns, - certificate_authorities = Authorities - }. - -%%-------------------------------------------------------------------- --spec key_exchange(client | server, tls_version(), - {premaster_secret, binary(), public_key_info()} | - {dh, binary()} | - {dh, {binary(), binary()}, #'DHParameter'{}, {HashAlgo::atom(), SignAlgo::atom()}, - binary(), binary(), private_key()} | - {ecdh, #'ECPrivateKey'{}} | - {psk, binary()} | - {dhe_psk, binary(), binary()} | - {srp, {binary(), binary()}, #srp_user{}, {HashAlgo::atom(), SignAlgo::atom()}, - binary(), binary(), private_key()}) -> - #client_key_exchange{} | #server_key_exchange{}. -%% -%% Description: Creates a keyexchange message. -%%-------------------------------------------------------------------- -key_exchange(client, _Version, {premaster_secret, Secret, {_, PublicKey, _}}) -> - EncPremasterSecret = - encrypted_premaster_secret(Secret, PublicKey), - #client_key_exchange{exchange_keys = EncPremasterSecret}; - -key_exchange(client, _Version, {dh, PublicKey}) -> - #client_key_exchange{ - exchange_keys = #client_diffie_hellman_public{ - dh_public = PublicKey} - }; - -key_exchange(client, _Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}}}) -> - #client_key_exchange{ - exchange_keys = #client_ec_diffie_hellman_public{ - dh_public = ECPublicKey} - }; - -key_exchange(client, _Version, {psk, Identity}) -> - #client_key_exchange{ - exchange_keys = #client_psk_identity{ - identity = Identity} - }; - -key_exchange(client, _Version, {dhe_psk, Identity, PublicKey}) -> - #client_key_exchange{ - exchange_keys = #client_dhe_psk_identity{ - identity = Identity, - dh_public = PublicKey} - }; - -key_exchange(client, _Version, {psk_premaster_secret, PskIdentity, Secret, {_, PublicKey, _}}) -> - EncPremasterSecret = - encrypted_premaster_secret(Secret, PublicKey), - #client_key_exchange{ - exchange_keys = #client_rsa_psk_identity{ - identity = PskIdentity, - exchange_keys = EncPremasterSecret}}; - -key_exchange(client, _Version, {srp, PublicKey}) -> - #client_key_exchange{ - exchange_keys = #client_srp_public{ - srp_a = PublicKey} - }; - -key_exchange(server, Version, {dh, {PublicKey, _}, - #'DHParameter'{prime = P, base = G}, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerDHParams = #server_dh_params{dh_p = int_to_bin(P), - dh_g = int_to_bin(G), dh_y = PublicKey}, - enc_server_key_exchange(Version, ServerDHParams, HashSign, - ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {ecdh, #'ECPrivateKey'{publicKey = {0, ECPublicKey}, - parameters = ECCurve}, HashSign, ClientRandom, ServerRandom, - PrivateKey}) -> - ServerECParams = #server_ecdh_params{curve = ECCurve, public = ECPublicKey}, - enc_server_key_exchange(Version, ServerECParams, HashSign, - ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {psk, PskIdentityHint, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerPSKParams = #server_psk_params{hint = PskIdentityHint}, - enc_server_key_exchange(Version, ServerPSKParams, HashSign, - ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {dhe_psk, PskIdentityHint, {PublicKey, _}, - #'DHParameter'{prime = P, base = G}, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerEDHPSKParams = #server_dhe_psk_params{ - hint = PskIdentityHint, - dh_params = #server_dh_params{dh_p = int_to_bin(P), - dh_g = int_to_bin(G), dh_y = PublicKey} - }, - enc_server_key_exchange(Version, ServerEDHPSKParams, - HashSign, ClientRandom, ServerRandom, PrivateKey); - -key_exchange(server, Version, {srp, {PublicKey, _}, - #srp_user{generator = Generator, prime = Prime, - salt = Salt}, - HashSign, ClientRandom, ServerRandom, PrivateKey}) -> - ServerSRPParams = #server_srp_params{srp_n = Prime, srp_g = Generator, - srp_s = Salt, srp_b = PublicKey}, - enc_server_key_exchange(Version, ServerSRPParams, HashSign, - ClientRandom, ServerRandom, PrivateKey). - -enc_server_key_exchange(Version, Params, {HashAlgo, SignAlgo}, - ClientRandom, ServerRandom, PrivateKey) -> - EncParams = enc_server_key(Params), - case HashAlgo of - null -> - #server_key_params{params = Params, - params_bin = EncParams, - hashsign = {null, anon}, - signature = <<>>}; - _ -> - Hash = - server_key_exchange_hash(HashAlgo, <<ClientRandom/binary, - ServerRandom/binary, - EncParams/binary>>), - Signature = digitally_signed(Version, Hash, HashAlgo, PrivateKey), - #server_key_params{params = Params, - params_bin = EncParams, - hashsign = {HashAlgo, SignAlgo}, - signature = Signature} - end. - -%%-------------------------------------------------------------------- --spec master_secret(tls_version(), #session{} | binary(), #connection_states{}, - client | server) -> {binary(), #connection_states{}} | #alert{}. -%% -%% Description: Sets or calculates the master secret and calculate keys, -%% updating the pending connection states. The Mastersecret and the update -%% connection states are returned or an alert if the calculation fails. -%%------------------------------------------------------------------- -master_secret(Version, #session{master_secret = Mastersecret}, - ConnectionStates, Role) -> - ConnectionState = - tls_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - try master_secret(Version, Mastersecret, SecParams, - ConnectionStates, Role) - catch - exit:Reason -> - Report = io_lib:format("Key calculation failed due to ~p", - [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end; - -master_secret(Version, PremasterSecret, ConnectionStates, Role) -> - ConnectionState = - tls_record:pending_connection_state(ConnectionStates, read), - SecParams = ConnectionState#connection_state.security_parameters, - #security_parameters{prf_algorithm = PrfAlgo, - client_random = ClientRandom, - server_random = ServerRandom} = SecParams, - try master_secret(Version, - calc_master_secret(Version,PrfAlgo,PremasterSecret, - ClientRandom, ServerRandom), - SecParams, ConnectionStates, Role) - catch - exit:Reason -> - Report = io_lib:format("Master secret calculation failed" - " due to ~p", [Reason]), - error_logger:error_report(Report), - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end. - --spec next_protocol(binary()) -> #next_protocol{}. - -next_protocol(SelectedProtocol) -> - #next_protocol{selected_protocol = SelectedProtocol}. - -%%-------------------------------------------------------------------- --spec finished(tls_version(), client | server, integer(), binary(), tls_handshake_history()) -> - #finished{}. -%% -%% Description: Creates a handshake finished message -%%------------------------------------------------------------------- -finished(Version, Role, PrfAlgo, MasterSecret, {Handshake, _}) -> % use the current handshake - #finished{verify_data = - calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake)}. - -%%-------------------------------------------------------------------- --spec verify_connection(tls_version(), #finished{}, client | server, integer(), binary(), - tls_handshake_history()) -> verified | #alert{}. -%% -%% Description: Checks the ssl handshake finished message to verify -%% the connection. -%%------------------------------------------------------------------- -verify_connection(Version, #finished{verify_data = Data}, - Role, PrfAlgo, MasterSecret, {_, Handshake}) -> - %% use the previous hashes - case calc_finished(Version, Role, PrfAlgo, MasterSecret, Handshake) of - Data -> - verified; - _ -> - ?ALERT_REC(?FATAL, ?DECRYPT_ERROR) + handle_client_hello(Version, Hello, SslOpts, Info, Renegotiation) end. %%-------------------------------------------------------------------- --spec server_hello_done() -> #server_hello_done{}. +-spec encode_handshake(tls_handshake(), tls_record:tls_version()) -> iolist(). %% -%% Description: Creates a server hello done message. -%%-------------------------------------------------------------------- -server_hello_done() -> - #server_hello_done{}. - -%%-------------------------------------------------------------------- --spec encode_handshake(tls_handshake(), tls_version()) -> iolist(). -%% -%% Description: Encode a handshake packet to binary +%% Description: Encode a handshake packet %%--------------------------------------------------------------------x encode_handshake(Package, Version) -> - {MsgType, Bin} = enc_hs(Package, Version), + {MsgType, Bin} = enc_handshake(Package, Version), Len = byte_size(Bin), [MsgType, ?uint24(Len), Bin]. %%-------------------------------------------------------------------- --spec get_tls_handshake(tls_version(), binary(), binary() | iolist()) -> +-spec get_tls_handshake(tls_record:tls_version(), binary(), binary() | iolist(), #ssl_options{}) -> {[tls_handshake()], binary()}. %% %% Description: Given buffered and new data from ssl_record, collects %% and returns it as a list of handshake messages, also returns leftover %% data. %%-------------------------------------------------------------------- -get_tls_handshake(Version, Data, <<>>) -> - get_tls_handshake_aux(Version, Data, []); -get_tls_handshake(Version, Data, Buffer) -> - get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), []). - -%%-------------------------------------------------------------------- --spec decode_client_key(binary(), key_algo(), tls_version()) -> - #encrypted_premaster_secret{} - | #client_diffie_hellman_public{} - | #client_ec_diffie_hellman_public{} - | #client_psk_identity{} - | #client_dhe_psk_identity{} - | #client_rsa_psk_identity{} - | #client_srp_public{}. -%% -%% Description: Decode client_key data and return appropriate type -%%-------------------------------------------------------------------- -decode_client_key(ClientKey, Type, Version) -> - dec_client_key(ClientKey, key_exchange_alg(Type), Version). - -%%-------------------------------------------------------------------- --spec decode_server_key(binary(), key_algo(), tls_version()) -> - #server_key_params{}. -%% -%% Description: Decode server_key data and return appropriate type -%%-------------------------------------------------------------------- -decode_server_key(ServerKey, Type, Version) -> - dec_server_key(ServerKey, key_exchange_alg(Type), Version). - -%%-------------------------------------------------------------------- --spec init_handshake_history() -> tls_handshake_history(). - -%% -%% Description: Initialize the empty handshake history buffer. -%%-------------------------------------------------------------------- -init_handshake_history() -> - {[], []}. - -%%-------------------------------------------------------------------- --spec update_handshake_history(tls_handshake_history(), Data ::term()) -> - tls_handshake_history(). -%% -%% Description: Update the handshake history buffer with Data. -%%-------------------------------------------------------------------- -update_handshake_history(Handshake, % special-case SSL2 client hello - <<?CLIENT_HELLO, ?UINT24(_), ?BYTE(Major), ?BYTE(Minor), - ?UINT16(CSLength), ?UINT16(0), - ?UINT16(CDLength), - CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>) -> - update_handshake_history(Handshake, - <<?CLIENT_HELLO, ?BYTE(Major), ?BYTE(Minor), - ?UINT16(CSLength), ?UINT16(0), - ?UINT16(CDLength), - CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>); -update_handshake_history({Handshake0, _Prev}, Data) -> - {[Data|Handshake0], Handshake0}. - -%%-------------------------------------------------------------------- --spec decrypt_premaster_secret(binary(), #'RSAPrivateKey'{}) -> binary(). - -%% -%% Description: Public key decryption using the private key. -%%-------------------------------------------------------------------- -decrypt_premaster_secret(Secret, RSAPrivateKey) -> - try public_key:decrypt_private(Secret, RSAPrivateKey, - [{rsa_pad, rsa_pkcs1_padding}]) - catch - _:_ -> - io:format("decrypt_premaster_secret error"), - throw(?ALERT_REC(?FATAL, ?DECRYPT_ERROR)) - end. - -%%-------------------------------------------------------------------- --spec server_key_exchange_hash(md5sha | md5 | sha | sha224 |sha256 | sha384 | sha512, binary()) -> binary(). -%% -%% Description: Calculate server key exchange hash -%%-------------------------------------------------------------------- -server_key_exchange_hash(md5sha, Value) -> - MD5 = crypto:hash(md5, Value), - SHA = crypto:hash(sha, Value), - <<MD5/binary, SHA/binary>>; - -server_key_exchange_hash(Hash, Value) -> - crypto:hash(Hash, Value). - -%%-------------------------------------------------------------------- --spec prf(tls_version(), binary(), binary(), [binary()], non_neg_integer()) -> - {ok, binary()} | {error, undefined}. -%% -%% Description: use the TLS PRF to generate key material -%%-------------------------------------------------------------------- -prf({3,0}, _, _, _, _) -> - {error, undefined}; -prf({3,1}, Secret, Label, Seed, WantedLength) -> - {ok, ssl_tls1:prf(?MD5SHA, Secret, Label, Seed, WantedLength)}; -prf({3,_N}, Secret, Label, Seed, WantedLength) -> - {ok, ssl_tls1:prf(?SHA256, Secret, Label, Seed, WantedLength)}. +get_tls_handshake(Version, Data, <<>>, Options) -> + get_tls_handshake_aux(Version, Data, Options, []); +get_tls_handshake(Version, Data, Buffer, Options) -> + get_tls_handshake_aux(Version, list_to_binary([Buffer, Data]), Options, []). %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), - Body:Length/binary,Rest/binary>>, Acc) -> - Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, - H = dec_hs(Version, Type, Body), - get_tls_handshake_aux(Version, Rest, [{H,Raw} | Acc]); -get_tls_handshake_aux(_Version, Data, Acc) -> - {lists:reverse(Acc), Data}. - -path_validation_alert({bad_cert, cert_expired}) -> - ?ALERT_REC(?FATAL, ?CERTIFICATE_EXPIRED); -path_validation_alert({bad_cert, invalid_issuer}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, invalid_signature}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, name_not_permitted}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, unknown_critical_extension}) -> - ?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE); -path_validation_alert({bad_cert, cert_revoked}) -> - ?ALERT_REC(?FATAL, ?CERTIFICATE_REVOKED); -path_validation_alert({bad_cert, selfsigned_peer}) -> - ?ALERT_REC(?FATAL, ?BAD_CERTIFICATE); -path_validation_alert({bad_cert, unknown_ca}) -> - ?ALERT_REC(?FATAL, ?UNKNOWN_CA); -path_validation_alert(_) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). - -select_session(Hello, Port, Session, Version, - #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) -> - SuggestedSessionId = Hello#client_hello.session_id, - {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId, - SslOpts, Cert, - Cache, CacheCb), - Suites = available_suites(Cert, UserSuites, Version), - case Resumed of - undefined -> - CipherSuite = select_cipher_suite(Hello#client_hello.cipher_suites, Suites), - Compressions = Hello#client_hello.compression_methods, - Compression = select_compression(Compressions), - {new, Session#session{session_id = SessionId, - cipher_suite = CipherSuite, - compression_method = Compression}}; - _ -> - {resumed, Resumed} - end. - -available_suites(UserSuites, Version) -> - case UserSuites of - [] -> - ssl_cipher:suites(Version); - _ -> - UserSuites - end. - -available_suites(ServerCert, UserSuites, Version) -> - ssl_cipher:filter(ServerCert, available_suites(UserSuites, Version)). - -cipher_suites(Suites, false) -> - [?TLS_EMPTY_RENEGOTIATION_INFO_SCSV | Suites]; -cipher_suites(Suites, true) -> - Suites. - -srp_user(#ssl_options{srp_identity = {UserName, _}}) -> - #srp{username = UserName}; -srp_user(_) -> - undefined. - -renegotiation_info(client, _, false) -> - #renegotiation_info{renegotiated_connection = undefined}; -renegotiation_info(server, ConnectionStates, false) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - #renegotiation_info{renegotiated_connection = ?byte(0)}; - false -> - #renegotiation_info{renegotiated_connection = undefined} - end; -renegotiation_info(client, ConnectionStates, true) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - Data = CS#connection_state.client_verify_data, - #renegotiation_info{renegotiated_connection = Data}; - false -> - #renegotiation_info{renegotiated_connection = undefined} - end; - -renegotiation_info(server, ConnectionStates, true) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - case CS#connection_state.secure_renegotiation of - true -> - CData = CS#connection_state.client_verify_data, - SData =CS#connection_state.server_verify_data, - #renegotiation_info{renegotiated_connection = <<CData/binary, SData/binary>>}; - false -> - #renegotiation_info{renegotiated_connection = undefined} - end. - -decode_next_protocols({next_protocol_negotiation, Protocols}) -> - decode_next_protocols(Protocols, []). -decode_next_protocols(<<>>, Acc) -> - lists:reverse(Acc); -decode_next_protocols(<<?BYTE(Len), Protocol:Len/binary, Rest/binary>>, Acc) -> - case Len of - 0 -> - {error, invalid_next_protocols}; - _ -> - decode_next_protocols(Rest, [Protocol|Acc]) - end; -decode_next_protocols(_Bytes, _Acc) -> - {error, invalid_next_protocols}. - -next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) -> - NextProtocolSelector =/= undefined andalso not Renegotiating. - -handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = undefined}, _Renegotiation, _SslOpts) -> - undefined; - -handle_next_protocol_on_server(#client_hello{next_protocol_negotiation = {next_protocol_negotiation, <<>>}}, - false, #ssl_options{next_protocols_advertised = Protocols}) -> - Protocols; - -handle_next_protocol_on_server(_Hello, _Renegotiation, _SSLOpts) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE). % unexpected next protocol extension - -handle_next_protocol(#server_hello{next_protocol_negotiation = undefined}, - _NextProtocolSelector, _Renegotiating) -> - undefined; - -handle_next_protocol(#server_hello{next_protocol_negotiation = Protocols}, - NextProtocolSelector, Renegotiating) -> - - case next_protocol_extension_allowed(NextProtocolSelector, Renegotiating) of - true -> - select_next_protocol(decode_next_protocols(Protocols), NextProtocolSelector); - false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) % unexpected next protocol extension - end. - -select_next_protocol({error, _Reason}, _NextProtocolSelector) -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); -select_next_protocol(Protocols, NextProtocolSelector) -> - case NextProtocolSelector(Protocols) of - ?NO_PROTOCOL -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - Protocol when is_binary(Protocol) -> - Protocol - end. - -default_ecc_extensions(Version) -> - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - case proplists:get_bool(ecdh, CryptoSupport) of - true -> - EcPointFormats = #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}, - EllipticCurves = #elliptic_curves{elliptic_curve_list = ssl_tls1:ecc_curves(Version)}, - {EcPointFormats, EllipticCurves}; - _ -> - {undefined, undefined} - end. - -handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0) -> - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - case proplists:get_bool(ecdh, CryptoSupport) of - true -> - EcPointFormats1 = handle_ecc_point_fmt_extension(EcPointFormats0), - EllipticCurves1 = handle_ecc_curves_extension(Version, EllipticCurves0), - {EcPointFormats1, EllipticCurves1}; - _ -> - {undefined, undefined} - end. - -handle_ecc_point_fmt_extension(undefined) -> - undefined; -handle_ecc_point_fmt_extension(_) -> - #ec_point_formats{ec_point_format_list = [?ECPOINT_UNCOMPRESSED]}. - -handle_ecc_curves_extension(_Version, undefined) -> - undefined; -handle_ecc_curves_extension(Version, _) -> - #elliptic_curves{elliptic_curve_list = ssl_tls1:ecc_curves(Version)}. - -handle_renegotiation_info(_, #renegotiation_info{renegotiated_connection = ?byte(0)}, - ConnectionStates, false, _, _) -> - {ok, tls_record:set_renegotiation_flag(true, ConnectionStates)}; - -handle_renegotiation_info(server, undefined, ConnectionStates, _, _, CipherSuites) -> - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - {ok, tls_record:set_renegotiation_flag(true, ConnectionStates)}; - false -> - {ok, tls_record:set_renegotiation_flag(false, ConnectionStates)} - end; - -handle_renegotiation_info(_, undefined, ConnectionStates, false, _, _) -> - {ok, tls_record:set_renegotiation_flag(false, ConnectionStates)}; - -handle_renegotiation_info(client, #renegotiation_info{renegotiated_connection = ClientServerVerify}, - ConnectionStates, true, _, _) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - CData = CS#connection_state.client_verify_data, - SData = CS#connection_state.server_verify_data, - case <<CData/binary, SData/binary>> == ClientServerVerify of +handle_client_hello(Version, #client_hello{session_id = SugesstedId, + cipher_suites = CipherSuites, + compression_methods = Compressions, + random = Random, + extensions = #hello_extensions{elliptic_curves = Curves, + signature_algs = ClientHashSigns} = HelloExt}, + #ssl_options{versions = Versions, + signature_algs = SupportedHashSigns} = SslOpts, + {Port, Session0, Cache, CacheCb, ConnectionStates0, Cert, _}, Renegotiation) -> + case tls_record:is_acceptable_version(Version, Versions) of true -> - {ok, ConnectionStates}; + AvailableHashSigns = ssl_handshake:available_signature_algs( + ClientHashSigns, SupportedHashSigns, Cert, Version), + ECCCurve = ssl_handshake:select_curve(Curves, ssl_handshake:supported_ecc(Version)), + {Type, #session{cipher_suite = CipherSuite} = Session1} + = ssl_handshake:select_session(SugesstedId, CipherSuites, AvailableHashSigns, Compressions, + Port, Session0#session{ecc = ECCCurve}, Version, + SslOpts, Cache, CacheCb, Cert), + case CipherSuite of + no_suite -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_ciphers); + _ -> + {KeyExAlg,_,_,_} = ssl_cipher:suite_definition(CipherSuite), + case ssl_handshake:select_hashsign(ClientHashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of + #alert{} = Alert -> + Alert; + HashSign -> + handle_client_hello_extensions(Version, Type, Random, CipherSuites, HelloExt, + SslOpts, Session1, ConnectionStates0, + Renegotiation, HashSign) + end + end; false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end; -handle_renegotiation_info(server, #renegotiation_info{renegotiated_connection = ClientVerify}, - ConnectionStates, true, _, CipherSuites) -> - - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - false -> - CS = tls_record:current_connection_state(ConnectionStates, read), - Data = CS#connection_state.client_verify_data, - case Data == ClientVerify of - true -> - {ok, ConnectionStates}; - false -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE) - end - end; - -handle_renegotiation_info(client, undefined, ConnectionStates, true, SecureRenegotation, _) -> - handle_renegotiation_info(ConnectionStates, SecureRenegotation); - -handle_renegotiation_info(server, undefined, ConnectionStates, true, SecureRenegotation, CipherSuites) -> - case is_member(?TLS_EMPTY_RENEGOTIATION_INFO_SCSV, CipherSuites) of - true -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - false -> - handle_renegotiation_info(ConnectionStates, SecureRenegotation) - end. - -handle_renegotiation_info(ConnectionStates, SecureRenegotation) -> - CS = tls_record:current_connection_state(ConnectionStates, read), - case {SecureRenegotation, CS#connection_state.secure_renegotiation} of - {_, true} -> - ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE); - {true, false} -> - ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION); - {false, false} -> - {ok, ConnectionStates} - end. - -%% Update pending connection states with parameters exchanged via -%% hello messages -%% NOTE : Role is the role of the receiver of the hello message -%% currently being processed. -hello_pending_connection_states(Role, Version, CipherSuite, Random, Compression, - ConnectionStates) -> - ReadState = - tls_record:pending_connection_state(ConnectionStates, read), - WriteState = - tls_record:pending_connection_state(ConnectionStates, write), - - NewReadSecParams = - hello_security_parameters(Role, Version, ReadState, CipherSuite, - Random, Compression), - - NewWriteSecParams = - hello_security_parameters(Role, Version, WriteState, CipherSuite, - Random, Compression), - - tls_record:update_security_params(NewReadSecParams, - NewWriteSecParams, - ConnectionStates). - -hello_security_parameters(client, Version, ConnectionState, CipherSuite, Random, - Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, - NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), - NewSecParams#security_parameters{ - server_random = Random, - compression_algorithm = Compression - }; - -hello_security_parameters(server, Version, ConnectionState, CipherSuite, Random, - Compression) -> - SecParams = ConnectionState#connection_state.security_parameters, - NewSecParams = ssl_cipher:security_parameters(Version, CipherSuite, SecParams), - NewSecParams#security_parameters{ - client_random = Random, - compression_algorithm = Compression - }. - -select_version(ClientVersion, Versions) -> - ServerVersion = tls_record:highest_protocol_version(Versions), - tls_record:lowest_protocol_version(ClientVersion, ServerVersion). - -select_cipher_suite([], _) -> - no_suite; -select_cipher_suite([Suite | ClientSuites], SupportedSuites) -> - case is_member(Suite, SupportedSuites) of - true -> - Suite; - false -> - select_cipher_suite(ClientSuites, SupportedSuites) + ?ALERT_REC(?FATAL, ?PROTOCOL_VERSION) end. -is_member(Suite, SupportedSuites) -> - lists:member(Suite, SupportedSuites). - -select_compression(_CompressionMetodes) -> - ?NULL. - -master_secret(Version, MasterSecret, #security_parameters{ - client_random = ClientRandom, - server_random = ServerRandom, - hash_size = HashSize, - prf_algorithm = PrfAlgo, - key_material_length = KML, - expanded_key_material_length = EKML, - iv_size = IVS}, - ConnectionStates, Role) -> - {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, - ServerWriteKey, ClientIV, ServerIV} = - setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, - ClientRandom, HashSize, KML, EKML, IVS), - - ConnStates1 = tls_record:set_master_secret(MasterSecret, ConnectionStates), - ConnStates2 = - tls_record:set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, - Role, ConnStates1), - - ClientCipherState = #cipher_state{iv = ClientIV, key = ClientWriteKey}, - ServerCipherState = #cipher_state{iv = ServerIV, key = ServerWriteKey}, - {MasterSecret, - tls_record:set_pending_cipher_state(ConnStates2, ClientCipherState, - ServerCipherState, Role)}. - - -dec_hs(_, ?NEXT_PROTOCOL, <<?BYTE(SelectedProtocolLength), SelectedProtocol:SelectedProtocolLength/binary, - ?BYTE(PaddingLength), _Padding:PaddingLength/binary>>) -> - #next_protocol{selected_protocol = SelectedProtocol}; +get_tls_handshake_aux(Version, <<?BYTE(Type), ?UINT24(Length), + Body:Length/binary,Rest/binary>>, #ssl_options{v2_hello_compatible = V2Hello} = Opts, Acc) -> + Raw = <<?BYTE(Type), ?UINT24(Length), Body/binary>>, + Handshake = decode_handshake(Version, Type, Body, V2Hello), + get_tls_handshake_aux(Version, Rest, Opts, [{Handshake,Raw} | Acc]); +get_tls_handshake_aux(_Version, Data, _, Acc) -> + {lists:reverse(Acc), Data}. -dec_hs(_, ?HELLO_REQUEST, <<>>) -> +decode_handshake(_, ?HELLO_REQUEST, <<>>, _) -> #hello_request{}; %% Client hello v2. %% The server must be able to receive such messages, from clients that %% are willing to use ssl v3 or higher, but have ssl v2 compatibility. -dec_hs(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), - ?UINT16(CSLength), ?UINT16(0), - ?UINT16(CDLength), - CipherSuites:CSLength/binary, - ChallengeData:CDLength/binary>>) -> +decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), + ?UINT16(CSLength), ?UINT16(0), + ?UINT16(CDLength), + CipherSuites:CSLength/binary, + ChallengeData:CDLength/binary>>, true) -> #client_hello{client_version = {Major, Minor}, - random = ssl_ssl2:client_random(ChallengeData, CDLength), + random = ssl_v2:client_random(ChallengeData, CDLength), session_id = 0, - cipher_suites = from_3bytes(CipherSuites), + cipher_suites = ssl_handshake:decode_suites('3_bytes', CipherSuites), compression_methods = [?NULL], - renegotiation_info = undefined + extensions = #hello_extensions{} }; -dec_hs(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, - ?BYTE(SID_length), Session_ID:SID_length/binary, - ?UINT16(Cs_length), CipherSuites:Cs_length/binary, - ?BYTE(Cm_length), Comp_methods:Cm_length/binary, - Extensions/binary>>) -> - - DecodedExtensions = dec_hello_extensions(Extensions), - RenegotiationInfo = proplists:get_value(renegotiation_info, DecodedExtensions, undefined), - SRP = proplists:get_value(srp, DecodedExtensions, undefined), - HashSigns = proplists:get_value(hash_signs, DecodedExtensions, undefined), - EllipticCurves = proplists:get_value(elliptic_curves, DecodedExtensions, - undefined), - NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, DecodedExtensions, undefined), +decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(_), ?BYTE(_), + ?UINT16(CSLength), ?UINT16(0), + ?UINT16(CDLength), + _CipherSuites:CSLength/binary, + _ChallengeData:CDLength/binary>>, false) -> + throw(?ALERT_REC(?FATAL, ?PROTOCOL_VERSION, ssl_v2_client_hello_no_supported)); +decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SID_length), Session_ID:SID_length/binary, + ?UINT16(Cs_length), CipherSuites:Cs_length/binary, + ?BYTE(Cm_length), Comp_methods:Cm_length/binary, + Extensions/binary>>, _) -> + + DecodedExtensions = ssl_handshake:decode_hello_extensions({client, Extensions}), #client_hello{ client_version = {Major,Minor}, random = Random, session_id = Session_ID, - cipher_suites = from_2bytes(CipherSuites), + cipher_suites = ssl_handshake:decode_suites('2_bytes', CipherSuites), compression_methods = Comp_methods, - renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation + extensions = DecodedExtensions }; -dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, - ?BYTE(SID_length), Session_ID:SID_length/binary, - Cipher_suite:2/binary, ?BYTE(Comp_method)>>) -> - #server_hello{ - server_version = {Major,Minor}, - random = Random, - session_id = Session_ID, - cipher_suite = Cipher_suite, - compression_method = Comp_method, - renegotiation_info = undefined, - hash_signs = undefined, - elliptic_curves = undefined}; - -dec_hs(_Version, ?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, - ?BYTE(SID_length), Session_ID:SID_length/binary, - Cipher_suite:2/binary, ?BYTE(Comp_method), - ?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> - - HelloExtensions = dec_hello_extensions(Extensions, []), - RenegotiationInfo = proplists:get_value(renegotiation_info, HelloExtensions, - undefined), - HashSigns = proplists:get_value(hash_signs, HelloExtensions, - undefined), - EllipticCurves = proplists:get_value(elliptic_curves, HelloExtensions, - undefined), - NextProtocolNegotiation = proplists:get_value(next_protocol_negotiation, HelloExtensions, undefined), - - #server_hello{ - server_version = {Major,Minor}, - random = Random, - session_id = Session_ID, - cipher_suite = Cipher_suite, - compression_method = Comp_method, - renegotiation_info = RenegotiationInfo, - hash_signs = HashSigns, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}; -dec_hs(_Version, ?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>) -> - #certificate{asn1_certificates = certs_to_list(ASN1Certs)}; -dec_hs(_Version, ?SERVER_KEY_EXCHANGE, Keys) -> - #server_key_exchange{exchange_keys = Keys}; -dec_hs({Major, Minor}, ?CERTIFICATE_REQUEST, - <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, - ?UINT16(HashSignsLen), HashSigns:HashSignsLen/binary, - ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) - when Major == 3, Minor >= 3 -> - HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || - <<?BYTE(Hash), ?BYTE(Sign)>> <= HashSigns], - #certificate_request{certificate_types = CertTypes, - hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, - certificate_authorities = CertAuths}; -dec_hs(_Version, ?CERTIFICATE_REQUEST, - <<?BYTE(CertTypesLen), CertTypes:CertTypesLen/binary, - ?UINT16(CertAuthsLen), CertAuths:CertAuthsLen/binary>>) -> - #certificate_request{certificate_types = CertTypes, - certificate_authorities = CertAuths}; -dec_hs(_Version, ?SERVER_HELLO_DONE, <<>>) -> - #server_hello_done{}; -dec_hs({Major, Minor}, ?CERTIFICATE_VERIFY,<<HashSign:2/binary, ?UINT16(SignLen), Signature:SignLen/binary>>) - when Major == 3, Minor >= 3 -> - #certificate_verify{hashsign_algorithm = hashsign_dec(HashSign), signature = Signature}; -dec_hs(_Version, ?CERTIFICATE_VERIFY,<<?UINT16(SignLen), Signature:SignLen/binary>>)-> - #certificate_verify{signature = Signature}; -dec_hs(_Version, ?CLIENT_KEY_EXCHANGE, PKEPMS) -> - #client_key_exchange{exchange_keys = PKEPMS}; -dec_hs(_Version, ?FINISHED, VerifyData) -> - #finished{verify_data = VerifyData}; -dec_hs(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). - -dec_client_key(PKEPMS, ?KEY_EXCHANGE_RSA, {3, 0}) -> - #encrypted_premaster_secret{premaster_secret = PKEPMS}; -dec_client_key(<<?UINT16(_), PKEPMS/binary>>, ?KEY_EXCHANGE_RSA, _) -> - #encrypted_premaster_secret{premaster_secret = PKEPMS}; -dec_client_key(<<>>, ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> - throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); -dec_client_key(<<?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>, - ?KEY_EXCHANGE_DIFFIE_HELLMAN, _) -> - #client_diffie_hellman_public{dh_public = DH_Y}; -dec_client_key(<<>>, ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> - throw(?ALERT_REC(?FATAL, ?UNSUPPORTED_CERTIFICATE)); -dec_client_key(<<?BYTE(DH_YLen), DH_Y:DH_YLen/binary>>, - ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, _) -> - #client_ec_diffie_hellman_public{dh_public = DH_Y}; -dec_client_key(<<?UINT16(Len), Id:Len/binary>>, - ?KEY_EXCHANGE_PSK, _) -> - #client_psk_identity{identity = Id}; -dec_client_key(<<?UINT16(Len), Id:Len/binary, - ?UINT16(DH_YLen), DH_Y:DH_YLen/binary>>, - ?KEY_EXCHANGE_DHE_PSK, _) -> - #client_dhe_psk_identity{identity = Id, dh_public = DH_Y}; -dec_client_key(<<?UINT16(Len), Id:Len/binary, PKEPMS/binary>>, - ?KEY_EXCHANGE_RSA_PSK, {3, 0}) -> - #client_rsa_psk_identity{identity = Id, exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; -dec_client_key(<<?UINT16(Len), Id:Len/binary, ?UINT16(_), PKEPMS/binary>>, - ?KEY_EXCHANGE_RSA_PSK, _) -> - #client_rsa_psk_identity{identity = Id, exchange_keys = #encrypted_premaster_secret{premaster_secret = PKEPMS}}; -dec_client_key(<<?UINT16(ALen), A:ALen/binary>>, - ?KEY_EXCHANGE_SRP, _) -> - #client_srp_public{srp_a = A}. - -dec_ske_params(Len, Keys, Version) -> - <<Params:Len/bytes, Signature/binary>> = Keys, - dec_ske_signature(Params, Signature, Version). - -dec_ske_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo), - ?UINT16(0)>>, {Major, Minor}) - when Major == 3, Minor >= 3 -> - HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, - {Params, HashSign, <<>>}; -dec_ske_signature(Params, <<?BYTE(HashAlgo), ?BYTE(SignAlgo), - ?UINT16(Len), Signature:Len/binary>>, {Major, Minor}) - when Major == 3, Minor >= 3 -> - HashSign = {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}, - {Params, HashSign, Signature}; -dec_ske_signature(Params, <<>>, _) -> - {Params, {null, anon}, <<>>}; -dec_ske_signature(Params, <<?UINT16(0)>>, _) -> - {Params, {null, anon}, <<>>}; -dec_ske_signature(Params, <<?UINT16(Len), Signature:Len/binary>>, _) -> - {Params, undefined, Signature}; -dec_ske_signature(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). - -dec_server_key(<<?UINT16(PLen), P:PLen/binary, - ?UINT16(GLen), G:GLen/binary, - ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, - ?KEY_EXCHANGE_DIFFIE_HELLMAN, Version) -> - Params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, - {BinMsg, HashSign, Signature} = dec_ske_params(PLen + GLen + YLen + 6, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -%% ECParameters with named_curve -%% TODO: explicit curve -dec_server_key(<<?BYTE(?NAMED_CURVE), ?UINT16(CurveID), - ?BYTE(PointLen), ECPoint:PointLen/binary, - _/binary>> = KeyStruct, - ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN, Version) -> - Params = #server_ecdh_params{curve = {namedCurve, ssl_tls1:enum_to_oid(CurveID)}, - public = ECPoint}, - {BinMsg, HashSign, Signature} = dec_ske_params(PointLen + 4, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<<?UINT16(Len), PskIdentityHint:Len/binary>> = KeyStruct, - KeyExchange, Version) - when KeyExchange == ?KEY_EXCHANGE_PSK; KeyExchange == ?KEY_EXCHANGE_RSA_PSK -> - Params = #server_psk_params{ - hint = PskIdentityHint}, - {BinMsg, HashSign, Signature} = dec_ske_params(Len + 2, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<<?UINT16(Len), IdentityHint:Len/binary, - ?UINT16(PLen), P:PLen/binary, - ?UINT16(GLen), G:GLen/binary, - ?UINT16(YLen), Y:YLen/binary, _/binary>> = KeyStruct, - ?KEY_EXCHANGE_DHE_PSK, Version) -> - DHParams = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}, - Params = #server_dhe_psk_params{ - hint = IdentityHint, - dh_params = DHParams}, - {BinMsg, HashSign, Signature} = dec_ske_params(Len + PLen + GLen + YLen + 8, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(<<?UINT16(NLen), N:NLen/binary, - ?UINT16(GLen), G:GLen/binary, - ?BYTE(SLen), S:SLen/binary, - ?UINT16(BLen), B:BLen/binary, _/binary>> = KeyStruct, - ?KEY_EXCHANGE_SRP, Version) -> - Params = #server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}, - {BinMsg, HashSign, Signature} = dec_ske_params(NLen + GLen + SLen + BLen + 7, KeyStruct, Version), - #server_key_params{params = Params, - params_bin = BinMsg, - hashsign = HashSign, - signature = Signature}; -dec_server_key(_, _, _) -> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)). - -dec_hello_extensions(<<>>) -> - []; -dec_hello_extensions(<<?UINT16(ExtLen), Extensions:ExtLen/binary>>) -> - dec_hello_extensions(Extensions, []); -dec_hello_extensions(_) -> - []. - -dec_hello_extensions(<<>>, Acc) -> - Acc; -dec_hello_extensions(<<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData:Len/binary, Rest/binary>>, Acc) -> - Prop = {next_protocol_negotiation, #next_protocol_negotiation{extension_data = ExtensionData}}, - dec_hello_extensions(Rest, [Prop | Acc]); -dec_hello_extensions(<<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info:Len/binary, Rest/binary>>, Acc) -> - RenegotiateInfo = case Len of - 1 -> % Initial handshake - Info; % should be <<0>> will be matched in handle_renegotiation_info - _ -> - VerifyLen = Len - 1, - <<?BYTE(VerifyLen), VerifyInfo/binary>> = Info, - VerifyInfo - end, - dec_hello_extensions(Rest, [{renegotiation_info, - #renegotiation_info{renegotiated_connection = RenegotiateInfo}} | Acc]); - -dec_hello_extensions(<<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), SRP:SRPLen/binary, Rest/binary>>, Acc) - when Len == SRPLen + 2 -> - dec_hello_extensions(Rest, [{srp, - #srp{username = SRP}} | Acc]); - -dec_hello_extensions(<<?UINT16(?SIGNATURE_ALGORITHMS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Acc) -> - SignAlgoListLen = Len - 2, - <<?UINT16(SignAlgoListLen), SignAlgoList/binary>> = ExtData, - HashSignAlgos = [{ssl_cipher:hash_algorithm(Hash), ssl_cipher:sign_algorithm(Sign)} || - <<?BYTE(Hash), ?BYTE(Sign)>> <= SignAlgoList], - dec_hello_extensions(Rest, [{hash_signs, - #hash_sign_algos{hash_sign_algos = HashSignAlgos}} | Acc]); - -dec_hello_extensions(<<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Acc) -> - EllipticCurveListLen = Len - 2, - <<?UINT16(EllipticCurveListLen), EllipticCurveList/binary>> = ExtData, - EllipticCurves = [ssl_tls1:enum_to_oid(X) || <<X:16>> <= EllipticCurveList], - dec_hello_extensions(Rest, [{elliptic_curves, - #elliptic_curves{elliptic_curve_list = EllipticCurves}} | Acc]); - -dec_hello_extensions(<<?UINT16(?EC_POINT_FORMATS_EXT), ?UINT16(Len), - ExtData:Len/binary, Rest/binary>>, Acc) -> - ECPointFormatListLen = Len - 1, - <<?BYTE(ECPointFormatListLen), ECPointFormatList/binary>> = ExtData, - ECPointFormats = binary_to_list(ECPointFormatList), - dec_hello_extensions(Rest, [{ec_point_formats, - #ec_point_formats{ec_point_format_list = ECPointFormats}} | Acc]); - -%% Ignore data following the ClientHello (i.e., -%% extensions) if not understood. - -dec_hello_extensions(<<?UINT16(_), ?UINT16(Len), _Unknown:Len/binary, Rest/binary>>, Acc) -> - dec_hello_extensions(Rest, Acc); -%% This theoretically should not happen if the protocol is followed, but if it does it is ignored. -dec_hello_extensions(_, Acc) -> - Acc. - -encrypted_premaster_secret(Secret, RSAPublicKey) -> - try - PreMasterSecret = public_key:encrypt_public(Secret, RSAPublicKey, - [{rsa_pad, - rsa_pkcs1_padding}]), - #encrypted_premaster_secret{premaster_secret = PreMasterSecret} - catch - _:_-> - throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)) - end. - -%% encode/decode stream of certificate data to/from list of certificate data -certs_to_list(ASN1Certs) -> - certs_to_list(ASN1Certs, []). - -certs_to_list(<<?UINT24(CertLen), Cert:CertLen/binary, Rest/binary>>, Acc) -> - certs_to_list(Rest, [Cert | Acc]); -certs_to_list(<<>>, Acc) -> - lists:reverse(Acc, []). - -certs_from_list(ACList) -> - list_to_binary([begin - CertLen = byte_size(Cert), - <<?UINT24(CertLen), Cert/binary>> - end || Cert <- ACList]). +decode_handshake(Version, Tag, Msg, _) -> + ssl_handshake:decode_handshake(Version, Tag, Msg). -enc_hs(#next_protocol{selected_protocol = SelectedProtocol}, _Version) -> - PaddingLength = 32 - ((byte_size(SelectedProtocol) + 2) rem 32), - - {?NEXT_PROTOCOL, <<?BYTE((byte_size(SelectedProtocol))), SelectedProtocol/binary, - ?BYTE(PaddingLength), 0:(PaddingLength * 8)>>}; -enc_hs(#hello_request{}, _Version) -> +enc_handshake(#hello_request{}, _Version) -> {?HELLO_REQUEST, <<>>}; -enc_hs(#client_hello{client_version = {Major, Minor}, +enc_handshake(#client_hello{client_version = {Major, Minor}, random = Random, session_id = SessionID, cipher_suites = CipherSuites, compression_methods = CompMethods, - renegotiation_info = RenegotiationInfo, - srp = SRP, - hash_signs = HashSigns, - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}, _Version) -> + extensions = HelloExtensions}, _Version) -> SIDLength = byte_size(SessionID), BinCompMethods = list_to_binary(CompMethods), CmLength = byte_size(BinCompMethods), BinCipherSuites = list_to_binary(CipherSuites), CsLength = byte_size(BinCipherSuites), - Extensions0 = hello_extensions(RenegotiationInfo, SRP, NextProtocolNegotiation) - ++ ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EcPointFormats) - ++ ec_hello_extensions(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites), EllipticCurves), - Extensions1 = if - Major == 3, Minor >=3 -> Extensions0 ++ hello_extensions(HashSigns); - true -> Extensions0 - end, - ExtensionsBin = enc_hello_extensions(Extensions1), - - {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, - ?BYTE(SIDLength), SessionID/binary, - ?UINT16(CsLength), BinCipherSuites/binary, - ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; - -enc_hs(#server_hello{server_version = {Major, Minor}, - random = Random, - session_id = Session_ID, - cipher_suite = CipherSuite, - compression_method = Comp_method, - renegotiation_info = RenegotiationInfo, - ec_point_formats = EcPointFormats, - elliptic_curves = EllipticCurves, - next_protocol_negotiation = NextProtocolNegotiation}, _Version) -> - SID_length = byte_size(Session_ID), - CipherSuites = [ssl_cipher:suite_definition(CipherSuite)], - Extensions = hello_extensions(RenegotiationInfo, NextProtocolNegotiation) - ++ ec_hello_extensions(CipherSuites, EcPointFormats) - ++ ec_hello_extensions(CipherSuites, EllipticCurves), - ExtensionsBin = enc_hello_extensions(Extensions), - {?SERVER_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, - ?BYTE(SID_length), Session_ID/binary, - CipherSuite/binary, ?BYTE(Comp_method), ExtensionsBin/binary>>}; -enc_hs(#certificate{asn1_certificates = ASN1CertList}, _Version) -> - ASN1Certs = certs_from_list(ASN1CertList), - ACLen = erlang:iolist_size(ASN1Certs), - {?CERTIFICATE, <<?UINT24(ACLen), ASN1Certs:ACLen/binary>>}; -enc_hs(#server_key_exchange{exchange_keys = Keys}, _Version) -> - {?SERVER_KEY_EXCHANGE, Keys}; -enc_hs(#server_key_params{params_bin = Keys, hashsign = HashSign, - signature = Signature}, Version) -> - EncSign = enc_sign(HashSign, Signature, Version), - {?SERVER_KEY_EXCHANGE, <<Keys/binary, EncSign/binary>>}; -enc_hs(#certificate_request{certificate_types = CertTypes, - hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSignAlgos}, - certificate_authorities = CertAuths}, - {Major, Minor}) - when Major == 3, Minor >= 3 -> - HashSigns= << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || - {Hash, Sign} <- HashSignAlgos >>, - CertTypesLen = byte_size(CertTypes), - HashSignsLen = byte_size(HashSigns), - CertAuthsLen = byte_size(CertAuths), - {?CERTIFICATE_REQUEST, - <<?BYTE(CertTypesLen), CertTypes/binary, - ?UINT16(HashSignsLen), HashSigns/binary, - ?UINT16(CertAuthsLen), CertAuths/binary>> - }; -enc_hs(#certificate_request{certificate_types = CertTypes, - certificate_authorities = CertAuths}, - _Version) -> - CertTypesLen = byte_size(CertTypes), - CertAuthsLen = byte_size(CertAuths), - {?CERTIFICATE_REQUEST, - <<?BYTE(CertTypesLen), CertTypes/binary, - ?UINT16(CertAuthsLen), CertAuths/binary>> - }; -enc_hs(#server_hello_done{}, _Version) -> - {?SERVER_HELLO_DONE, <<>>}; -enc_hs(#client_key_exchange{exchange_keys = ExchangeKeys}, Version) -> - {?CLIENT_KEY_EXCHANGE, enc_cke(ExchangeKeys, Version)}; -enc_hs(#certificate_verify{signature = BinSig, hashsign_algorithm = HashSign}, Version) -> - EncSig = enc_sign(HashSign, BinSig, Version), - {?CERTIFICATE_VERIFY, EncSig}; -enc_hs(#finished{verify_data = VerifyData}, _Version) -> - {?FINISHED, VerifyData}. - -enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS},{3, 0}) -> - PKEPMS; -enc_cke(#encrypted_premaster_secret{premaster_secret = PKEPMS}, _) -> - PKEPMSLen = byte_size(PKEPMS), - <<?UINT16(PKEPMSLen), PKEPMS/binary>>; -enc_cke(#client_diffie_hellman_public{dh_public = DHPublic}, _) -> - Len = byte_size(DHPublic), - <<?UINT16(Len), DHPublic/binary>>; -enc_cke(#client_ec_diffie_hellman_public{dh_public = DHPublic}, _) -> - Len = byte_size(DHPublic), - <<?BYTE(Len), DHPublic/binary>>; -enc_cke(#client_psk_identity{identity = undefined}, _) -> - Id = <<"psk_identity">>, - Len = byte_size(Id), - <<?UINT16(Len), Id/binary>>; -enc_cke(#client_psk_identity{identity = Id}, _) -> - Len = byte_size(Id), - <<?UINT16(Len), Id/binary>>; -enc_cke(Identity = #client_dhe_psk_identity{identity = undefined}, Version) -> - enc_cke(Identity#client_dhe_psk_identity{identity = <<"psk_identity">>}, Version); -enc_cke(#client_dhe_psk_identity{identity = Id, dh_public = DHPublic}, _) -> - Len = byte_size(Id), - DHLen = byte_size(DHPublic), - <<?UINT16(Len), Id/binary, ?UINT16(DHLen), DHPublic/binary>>; -enc_cke(Identity = #client_rsa_psk_identity{identity = undefined}, Version) -> - enc_cke(Identity#client_rsa_psk_identity{identity = <<"psk_identity">>}, Version); -enc_cke(#client_rsa_psk_identity{identity = Id, exchange_keys = ExchangeKeys}, Version) -> - EncPMS = enc_cke(ExchangeKeys, Version), - Len = byte_size(Id), - <<?UINT16(Len), Id/binary, EncPMS/binary>>; -enc_cke(#client_srp_public{srp_a = A}, _) -> - Len = byte_size(A), - <<?UINT16(Len), A/binary>>. - -enc_server_key(#server_dh_params{dh_p = P, dh_g = G, dh_y = Y}) -> - PLen = byte_size(P), - GLen = byte_size(G), - YLen = byte_size(Y), - <<?UINT16(PLen), P/binary, ?UINT16(GLen), G/binary, ?UINT16(YLen), Y/binary>>; -enc_server_key(#server_ecdh_params{curve = {namedCurve, ECCurve}, public = ECPubKey}) -> - %%TODO: support arbitrary keys - KLen = size(ECPubKey), - <<?BYTE(?NAMED_CURVE_TYPE), ?UINT16((ssl_tls1:oid_to_enum(ECCurve))), - ?BYTE(KLen), ECPubKey/binary>>; -enc_server_key(#server_psk_params{hint = PskIdentityHint}) -> - Len = byte_size(PskIdentityHint), - <<?UINT16(Len), PskIdentityHint/binary>>; -enc_server_key(Params = #server_dhe_psk_params{hint = undefined}) -> - enc_server_key(Params#server_dhe_psk_params{hint = <<>>}); -enc_server_key(#server_dhe_psk_params{ - hint = PskIdentityHint, - dh_params = #server_dh_params{dh_p = P, dh_g = G, dh_y = Y}}) -> - Len = byte_size(PskIdentityHint), - PLen = byte_size(P), - GLen = byte_size(G), - YLen = byte_size(Y), - <<?UINT16(Len), PskIdentityHint/binary, - ?UINT16(PLen), P/binary, ?UINT16(GLen), G/binary, ?UINT16(YLen), Y/binary>>; -enc_server_key(#server_srp_params{srp_n = N, srp_g = G, srp_s = S, srp_b = B}) -> - NLen = byte_size(N), - GLen = byte_size(G), - SLen = byte_size(S), - BLen = byte_size(B), - <<?UINT16(NLen), N/binary, ?UINT16(GLen), G/binary, - ?BYTE(SLen), S/binary, ?UINT16(BLen), B/binary>>. - -enc_sign({_, anon}, _Sign, _Version) -> - <<>>; -enc_sign({HashAlg, SignAlg}, Signature, _Version = {Major, Minor}) - when Major == 3, Minor >= 3-> - SignLen = byte_size(Signature), - HashSign = hashsign_enc(HashAlg, SignAlg), - <<HashSign/binary, ?UINT16(SignLen), Signature/binary>>; -enc_sign(_HashSign, Sign, _Version) -> - SignLen = byte_size(Sign), - <<?UINT16(SignLen), Sign/binary>>. - - -ec_hello_extensions(CipherSuites, #elliptic_curves{} = Info) -> - case advertises_ec_ciphers(CipherSuites) of - true -> - [Info]; - false -> - [] - end; -ec_hello_extensions(CipherSuites, #ec_point_formats{} = Info) -> - case advertises_ec_ciphers(CipherSuites) of - true -> - [Info]; - false -> - [] - end; -ec_hello_extensions(_, undefined) -> - []. + ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions), -hello_extensions(RenegotiationInfo, NextProtocolNegotiation) -> - hello_extensions(RenegotiationInfo) ++ next_protocol_extension(NextProtocolNegotiation). + {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary, + ?BYTE(SIDLength), SessionID/binary, + ?UINT16(CsLength), BinCipherSuites/binary, + ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>}; -hello_extensions(RenegotiationInfo, SRP, NextProtocolNegotiation) -> - hello_extensions(RenegotiationInfo) - ++ hello_extensions(SRP) - ++ next_protocol_extension(NextProtocolNegotiation). +enc_handshake(HandshakeMsg, Version) -> + ssl_handshake:encode_handshake(HandshakeMsg, Version). -advertises_ec_ciphers([]) -> - false; -advertises_ec_ciphers([{ecdh_ecdsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdhe_ecdsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdh_rsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdhe_rsa, _,_,_} | _]) -> - true; -advertises_ec_ciphers([{ecdh_anon, _,_,_} | _]) -> - true; -advertises_ec_ciphers([_| Rest]) -> - advertises_ec_ciphers(Rest). -%% Renegotiation info -hello_extensions(#renegotiation_info{renegotiated_connection = undefined}) -> - []; -hello_extensions(#renegotiation_info{} = Info) -> - [Info]; -hello_extensions(#srp{} = Info) -> - [Info]; -hello_extensions(#hash_sign_algos{} = Info) -> - [Info]; -hello_extensions(undefined) -> - []. - -next_protocol_extension(undefined) -> - []; -next_protocol_extension(#next_protocol_negotiation{} = Info) -> - [Info]. - -enc_hello_extensions(Extensions) -> - enc_hello_extensions(Extensions, <<>>). -enc_hello_extensions([], <<>>) -> - <<>>; -enc_hello_extensions([], Acc) -> - Size = byte_size(Acc), - <<?UINT16(Size), Acc/binary>>; - -enc_hello_extensions([#next_protocol_negotiation{extension_data = ExtensionData} | Rest], Acc) -> - Len = byte_size(ExtensionData), - enc_hello_extensions(Rest, <<?UINT16(?NEXTPROTONEG_EXT), ?UINT16(Len), ExtensionData/binary, Acc/binary>>); -enc_hello_extensions([#renegotiation_info{renegotiated_connection = ?byte(0) = Info} | Rest], Acc) -> - Len = byte_size(Info), - enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), Info/binary, Acc/binary>>); - -enc_hello_extensions([#renegotiation_info{renegotiated_connection = Info} | Rest], Acc) -> - InfoLen = byte_size(Info), - Len = InfoLen +1, - enc_hello_extensions(Rest, <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(Len), ?BYTE(InfoLen), Info/binary, Acc/binary>>); -enc_hello_extensions([#elliptic_curves{elliptic_curve_list = EllipticCurves} | Rest], Acc) -> - EllipticCurveList = << <<(ssl_tls1:oid_to_enum(X)):16>> || X <- EllipticCurves>>, - ListLen = byte_size(EllipticCurveList), - Len = ListLen + 2, - enc_hello_extensions(Rest, <<?UINT16(?ELLIPTIC_CURVES_EXT), - ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary, Acc/binary>>); -enc_hello_extensions([#ec_point_formats{ec_point_format_list = ECPointFormats} | Rest], Acc) -> - ECPointFormatList = list_to_binary(ECPointFormats), - ListLen = byte_size(ECPointFormatList), - Len = ListLen + 1, - enc_hello_extensions(Rest, <<?UINT16(?EC_POINT_FORMATS_EXT), - ?UINT16(Len), ?BYTE(ListLen), ECPointFormatList/binary, Acc/binary>>); -enc_hello_extensions([#srp{username = UserName} | Rest], Acc) -> - SRPLen = byte_size(UserName), - Len = SRPLen + 2, - enc_hello_extensions(Rest, <<?UINT16(?SRP_EXT), ?UINT16(Len), ?BYTE(SRPLen), UserName/binary, Acc/binary>>); -enc_hello_extensions([#hash_sign_algos{hash_sign_algos = HashSignAlgos} | Rest], Acc) -> - SignAlgoList = << <<(ssl_cipher:hash_algorithm(Hash)):8, (ssl_cipher:sign_algorithm(Sign)):8>> || - {Hash, Sign} <- HashSignAlgos >>, - ListLen = byte_size(SignAlgoList), - Len = ListLen + 2, - enc_hello_extensions(Rest, <<?UINT16(?SIGNATURE_ALGORITHMS_EXT), - ?UINT16(Len), ?UINT16(ListLen), SignAlgoList/binary, Acc/binary>>). - -encode_client_protocol_negotiation(undefined, _) -> - undefined; -encode_client_protocol_negotiation(_, false) -> - #next_protocol_negotiation{extension_data = <<>>}; -encode_client_protocol_negotiation(_, _) -> - undefined. - -from_3bytes(Bin3) -> - from_3bytes(Bin3, []). - -from_3bytes(<<>>, Acc) -> - lists:reverse(Acc); -from_3bytes(<<?UINT24(N), Rest/binary>>, Acc) -> - from_3bytes(Rest, [?uint16(N) | Acc]). - -from_2bytes(Bin2) -> - from_2bytes(Bin2, []). - -from_2bytes(<<>>, Acc) -> - lists:reverse(Acc); -from_2bytes(<<?UINT16(N), Rest/binary>>, Acc) -> - from_2bytes(Rest, [?uint16(N) | Acc]). - -certificate_types({KeyExchange, _, _, _}) - when KeyExchange == rsa; - KeyExchange == dhe_dss; - KeyExchange == dhe_rsa; - KeyExchange == ecdhe_rsa -> - <<?BYTE(?RSA_SIGN), ?BYTE(?DSS_SIGN)>>; - -certificate_types({KeyExchange, _, _, _}) - when KeyExchange == dh_ecdsa; - KeyExchange == dhe_ecdsa -> - <<?BYTE(?ECDSA_SIGN)>>; - -certificate_types(_) -> - <<?BYTE(?RSA_SIGN)>>. - -hashsign_dec(<<?BYTE(HashAlgo), ?BYTE(SignAlgo)>>) -> - {ssl_cipher:hash_algorithm(HashAlgo), ssl_cipher:sign_algorithm(SignAlgo)}. - -hashsign_enc(HashAlgo, SignAlgo) -> - Hash = ssl_cipher:hash_algorithm(HashAlgo), - Sign = ssl_cipher:sign_algorithm(SignAlgo), - <<?BYTE(Hash), ?BYTE(Sign)>>. - -certificate_authorities(CertDbHandle, CertDbRef) -> - Authorities = certificate_authorities_from_db(CertDbHandle, CertDbRef), - Enc = fun(#'OTPCertificate'{tbsCertificate=TBSCert}) -> - OTPSubj = TBSCert#'OTPTBSCertificate'.subject, - DNEncodedBin = public_key:pkix_encode('Name', OTPSubj, otp), - DNEncodedLen = byte_size(DNEncodedBin), - <<?UINT16(DNEncodedLen), DNEncodedBin/binary>> - end, - list_to_binary([Enc(Cert) || {_, Cert} <- Authorities]). - -certificate_authorities_from_db(CertDbHandle, CertDbRef) -> - ConnectionCerts = fun({{Ref, _, _}, Cert}, Acc) when Ref == CertDbRef -> - [Cert | Acc]; - (_, Acc) -> - Acc - end, - ssl_pkix_db:foldl(ConnectionCerts, [], CertDbHandle). - - -digitally_signed({3, Minor}, Hash, HashAlgo, Key) when Minor >= 3 -> - public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, HashAlgo, #'DSAPrivateKey'{} = Key) -> - public_key:sign({digest, Hash}, HashAlgo, Key); -digitally_signed(_Version, Hash, _HashAlgo, #'RSAPrivateKey'{} = Key) -> - public_key:encrypt_private(Hash, Key, - [{rsa_pad, rsa_pkcs1_padding}]); -digitally_signed(_Version, Hash, HashAlgo, Key) -> - public_key:sign({digest, Hash}, HashAlgo, Key). - -calc_master_secret({3,0}, _PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> - ssl_ssl3:master_secret(PremasterSecret, ClientRandom, ServerRandom); - -calc_master_secret({3,_}, PrfAlgo, PremasterSecret, ClientRandom, ServerRandom) -> - ssl_tls1:master_secret(PrfAlgo, PremasterSecret, ClientRandom, ServerRandom). - -setup_keys({3,0}, _PrfAlgo, MasterSecret, - ServerRandom, ClientRandom, HashSize, KML, EKML, IVS) -> - ssl_ssl3:setup_keys(MasterSecret, ServerRandom, - ClientRandom, HashSize, KML, EKML, IVS); - -setup_keys({3,N}, PrfAlgo, MasterSecret, - ServerRandom, ClientRandom, HashSize, KML, _EKML, IVS) -> - ssl_tls1:setup_keys(N, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, - KML, IVS). - -calc_finished({3, 0}, Role, _PrfAlgo, MasterSecret, Handshake) -> - ssl_ssl3:finished(Role, MasterSecret, lists:reverse(Handshake)); -calc_finished({3, N}, Role, PrfAlgo, MasterSecret, Handshake) -> - ssl_tls1:finished(Role, N, PrfAlgo, MasterSecret, lists:reverse(Handshake)). - -calc_certificate_verify({3, 0}, HashAlgo, MasterSecret, Handshake) -> - ssl_ssl3:certificate_verify(HashAlgo, MasterSecret, lists:reverse(Handshake)); -calc_certificate_verify({3, N}, HashAlgo, _MasterSecret, Handshake) -> - ssl_tls1:certificate_verify(HashAlgo, N, lists:reverse(Handshake)). - -key_exchange_alg(rsa) -> - ?KEY_EXCHANGE_RSA; -key_exchange_alg(Alg) when Alg == dhe_rsa; Alg == dhe_dss; - Alg == dh_dss; Alg == dh_rsa; Alg == dh_anon -> - ?KEY_EXCHANGE_DIFFIE_HELLMAN; -key_exchange_alg(Alg) when Alg == ecdhe_rsa; Alg == ecdh_rsa; - Alg == ecdhe_ecdsa; Alg == ecdh_ecdsa; - Alg == ecdh_anon -> - ?KEY_EXCHANGE_EC_DIFFIE_HELLMAN; -key_exchange_alg(psk) -> - ?KEY_EXCHANGE_PSK; -key_exchange_alg(dhe_psk) -> - ?KEY_EXCHANGE_DHE_PSK; -key_exchange_alg(rsa_psk) -> - ?KEY_EXCHANGE_RSA_PSK; -key_exchange_alg(Alg) - when Alg == srp_rsa; Alg == srp_dss; Alg == srp_anon -> - ?KEY_EXCHANGE_SRP; -key_exchange_alg(_) -> - ?NULL. - -apply_user_fun(Fun, OtpCert, ExtensionOrError, UserState0, SslState) -> - case Fun(OtpCert, ExtensionOrError, UserState0) of - {valid, UserState} -> - {valid, {SslState, UserState}}; - {fail, _} = Fail -> - Fail; - {unknown, UserState} -> - {unknown, {SslState, UserState}} - end. - --define(TLSEXT_SIGALG_RSA(MD), {MD, rsa}). --define(TLSEXT_SIGALG_DSA(MD), {MD, dsa}). --define(TLSEXT_SIGALG_ECDSA(MD), {MD, ecdsa}). - --define(TLSEXT_SIGALG(MD), ?TLSEXT_SIGALG_ECDSA(MD), ?TLSEXT_SIGALG_RSA(MD)). - -default_hash_signs() -> - HashSigns = [?TLSEXT_SIGALG(sha512), - ?TLSEXT_SIGALG(sha384), - ?TLSEXT_SIGALG(sha256), - ?TLSEXT_SIGALG(sha224), - ?TLSEXT_SIGALG(sha), - ?TLSEXT_SIGALG_DSA(sha), - ?TLSEXT_SIGALG_RSA(md5)], - CryptoSupport = proplists:get_value(public_keys, crypto:supports()), - HasECC = proplists:get_bool(ecdsa, CryptoSupport), - #hash_sign_algos{hash_sign_algos = - lists:filter(fun({_, ecdsa}) -> HasECC; - (_) -> true end, HashSigns)}. - -handle_hello_extensions(#client_hello{random = Random, - cipher_suites = CipherSuites, - renegotiation_info = Info, - srp = SRP, - ec_point_formats = EcPointFormats0, - elliptic_curves = EllipticCurves0} = Hello, Version, - #ssl_options{secure_renegotiate = SecureRenegotation} = Opts, - Session0, ConnectionStates0, Renegotiation) -> - Session = handle_srp_extension(SRP, Session0), - ConnectionStates = handle_renegotiation_extension(Version, Info, Random, Session, ConnectionStates0, - Renegotiation, SecureRenegotation, CipherSuites), - ProtocolsToAdvertise = handle_next_protocol_extension(Hello, Renegotiation, Opts), - {EcPointFormats, EllipticCurves} = handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0), - %%TODO make extensions compund data structure - {Session, ConnectionStates, ProtocolsToAdvertise, EcPointFormats, EllipticCurves}. - - -handle_renegotiation_extension(Version, Info, Random, #session{cipher_suite = CipherSuite, - compression_method = Compression}, - ConnectionStates0, Renegotiation, SecureRenegotation, CipherSuites) -> - case handle_renegotiation_info(server, Info, ConnectionStates0, - Renegotiation, SecureRenegotation, - CipherSuites) of - {ok, ConnectionStates1} -> - hello_pending_connection_states(server, - Version, - CipherSuite, - Random, - Compression, - ConnectionStates1); +handle_client_hello_extensions(Version, Type, Random, CipherSuites, + HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation, HashSign) -> + try ssl_handshake:handle_client_hello_extensions(tls_record, Random, CipherSuites, + HelloExt, Version, SslOpts, + Session0, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - throw(Alert) + Alert; + {Session, ConnectionStates, Protocol, ServerHelloExt} -> + {Version, {Type, Session}, ConnectionStates, Protocol, ServerHelloExt, HashSign} + catch throw:Alert -> + Alert end. -handle_next_protocol_extension(Hello, Renegotiation, SslOpts)-> - case handle_next_protocol_on_server(Hello, Renegotiation, SslOpts) of + +handle_server_hello_extensions(Version, SessionId, Random, CipherSuite, + Compression, HelloExt, SslOpt, ConnectionStates0, Renegotiation) -> + case ssl_handshake:handle_server_hello_extensions(tls_record, Random, CipherSuite, + Compression, HelloExt, Version, + SslOpt, ConnectionStates0, Renegotiation) of #alert{} = Alert -> - throw(Alert); - ProtocolsToAdvertise -> - ProtocolsToAdvertise + Alert; + {ConnectionStates, ProtoExt, Protocol} -> + {Version, SessionId, ConnectionStates, ProtoExt, Protocol} end. -handle_srp_extension(undefined, Session) -> - Session; -handle_srp_extension(#srp{username = Username}, Session) -> - Session#session{srp_username = Username}. - -int_to_bin(I) -> - L = (length(integer_to_list(I, 16)) + 1) div 2, - <<I:(L*8)>>. diff --git a/lib/ssl/src/tls_handshake.hrl b/lib/ssl/src/tls_handshake.hrl index abf1b5abb6..f6644f64af 100644 --- a/lib/ssl/src/tls_handshake.hrl +++ b/lib/ssl/src/tls_handshake.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -34,12 +35,7 @@ cipher_suites, % cipher_suites<2..2^16-1> compression_methods, % compression_methods<1..2^8-1>, %% Extensions - renegotiation_info, - hash_signs, % supported combinations of hashes/signature algos - next_protocol_negotiation = undefined, % [binary()] - srp, - ec_point_formats, - elliptic_curves + extensions }). -endif. % -ifdef(tls_handshake). diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl index 1409a04763..9348c8bbdd 100644 --- a/lib/ssl/src/tls_record.erl +++ b/lib/ssl/src/tls_record.erl @@ -1,26 +1,26 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% %% %%---------------------------------------------------------------------- -%% Purpose: Help functions for handling the SSL-Record protocol -%% +%% Purpose: Handle TLS/SSL record protocol. (Parts that are not shared with DTLS) %%---------------------------------------------------------------------- -module(tls_record). @@ -31,282 +31,33 @@ -include("tls_handshake.hrl"). -include("ssl_cipher.hrl"). -%% Connection state handling --export([init_connection_states/1, - current_connection_state/2, pending_connection_state/2, - update_security_params/3, - set_mac_secret/4, - set_master_secret/2, - activate_pending_connection_state/2, - set_pending_cipher_state/4, - set_renegotiation_flag/2, - set_client_verify_data/3, - set_server_verify_data/3]). - %% Handling of incoming data -export([get_tls_records/2]). -%% Encoding records --export([encode_handshake/3, encode_alert_record/3, - encode_change_cipher_spec/2, encode_data/3]). - %% Decoding --export([decode_cipher_text/2]). +-export([decode_cipher_text/3]). + +%% Encoding +-export([encode_plain_text/4]). -%% Misc. --export([protocol_version/1, lowest_protocol_version/2, - highest_protocol_version/1, supported_protocol_versions/0, +%% Protocol version handling +-export([protocol_version/1, lowest_protocol_version/1, lowest_protocol_version/2, + highest_protocol_version/1, highest_protocol_version/2, + is_higher/2, supported_protocol_versions/0, is_acceptable_version/1, is_acceptable_version/2]). --export([compressions/0]). +-export_type([tls_version/0, tls_atom_version/0]). --compile(inline). +-type tls_version() :: ssl_record:ssl_version(). +-type tls_atom_version() :: sslv3 | tlsv1 | 'tlsv1.1' | 'tlsv1.2'. --define(INITIAL_BYTES, 5). +-compile(inline). %%==================================================================== %% Internal application API %%==================================================================== %%-------------------------------------------------------------------- --spec init_connection_states(client | server) -> #connection_states{}. -%% -%% Description: Creates a connection_states record with appropriate -%% values for the initial SSL connection setup. -%%-------------------------------------------------------------------- -init_connection_states(Role) -> - ConnectionEnd = record_protocol_role(Role), - Current = initial_connection_state(ConnectionEnd), - Pending = empty_connection_state(ConnectionEnd), - #connection_states{current_read = Current, - pending_read = Pending, - current_write = Current, - pending_write = Pending - }. - -%%-------------------------------------------------------------------- --spec current_connection_state(#connection_states{}, read | write) -> - #connection_state{}. -%% -%% Description: Returns the instance of the connection_state record -%% that is currently defined as the current conection state. -%%-------------------------------------------------------------------- -current_connection_state(#connection_states{current_read = Current}, - read) -> - Current; -current_connection_state(#connection_states{current_write = Current}, - write) -> - Current. - -%%-------------------------------------------------------------------- --spec pending_connection_state(#connection_states{}, read | write) -> - #connection_state{}. -%% -%% Description: Returns the instance of the connection_state record -%% that is currently defined as the pending conection state. -%%-------------------------------------------------------------------- -pending_connection_state(#connection_states{pending_read = Pending}, - read) -> - Pending; -pending_connection_state(#connection_states{pending_write = Pending}, - write) -> - Pending. - -%%-------------------------------------------------------------------- --spec update_security_params(#security_parameters{}, #security_parameters{}, - #connection_states{}) -> #connection_states{}. -%% -%% Description: Creates a new instance of the connection_states record -%% where the pending states gets its security parameters updated. -%%-------------------------------------------------------------------- -update_security_params(ReadParams, WriteParams, States = - #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{pending_read = - Read#connection_state{security_parameters = - ReadParams}, - pending_write = - Write#connection_state{security_parameters = - WriteParams} - }. -%%-------------------------------------------------------------------- --spec set_mac_secret(binary(), binary(), client | server, - #connection_states{}) -> #connection_states{}. -%% -%% Description: update the mac_secret field in pending connection states -%%-------------------------------------------------------------------- -set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, client, States) -> - set_mac_secret(ServerWriteMacSecret, ClientWriteMacSecret, States); -set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, server, States) -> - set_mac_secret(ClientWriteMacSecret, ServerWriteMacSecret, States). - -set_mac_secret(ReadMacSecret, WriteMacSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - States#connection_states{ - pending_read = Read#connection_state{mac_secret = ReadMacSecret}, - pending_write = Write#connection_state{mac_secret = WriteMacSecret} - }. - - -%%-------------------------------------------------------------------- --spec set_master_secret(binary(), #connection_states{}) -> #connection_states{}. -%% -%% Description: Set master_secret in pending connection states -%%-------------------------------------------------------------------- -set_master_secret(MasterSecret, - States = #connection_states{pending_read = Read, - pending_write = Write}) -> - ReadSecPar = Read#connection_state.security_parameters, - Read1 = Read#connection_state{ - security_parameters = ReadSecPar#security_parameters{ - master_secret = MasterSecret}}, - WriteSecPar = Write#connection_state.security_parameters, - Write1 = Write#connection_state{ - security_parameters = WriteSecPar#security_parameters{ - master_secret = MasterSecret}}, - States#connection_states{pending_read = Read1, pending_write = Write1}. - -%%-------------------------------------------------------------------- --spec set_renegotiation_flag(boolean(), #connection_states{}) -> #connection_states{}. -%% -%% Description: Set secure_renegotiation in pending connection states -%%-------------------------------------------------------------------- -set_renegotiation_flag(Flag, #connection_states{ - current_read = CurrentRead0, - current_write = CurrentWrite0, - pending_read = PendingRead0, - pending_write = PendingWrite0} - = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{secure_renegotiation = Flag}, - CurrentWrite = CurrentWrite0#connection_state{secure_renegotiation = Flag}, - PendingRead = PendingRead0#connection_state{secure_renegotiation = Flag}, - PendingWrite = PendingWrite0#connection_state{secure_renegotiation = Flag}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite, - pending_read = PendingRead, - pending_write = PendingWrite}. - -%%-------------------------------------------------------------------- --spec set_client_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. -%% -%% Description: Set verify data in connection states. -%%-------------------------------------------------------------------- -set_client_verify_data(current_read, Data, - #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} - = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, - PendingWrite = PendingWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - pending_write = PendingWrite}; -set_client_verify_data(current_write, Data, - #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} - = ConnectionStates) -> - PendingRead = PendingRead0#connection_state{client_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{pending_read = PendingRead, - current_write = CurrentWrite}; -set_client_verify_data(current_both, Data, - #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} - = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{client_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{client_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite}. -%%-------------------------------------------------------------------- --spec set_server_verify_data(current_read | current_write | current_both, - binary(), #connection_states{})-> - #connection_states{}. -%% -%% Description: Set verify data in pending connection states. -%%-------------------------------------------------------------------- -set_server_verify_data(current_write, Data, - #connection_states{pending_read = PendingRead0, - current_write = CurrentWrite0} - = ConnectionStates) -> - PendingRead = PendingRead0#connection_state{server_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{pending_read = PendingRead, - current_write = CurrentWrite}; - -set_server_verify_data(current_read, Data, - #connection_states{current_read = CurrentRead0, - pending_write = PendingWrite0} - = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, - PendingWrite = PendingWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - pending_write = PendingWrite}; - -set_server_verify_data(current_both, Data, - #connection_states{current_read = CurrentRead0, - current_write = CurrentWrite0} - = ConnectionStates) -> - CurrentRead = CurrentRead0#connection_state{server_verify_data = Data}, - CurrentWrite = CurrentWrite0#connection_state{server_verify_data = Data}, - ConnectionStates#connection_states{current_read = CurrentRead, - current_write = CurrentWrite}. - -%%-------------------------------------------------------------------- --spec activate_pending_connection_state(#connection_states{}, read | write) -> - #connection_states{}. -%% -%% Description: Creates a new instance of the connection_states record -%% where the pending state of <Type> has been activated. -%%-------------------------------------------------------------------- -activate_pending_connection_state(States = - #connection_states{pending_read = Pending}, - read) -> - NewCurrent = Pending#connection_state{sequence_number = 0}, - SecParams = Pending#connection_state.security_parameters, - ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_read = NewCurrent, - pending_read = NewPending - }; - -activate_pending_connection_state(States = - #connection_states{pending_write = Pending}, - write) -> - NewCurrent = Pending#connection_state{sequence_number = 0}, - SecParams = Pending#connection_state.security_parameters, - ConnectionEnd = SecParams#security_parameters.connection_end, - EmptyPending = empty_connection_state(ConnectionEnd), - SecureRenegotation = NewCurrent#connection_state.secure_renegotiation, - NewPending = EmptyPending#connection_state{secure_renegotiation = SecureRenegotation}, - States#connection_states{current_write = NewCurrent, - pending_write = NewPending - }. - -%%-------------------------------------------------------------------- --spec set_pending_cipher_state(#connection_states{}, #cipher_state{}, - #cipher_state{}, client | server) -> - #connection_states{}. -%% -%% Description: Set the cipher state in the specified pending connection state. -%%-------------------------------------------------------------------- -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, - ClientState, ServerState, server) -> - States#connection_states{ - pending_read = Read#connection_state{cipher_state = ClientState}, - pending_write = Write#connection_state{cipher_state = ServerState}}; - -set_pending_cipher_state(#connection_states{pending_read = Read, - pending_write = Write} = States, - ClientState, ServerState, client) -> - States#connection_states{ - pending_read = Read#connection_state{cipher_state = ServerState}, - pending_write = Write#connection_state{cipher_state = ClientState}}. - -%%-------------------------------------------------------------------- -spec get_tls_records(binary(), binary()) -> {[binary()], binary()} | #alert{}. %% %% Description: Given old buffer and new data from TCP, packs up a records @@ -376,6 +127,97 @@ get_tls_records_aux(Data, Acc) -> false -> ?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE) end. + +encode_plain_text(Type, Version, Data, + #connection_states{current_write = + #connection_state{ + sequence_number = Seq, + compression_state=CompS0, + security_parameters= + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm=CompAlg} + }= WriteState0} = ConnectionStates) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + AAD = calc_aad(Type, Version, WriteState1), + {CipherFragment, WriteState} = ssl_record:cipher_aead(Version, Comp, WriteState1, AAD), + CipherText = encode_tls_cipher_text(Type, Version, CipherFragment), + {CipherText, ConnectionStates#connection_states{current_write = WriteState#connection_state{sequence_number = Seq +1}}}; + +encode_plain_text(Type, Version, Data, + #connection_states{current_write = + #connection_state{ + sequence_number = Seq, + compression_state=CompS0, + security_parameters= + #security_parameters{compression_algorithm=CompAlg} + }= WriteState0} = ConnectionStates) -> + {Comp, CompS1} = ssl_record:compress(CompAlg, Data, CompS0), + WriteState1 = WriteState0#connection_state{compression_state = CompS1}, + MacHash = calc_mac_hash(Type, Version, Comp, WriteState1), + {CipherFragment, WriteState} = ssl_record:cipher(Version, Comp, WriteState1, MacHash), + CipherText = encode_tls_cipher_text(Type, Version, CipherFragment), + {CipherText, ConnectionStates#connection_states{current_write = WriteState#connection_state{sequence_number = Seq +1}}}. + +%%-------------------------------------------------------------------- +-spec decode_cipher_text(#ssl_tls{}, #connection_states{}, boolean()) -> + {#ssl_tls{}, #connection_states{}}| #alert{}. +%% +%% Description: Decode cipher text +%%-------------------------------------------------------------------- +decode_cipher_text(#ssl_tls{type = Type, version = Version, + fragment = CipherFragment} = CipherText, + #connection_states{current_read = + #connection_state{ + compression_state = CompressionS0, + sequence_number = Seq, + security_parameters= + #security_parameters{ + cipher_type = ?AEAD, + compression_algorithm=CompAlg} + } = ReadState0} = ConnnectionStates0, _) -> + AAD = calc_aad(Type, Version, ReadState0), + case ssl_record:decipher_aead(Version, CipherFragment, ReadState0, AAD) of + {PlainFragment, ReadState1} -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + ConnnectionStates = ConnnectionStates0#connection_states{ + current_read = ReadState1#connection_state{ + sequence_number = Seq + 1, + compression_state = CompressionS1}}, + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + #alert{} = Alert -> + Alert + end; + +decode_cipher_text(#ssl_tls{type = Type, version = Version, + fragment = CipherFragment} = CipherText, + #connection_states{current_read = + #connection_state{ + compression_state = CompressionS0, + sequence_number = Seq, + security_parameters= + #security_parameters{compression_algorithm=CompAlg} + } = ReadState0} = ConnnectionStates0, PaddingCheck) -> + case ssl_record:decipher(Version, CipherFragment, ReadState0, PaddingCheck) of + {PlainFragment, Mac, ReadState1} -> + MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState1), + case ssl_record:is_correct_mac(Mac, MacHash) of + true -> + {Plain, CompressionS1} = ssl_record:uncompress(CompAlg, + PlainFragment, CompressionS0), + ConnnectionStates = ConnnectionStates0#connection_states{ + current_read = ReadState1#connection_state{ + sequence_number = Seq + 1, + compression_state = CompressionS1}}, + {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates}; + false -> + ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) + end; + #alert{} = Alert -> + Alert + end. %%-------------------------------------------------------------------- -spec protocol_version(tls_atom_version() | tls_version()) -> tls_version() | tls_atom_version(). @@ -416,6 +258,18 @@ lowest_protocol_version(Version = {M,_}, Version; lowest_protocol_version(_,Version) -> Version. + +%%-------------------------------------------------------------------- +-spec lowest_protocol_version([tls_version()]) -> tls_version(). +%% +%% Description: Lowest protocol version present in a list +%%-------------------------------------------------------------------- +lowest_protocol_version([]) -> + lowest_protocol_version(); +lowest_protocol_version(Versions) -> + [Ver | Vers] = Versions, + lowest_list_protocol_version(Ver, Vers). + %%-------------------------------------------------------------------- -spec highest_protocol_version([tls_version()]) -> tls_version(). %% @@ -425,18 +279,35 @@ highest_protocol_version([]) -> highest_protocol_version(); highest_protocol_version(Versions) -> [Ver | Vers] = Versions, - highest_protocol_version(Ver, Vers). + highest_list_protocol_version(Ver, Vers). -highest_protocol_version(Version, []) -> +%%-------------------------------------------------------------------- +-spec highest_protocol_version(tls_version(), tls_version()) -> tls_version(). +%% +%% Description: Highest protocol version of two given versions +%%-------------------------------------------------------------------- +highest_protocol_version(Version = {M, N}, {M, O}) when N > O -> Version; -highest_protocol_version(Version = {N, M}, [{N, O} | Rest]) when M > O -> - highest_protocol_version(Version, Rest); -highest_protocol_version({M, _}, [Version = {M, _} | Rest]) -> - highest_protocol_version(Version, Rest); -highest_protocol_version(Version = {M,_}, [{N,_} | Rest]) when M > N -> - highest_protocol_version(Version, Rest); -highest_protocol_version(_, [Version | Rest]) -> - highest_protocol_version(Version, Rest). +highest_protocol_version({M, _}, + Version = {M, _}) -> + Version; +highest_protocol_version(Version = {M,_}, + {N, _}) when M > N -> + Version; +highest_protocol_version(_,Version) -> + Version. + +%%-------------------------------------------------------------------- +-spec is_higher(V1 :: tls_version(), V2::tls_version()) -> boolean(). +%% +%% Description: Is V1 > V2 +%%-------------------------------------------------------------------- +is_higher({M, N}, {M, O}) when N > O -> + true; +is_higher({M, _}, {N, _}) when M > N -> + true; +is_higher(_, _) -> + false. %%-------------------------------------------------------------------- -spec supported_protocol_versions() -> [tls_version()]. @@ -471,21 +342,30 @@ supported_protocol_versions([]) -> Vsns; supported_protocol_versions([_|_] = Vsns) -> - Vsns. - + case sufficient_tlsv1_2_crypto_support() of + true -> + Vsns; + false -> + case Vsns -- ['tlsv1.2'] of + [] -> + ?MIN_SUPPORTED_VERSIONS; + NewVsns -> + NewVsns + end + end. %%-------------------------------------------------------------------- --spec is_acceptable_version(tls_version()) -> boolean(). --spec is_acceptable_version(tls_version(), Supported :: [tls_version()]) -> boolean(). %% %% Description: ssl version 2 is not acceptable security risks are too big. %% %%-------------------------------------------------------------------- +-spec is_acceptable_version(tls_version()) -> boolean(). is_acceptable_version({N,_}) when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION -> true; is_acceptable_version(_) -> false. +-spec is_acceptable_version(tls_version(), Supported :: [tls_version()]) -> boolean(). is_acceptable_version({N,_} = Version, Versions) when N >= ?LOWEST_MAJOR_SUPPORTED_VERSION -> lists:member(Version, Versions); @@ -493,224 +373,55 @@ is_acceptable_version(_,_) -> false. %%-------------------------------------------------------------------- --spec compressions() -> [binary()]. -%% -%% Description: return a list of compressions supported (currently none) -%%-------------------------------------------------------------------- -compressions() -> - [?byte(?NULL)]. - -%%-------------------------------------------------------------------- --spec decode_cipher_text(#ssl_tls{}, #connection_states{}) -> - {#ssl_tls{}, #connection_states{}}| #alert{}. -%% -%% Description: Decode cipher text -%%-------------------------------------------------------------------- -decode_cipher_text(CipherText, ConnnectionStates0) -> - ReadState0 = ConnnectionStates0#connection_states.current_read, - #connection_state{compression_state = CompressionS0, - security_parameters = SecParams} = ReadState0, - CompressAlg = SecParams#security_parameters.compression_algorithm, - case decipher(CipherText, ReadState0) of - {Compressed, ReadState1} -> - {Plain, CompressionS1} = uncompress(CompressAlg, - Compressed, CompressionS0), - ConnnectionStates = ConnnectionStates0#connection_states{ - current_read = ReadState1#connection_state{ - compression_state = CompressionS1}}, - {Plain, ConnnectionStates}; - #alert{} = Alert -> - Alert - end. -%%-------------------------------------------------------------------- --spec encode_data(binary(), tls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes data to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_data(Frag, Version, - #connection_states{current_write = #connection_state{ - security_parameters = - #security_parameters{bulk_cipher_algorithm = BCA}}} = - ConnectionStates) -> - Data = split_bin(Frag, ?MAX_PLAIN_TEXT_LENGTH, Version, BCA), - encode_iolist(?APPLICATION_DATA, Data, Version, ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_handshake(iolist(), tls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes a handshake message to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_handshake(Frag, Version, ConnectionStates) -> - encode_plain_text(?HANDSHAKE, Version, Frag, ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_alert_record(#alert{}, tls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes an alert message to send on the ssl-socket. -%%-------------------------------------------------------------------- -encode_alert_record(#alert{level = Level, description = Description}, - Version, ConnectionStates) -> - encode_plain_text(?ALERT, Version, <<?BYTE(Level), ?BYTE(Description)>>, - ConnectionStates). - -%%-------------------------------------------------------------------- --spec encode_change_cipher_spec(tls_version(), #connection_states{}) -> - {iolist(), #connection_states{}}. -%% -%% Description: Encodes a change_cipher_spec-message to send on the ssl socket. -%%-------------------------------------------------------------------- -encode_change_cipher_spec(Version, ConnectionStates) -> - encode_plain_text(?CHANGE_CIPHER_SPEC, Version, <<1:8>>, ConnectionStates). - -%%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -encode_iolist(Type, Data, Version, ConnectionStates0) -> - {ConnectionStates, EncodedMsg} = - lists:foldl(fun(Text, {CS0, Encoded}) -> - {Enc, CS1} = encode_plain_text(Type, Version, Text, CS0), - {CS1, [Enc | Encoded]} - end, {ConnectionStates0, []}, Data), - {lists:reverse(EncodedMsg), ConnectionStates}. - -highest_protocol_version() -> - highest_protocol_version(supported_protocol_versions()). -initial_connection_state(ConnectionEnd) -> - #connection_state{security_parameters = - initial_security_params(ConnectionEnd), - sequence_number = 0 - }. - -initial_security_params(ConnectionEnd) -> - SecParams = #security_parameters{connection_end = ConnectionEnd, - compression_algorithm = ?NULL}, - ssl_cipher:security_parameters(highest_protocol_version(), ?TLS_NULL_WITH_NULL_NULL, - SecParams). - -empty_connection_state(ConnectionEnd) -> - SecParams = empty_security_params(ConnectionEnd), - #connection_state{security_parameters = SecParams}. - -empty_security_params(ConnectionEnd = ?CLIENT) -> - #security_parameters{connection_end = ConnectionEnd, - client_random = random()}; -empty_security_params(ConnectionEnd = ?SERVER) -> - #security_parameters{connection_end = ConnectionEnd, - server_random = random()}. -random() -> - Secs_since_1970 = calendar:datetime_to_gregorian_seconds( - calendar:universal_time()) - 62167219200, - Random_28_bytes = crypto:rand_bytes(28), - <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. - -record_protocol_role(client) -> - ?CLIENT; -record_protocol_role(server) -> - ?SERVER. - -%% 1/n-1 splitting countermeasure Rizzo/Duong-Beast, RC4 chiphers are not vulnerable to this attack. -split_bin(<<FirstByte:8, Rest/binary>>, ChunkSize, Version, BCA) when BCA =/= ?RC4 andalso ({3, 1} == Version orelse - {3, 0} == Version) -> - do_split_bin(Rest, ChunkSize, [[FirstByte]]); -split_bin(Bin, ChunkSize, _, _) -> - do_split_bin(Bin, ChunkSize, []). - -do_split_bin(<<>>, _, Acc) -> - lists:reverse(Acc); -do_split_bin(Bin, ChunkSize, Acc) -> - case Bin of - <<Chunk:ChunkSize/binary, Rest/binary>> -> - do_split_bin(Rest, ChunkSize, [Chunk | Acc]); - _ -> - lists:reverse(Acc, [Bin]) - end. +lowest_list_protocol_version(Ver, []) -> + Ver; +lowest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + lowest_list_protocol_version(lowest_protocol_version(Ver1, Ver2), Rest). -encode_plain_text(Type, Version, Data, ConnectionStates) -> - #connection_states{current_write=#connection_state{ - compression_state=CompS0, - security_parameters= - #security_parameters{compression_algorithm=CompAlg} - }=CS0} = ConnectionStates, - {Comp, CompS1} = compress(CompAlg, Data, CompS0), - CS1 = CS0#connection_state{compression_state = CompS1}, - {CipherText, CS2} = cipher(Type, Version, Comp, CS1), - CTBin = encode_tls_cipher_text(Type, Version, CipherText), - {CTBin, ConnectionStates#connection_states{current_write = CS2}}. +highest_list_protocol_version(Ver, []) -> + Ver; +highest_list_protocol_version(Ver1, [Ver2 | Rest]) -> + highest_list_protocol_version(highest_protocol_version(Ver1, Ver2), Rest). encode_tls_cipher_text(Type, {MajVer, MinVer}, Fragment) -> Length = erlang:iolist_size(Fragment), [<<?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer), ?UINT16(Length)>>, Fragment]. -cipher(Type, Version, Fragment, CS0) -> - Length = erlang:iolist_size(Fragment), - {MacHash, CS1=#connection_state{cipher_state = CipherS0, - security_parameters= - #security_parameters{bulk_cipher_algorithm = - BCA} - }} = - hash_and_bump_seqno(CS0, Type, Version, Length, Fragment), - {Ciphered, CipherS1} = ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment, Version), - CS2 = CS1#connection_state{cipher_state=CipherS1}, - {Ciphered, CS2}. - -decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) -> - SP = CS0#connection_state.security_parameters, - BCA = SP#security_parameters.bulk_cipher_algorithm, - HashSz = SP#security_parameters.hash_size, - CipherS0 = CS0#connection_state.cipher_state, - case ssl_cipher:decipher(BCA, HashSz, CipherS0, Fragment, Version) of - {T, Mac, CipherS1} -> - CS1 = CS0#connection_state{cipher_state = CipherS1}, - TLength = size(T), - {MacHash, CS2} = hash_and_bump_seqno(CS1, Type, Version, TLength, T), - case is_correct_mac(Mac, MacHash) of - true -> - {TLS#ssl_tls{fragment = T}, CS2}; - false -> - ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC) - end; - #alert{} = Alert -> - Alert - end. - -uncompress(?NULL, Data = #ssl_tls{type = _Type, - version = _Version, - fragment = _Fragment}, CS) -> - {Data, CS}. - -compress(?NULL, Data, CS) -> - {Data, CS}. - -hash_and_bump_seqno(#connection_state{sequence_number = SeqNo, - mac_secret = MacSecret, - security_parameters = - SecPars} = CS0, - Type, Version, Length, Fragment) -> - Hash = mac_hash(Version, - SecPars#security_parameters.mac_algorithm, - MacSecret, SeqNo, Type, - Length, Fragment), - {Hash, CS0#connection_state{sequence_number = SeqNo+1}}. - -is_correct_mac(Mac, Mac) -> - true; -is_correct_mac(_M,_H) -> - false. mac_hash({_,_}, ?NULL, _MacSecret, _SeqNo, _Type, _Length, _Fragment) -> <<>>; mac_hash({3, 0}, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) -> - ssl_ssl3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment); + ssl_v3:mac_hash(MacAlg, MacSecret, SeqNo, Type, Length, Fragment); mac_hash({3, N} = Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) when N =:= 1; N =:= 2; N =:= 3 -> - ssl_tls1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, + tls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version, Length, Fragment). +highest_protocol_version() -> + highest_protocol_version(supported_protocol_versions()). + +lowest_protocol_version() -> + lowest_protocol_version(supported_protocol_versions()). + + sufficient_tlsv1_2_crypto_support() -> CryptoSupport = crypto:supports(), proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)). + +calc_mac_hash(Type, Version, + PlainFragment, #connection_state{sequence_number = SeqNo, + mac_secret = MacSecret, + security_parameters = + SecPars}) -> + Length = erlang:iolist_size(PlainFragment), + mac_hash(Version, SecPars#security_parameters.mac_algorithm, + MacSecret, SeqNo, Type, + Length, PlainFragment). + +calc_aad(Type, {MajVer, MinVer}, + #connection_state{sequence_number = SeqNo}) -> + <<SeqNo:64/integer, ?BYTE(Type), ?BYTE(MajVer), ?BYTE(MinVer)>>. diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl index c9350fa137..e296f23673 100644 --- a/lib/ssl/src/tls_record.hrl +++ b/lib/ssl/src/tls_record.hrl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2013. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,7 +30,6 @@ -include("ssl_record.hrl"). %% Common TLS and DTLS records and Constantes %% Used to handle tls_plain_text, tls_compressed and tls_cipher_text - -record(ssl_tls, { type, version, diff --git a/lib/ssl/src/ssl_tls1.erl b/lib/ssl/src/tls_v1.erl index 8ab66d0627..711db77708 100644 --- a/lib/ssl/src/ssl_tls1.erl +++ b/lib/ssl/src/tls_v1.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -22,7 +23,7 @@ %% Purpose: Handles tls1 encryption. %%---------------------------------------------------------------------- --module(ssl_tls1). +-module(tls_v1). -include("ssl_cipher.hrl"). -include("ssl_internal.hrl"). @@ -30,7 +31,8 @@ -export([master_secret/4, finished/5, certificate_verify/3, mac_hash/7, setup_keys/8, suites/1, prf/5, - ecc_curves/1, oid_to_enum/1, enum_to_oid/1]). + ecc_curves/1, oid_to_enum/1, enum_to_oid/1, + default_signature_algs/1, signature_algs/2]). %%==================================================================== %% Internal application API @@ -86,7 +88,7 @@ certificate_verify(HashAlgo, _Version, Handshake) -> -spec setup_keys(integer(), integer(), binary(), binary(), binary(), integer(), integer(), integer()) -> {binary(), binary(), binary(), - binary(), binary(), binary()}. + binary(), binary(), binary()}. setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, KeyMatLen, IVSize) @@ -106,7 +108,7 @@ setup_keys(Version, _PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize WantedLength = 2 * (HashSize + KeyMatLen + IVSize), KeyBlock = prf(?MD5SHA, MasterSecret, "key expansion", [ServerRandom, ClientRandom], WantedLength), - <<ClientWriteMacSecret:HashSize/binary, + <<ClientWriteMacSecret:HashSize/binary, ServerWriteMacSecret:HashSize/binary, ClientWriteKey:KeyMatLen/binary, ServerWriteKey:KeyMatLen/binary, ClientIV:IVSize/binary, ServerIV:IVSize/binary>> = KeyBlock, @@ -166,41 +168,25 @@ setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize, {ClientWriteMacSecret, ServerWriteMacSecret, ClientWriteKey, ServerWriteKey, ClientIV, ServerIV}. --spec mac_hash(integer(), binary(), integer(), integer(), tls_version(), - integer(), binary()) -> binary(). +-spec mac_hash(integer(), binary(), integer(), integer(), tls_record:tls_version(), + integer(), binary()) -> binary(). -mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, +mac_hash(Method, Mac_write_secret, Seq_num, Type, {Major, Minor}, Length, Fragment) -> %% RFC 2246 & 4346 - 6.2.3.1. %% HMAC_hash(MAC_write_secret, seq_num + TLSCompressed.type + %% TLSCompressed.version + TLSCompressed.length + %% TLSCompressed.fragment)); - Mac = hmac_hash(Method, Mac_write_secret, - [<<?UINT64(Seq_num), ?BYTE(Type), - ?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>, + Mac = hmac_hash(Method, Mac_write_secret, + [<<?UINT64(Seq_num), ?BYTE(Type), + ?BYTE(Major), ?BYTE(Minor), ?UINT16(Length)>>, Fragment]), Mac. --spec suites(1|2|3) -> [cipher_suite()]. - -suites(Minor) when Minor == 1; Minor == 2-> - case sufficent_ec_support() of - true -> - all_suites(Minor); - false -> - no_ec_suites(Minor) - end; - -suites(Minor) when Minor == 3 -> - case sufficent_ec_support() of - true -> - all_suites(3) ++ all_suites(2); - false -> - no_ec_suites(3) ++ no_ec_suites(2) - end. - -all_suites(Minor) when Minor == 1; Minor == 2-> - [ +-spec suites(1|2|3) -> [ssl_cipher:cipher_suite()]. + +suites(Minor) when Minor == 1; Minor == 2 -> + [ ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, @@ -223,64 +209,99 @@ all_suites(Minor) when Minor == 1; Minor == 2-> ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - - ?TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDHE_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_ECDH_ECDSA_WITH_RC4_128_SHA, - ?TLS_ECDH_RSA_WITH_RC4_128_SHA, - - ?TLS_RSA_WITH_DES_CBC_SHA + ?TLS_RSA_WITH_AES_128_CBC_SHA ]; -all_suites(3) -> +suites(3) -> [ + ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + + ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, + ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, + ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, - + + ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, + ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, + ?TLS_RSA_WITH_AES_256_GCM_SHA384, ?TLS_RSA_WITH_AES_256_CBC_SHA256, - + + ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, + ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, + ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, - - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, - ?TLS_RSA_WITH_AES_128_CBC_SHA256 - ]. -no_ec_suites(Minor) when Minor == 1; Minor == 2-> - [ - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA, - ?TLS_RSA_WITH_AES_256_CBC_SHA, - ?TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, - ?TLS_RSA_WITH_3DES_EDE_CBC_SHA, - ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA, - ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_AES_128_CBC_SHA, - ?TLS_RSA_WITH_RC4_128_SHA, - ?TLS_RSA_WITH_RC4_128_MD5, - ?TLS_DHE_RSA_WITH_DES_CBC_SHA, - ?TLS_RSA_WITH_DES_CBC_SHA - ]; -no_ec_suites(3) -> - [ - ?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, - ?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, - ?TLS_RSA_WITH_AES_256_CBC_SHA256, + ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, + ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, ?TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, ?TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, + ?TLS_RSA_WITH_AES_128_GCM_SHA256, ?TLS_RSA_WITH_AES_128_CBC_SHA256 - ]. + + %% not supported + %% ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384, + %% ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384, + %% ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256, + %% ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256 + ] ++ suites(2). + + + +signature_algs({3, 3}, HashSigns) -> + CryptoSupports = crypto:supports(), + Hashes = proplists:get_value(hashs, CryptoSupports), + PubKeys = proplists:get_value(public_keys, CryptoSupports), + Supported = lists:foldl(fun({Hash, dsa = Sign} = Alg, Acc) -> + case proplists:get_bool(dss, PubKeys) + andalso proplists:get_bool(Hash, Hashes) + andalso is_pair(Hash, Sign, Hashes) + of + true -> + [Alg | Acc]; + false -> + Acc + end; + ({Hash, Sign} = Alg, Acc) -> + case proplists:get_bool(Sign, PubKeys) + andalso proplists:get_bool(Hash, Hashes) + andalso is_pair(Hash, Sign, Hashes) + of + true -> + [Alg | Acc]; + false -> + Acc + end + end, [], HashSigns), + lists:reverse(Supported). + +default_signature_algs({3, 3} = Version) -> + Default = [%% SHA2 + {sha512, ecdsa}, + {sha512, rsa}, + {sha384, ecdsa}, + {sha384, rsa}, + {sha256, ecdsa}, + {sha256, rsa}, + {sha224, ecdsa}, + {sha224, rsa}, + %% SHA + {sha, ecdsa}, + {sha, rsa}, + {sha, dsa}], + signature_algs(Version, Default); +default_signature_algs(_) -> + undefined. %%-------------------------------------------------------------------- %%% Internal functions @@ -323,7 +344,7 @@ p_hash(Secret, Seed, WantedLength, Method, N, Acc) -> %% ... Where A(0) = seed %% A(i) = HMAC_hash(secret, A(i-1)) -%% a(0, _Secret, Seed, _Method) -> +%% a(0, _Secret, Seed, _Method) -> %% Seed. %% a(N, Secret, Seed, Method) -> %% hmac_hash(Method, Secret, a(N-1, Secret, Seed, Method)). @@ -366,13 +387,32 @@ finished_label(client) -> finished_label(server) -> <<"server finished">>. +is_pair(sha, dsa, _) -> + true; +is_pair(_, dsa, _) -> + false; +is_pair(Hash, ecdsa, Hashs) -> + AtLeastSha = Hashs -- [md2,md4,md5], + lists:member(Hash, AtLeastSha); +is_pair(Hash, rsa, Hashs) -> + AtLeastMd5 = Hashs -- [md2,md4], + lists:member(Hash, AtLeastMd5). + %% list ECC curves in prefered order ecc_curves(_Minor) -> - [?sect571r1,?sect571k1,?secp521r1,?sect409k1,?sect409r1, - ?secp384r1,?sect283k1,?sect283r1,?secp256k1,?secp256r1, - ?sect239k1,?sect233k1,?sect233r1,?secp224k1,?secp224r1, - ?sect193r1,?sect193r2,?secp192k1,?secp192r1,?sect163k1, - ?sect163r1,?sect163r2,?secp160k1,?secp160r1,?secp160r2]. + TLSCurves = [sect571r1,sect571k1,secp521r1,brainpoolP512r1, + sect409k1,sect409r1,brainpoolP384r1,secp384r1, + sect283k1,sect283r1,brainpoolP256r1,secp256k1,secp256r1, + sect239k1,sect233k1,sect233r1,secp224k1,secp224r1, + sect193r1,sect193r2,secp192k1,secp192r1,sect163k1, + sect163r1,sect163r2,secp160k1,secp160r1,secp160r2], + CryptoCurves = crypto:ec_curves(), + lists:foldr(fun(Curve, Curves) -> + case proplists:get_bool(Curve, CryptoCurves) of + true -> [pubkey_cert_records:namedCurves(Curve)|Curves]; + false -> Curves + end + end, [], TLSCurves). %% ECC curves from draft-ietf-tls-ecc-12.txt (Oct. 17, 2005) oid_to_enum(?sect163k1) -> 1; @@ -399,7 +439,10 @@ oid_to_enum(?secp224r1) -> 21; oid_to_enum(?secp256k1) -> 22; oid_to_enum(?secp256r1) -> 23; oid_to_enum(?secp384r1) -> 24; -oid_to_enum(?secp521r1) -> 25. +oid_to_enum(?secp521r1) -> 25; +oid_to_enum(?brainpoolP256r1) -> 26; +oid_to_enum(?brainpoolP384r1) -> 27; +oid_to_enum(?brainpoolP512r1) -> 28. enum_to_oid(1) -> ?sect163k1; enum_to_oid(2) -> ?sect163r1; @@ -425,8 +468,9 @@ enum_to_oid(21) -> ?secp224r1; enum_to_oid(22) -> ?secp256k1; enum_to_oid(23) -> ?secp256r1; enum_to_oid(24) -> ?secp384r1; -enum_to_oid(25) -> ?secp521r1. - -sufficent_ec_support() -> - CryptoSupport = crypto:supports(), - proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)). +enum_to_oid(25) -> ?secp521r1; +enum_to_oid(26) -> ?brainpoolP256r1; +enum_to_oid(27) -> ?brainpoolP384r1; +enum_to_oid(28) -> ?brainpoolP512r1; +enum_to_oid(_) -> + undefined. diff --git a/lib/ssl/test/Makefile b/lib/ssl/test/Makefile index 39aa22ffb4..a2eb4ce449 100644 --- a/lib/ssl/test/Makefile +++ b/lib/ssl/test/Makefile @@ -1,18 +1,19 @@ # # %CopyrightBegin% # -# Copyright Ericsson AB 1999-2013. All Rights Reserved. +# Copyright Ericsson AB 1999-2016. All Rights Reserved. # -# The contents of this file are subject to the Erlang Public License, -# Version 1.1, (the "License"); you may not use this file except in -# compliance with the License. You should have received a copy of the -# Erlang Public License along with this software. If not, it can be -# retrieved online at http://www.erlang.org/. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# Software distributed under the License is distributed on an "AS IS" -# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -# the License for the specific language governing rights and limitations -# under the License. +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # %CopyrightEnd% # @@ -36,17 +37,24 @@ VSN=$(GS_VSN) MODULES = \ ssl_test_lib \ + ssl_alpn_handshake_SUITE \ ssl_basic_SUITE \ + ssl_bench_SUITE \ ssl_cipher_SUITE \ ssl_certificate_verify_SUITE\ + ssl_crl_SUITE\ ssl_dist_SUITE \ ssl_handshake_SUITE \ ssl_npn_hello_SUITE \ ssl_npn_handshake_SUITE \ ssl_packet_SUITE \ ssl_payload_SUITE \ + ssl_pem_cache_SUITE \ ssl_session_cache_SUITE \ ssl_to_openssl_SUITE \ + ssl_ECC_SUITE \ + ssl_upgrade_SUITE\ + ssl_sni_SUITE \ make_certs\ erl_make_certs @@ -56,6 +64,7 @@ ERL_FILES = $(MODULES:%=%.erl) HRL_FILES = HRL_FILES_SRC = \ + ssl_api.hrl\ ssl_internal.hrl\ ssl_alert.hrl \ tls_handshake.hrl \ @@ -72,7 +81,7 @@ HRL_FILES_NEEDED_IN_TEST = \ TARGET_FILES = $(MODULES:%=$(EBIN)/%.$(EMULATOR)) -INCLUDES = -I. -I$(ERL_TOP)/lib/test_server/include/ +INCLUDES = -I. DATADIRS = ssl_basic_SUITE_data @@ -91,8 +100,7 @@ RELSYSDIR = $(RELEASE_PATH)/ssl_test # The path to the test_server ebin dir is needed when # running the target "targets". # ---------------------------------------------------- -ERL_COMPILE_FLAGS += -pa ../../../internal_tools/test_server/ebin \ - $(INCLUDES) +ERL_COMPILE_FLAGS += $(INCLUDES) # ---------------------------------------------------- # Targets @@ -127,7 +135,7 @@ release_spec: opt release_tests_spec: opt $(INSTALL_DIR) "$(RELSYSDIR)" $(INSTALL_DATA) $(ERL_FILES) $(HRL_FILES) $(HRL_FILES_NEEDED_IN_TEST) $(COVER_FILE) "$(RELSYSDIR)" - $(INSTALL_DATA) ssl.spec ssl.cover "$(RELSYSDIR)" + $(INSTALL_DATA) ssl.spec ssl_bench.spec ssl.cover "$(RELSYSDIR)" chmod -R u+w "$(RELSYSDIR)" @tar cf - *_SUITE_data | (cd "$(RELSYSDIR)"; tar xf -) diff --git a/lib/ssl/test/erl_make_certs.erl b/lib/ssl/test/erl_make_certs.erl index 22dc951ac1..a6657be995 100644 --- a/lib/ssl/test/erl_make_certs.erl +++ b/lib/ssl/test/erl_make_certs.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2011-2013. All Rights Reserved. +%% Copyright Ericsson AB 2011-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -114,7 +115,7 @@ verify_signature(DerEncodedCert, DerKey, _KeyParams) -> #'DSAPrivateKey'{p=P, q=Q, g=G, y=Y} -> public_key:pkix_verify(DerEncodedCert, {Y, #'Dss-Parms'{p=P, q=Q, g=G}}); #'ECPrivateKey'{version = _Version, privateKey = _PrivKey, - parameters = Params, publicKey = {0, PubKey}} -> + parameters = Params, publicKey = PubKey} -> public_key:pkix_verify(DerEncodedCert, {#'ECPoint'{point = PubKey}, Params}) end. @@ -204,7 +205,7 @@ issuer_der(Issuer) -> Subject. subject(undefined, IsRootCA) -> - User = if IsRootCA -> "RootCA"; true -> user() end, + User = if IsRootCA -> "RootCA"; true -> os:getenv("USER", "test_user") end, Opts = [{email, User ++ "@erlang.org"}, {name, User}, {city, "Stockholm"}, @@ -215,14 +216,6 @@ subject(undefined, IsRootCA) -> subject(Opts, _) -> subject(Opts). -user() -> - case os:getenv("USER") of - false -> - "test_user"; - User -> - User - end. - subject(SubjectOpts) when is_list(SubjectOpts) -> Encode = fun(Opt) -> {Type,Value} = subject_enc(Opt), @@ -300,7 +293,7 @@ publickey(#'DSAPrivateKey'{p=P, q=Q, g=G, y=Y}) -> publickey(#'ECPrivateKey'{version = _Version, privateKey = _PrivKey, parameters = Params, - publicKey = {0, PubKey}}) -> + publicKey = PubKey}) -> Algo = #'PublicKeyAlgorithm'{algorithm= ?'id-ecPublicKey', parameters=Params}, #'OTPSubjectPublicKeyInfo'{algorithm = Algo, subjectPublicKey = #'ECPoint'{point = PubKey}}. @@ -325,14 +318,14 @@ sign_algorithm(#'RSAPrivateKey'{}, Opts) -> {Type, 'NULL'}; sign_algorithm(#'DSAPrivateKey'{p=P, q=Q, g=G}, _Opts) -> {?'id-dsa-with-sha1', {params,#'Dss-Parms'{p=P, q=Q, g=G}}}; -sign_algorithm(#'ECPrivateKey'{}, Opts) -> +sign_algorithm(#'ECPrivateKey'{parameters = Parms}, Opts) -> Type = case proplists:get_value(digest, Opts, sha1) of sha1 -> ?'ecdsa-with-SHA1'; sha512 -> ?'ecdsa-with-SHA512'; sha384 -> ?'ecdsa-with-SHA384'; sha256 -> ?'ecdsa-with-SHA256' end, - {Type, 'NULL'}. + {Type, Parms}. make_key(rsa, _Opts) -> %% (OBS: for testing only) @@ -341,7 +334,9 @@ make_key(dsa, _Opts) -> gen_dsa2(128, 20); %% Bytes i.e. {1024, 160} make_key(ec, _Opts) -> %% (OBS: for testing only) - gen_ec2(secp256k1). + CurveOid = hd(tls_v1:ecc_curves(0)), + NamedCurve = pubkey_cert_records:namedCurves(CurveOid), + gen_ec2(NamedCurve). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% RSA key generation (OBS: for testing only) @@ -409,9 +404,9 @@ gen_ec2(CurveId) -> {PubKey, PrivKey} = crypto:generate_key(ecdh, CurveId), #'ECPrivateKey'{version = 1, - privateKey = binary_to_list(PrivKey), + privateKey = PrivKey, parameters = {namedCurve, pubkey_cert_records:namedCurves(CurveId)}, - publicKey = {0, PubKey}}. + publicKey = PubKey}. %% See fips_186-3.pdf dsa_search(T, P0, Q, Iter) when Iter > 0 -> diff --git a/lib/ssl/test/make_certs.erl b/lib/ssl/test/make_certs.erl index 4603a9f846..009bcd81ad 100644 --- a/lib/ssl/test/make_certs.erl +++ b/lib/ssl/test/make_certs.erl @@ -1,40 +1,89 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2012. All Rights Reserved. +%% Copyright Ericsson AB 2007-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. -%% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% -module(make_certs). +-compile([export_all]). --export([all/2]). +%-export([all/1, all/2, rootCA/2, intermediateCA/3, endusers/3, enduser/3, revoke/3, gencrl/2, verify/3]). --record(dn, {commonName, +-record(config, {commonName, organizationalUnitName = "Erlang OTP", organizationName = "Ericsson AB", localityName = "Stockholm", countryName = "SE", - emailAddress = "[email protected]"}). + emailAddress = "[email protected]", + default_bits = 2048, + v2_crls = true, + ecc_certs = false, + issuing_distribution_point = false, + crl_port = 8000, + openssl_cmd = "openssl"}). + + +default_config() -> + #config{}. + +make_config(Args) -> + make_config(Args, #config{}). + +make_config([], C) -> + C; +make_config([{organizationalUnitName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{organizationalUnitName = Name}); +make_config([{organizationName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{organizationName = Name}); +make_config([{localityName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{localityName = Name}); +make_config([{countryName, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{countryName = Name}); +make_config([{emailAddress, Name}|T], C) when is_list(Name) -> + make_config(T, C#config{emailAddress = Name}); +make_config([{default_bits, Bits}|T], C) when is_integer(Bits) -> + make_config(T, C#config{default_bits = Bits}); +make_config([{v2_crls, Bool}|T], C) when is_boolean(Bool) -> + make_config(T, C#config{v2_crls = Bool}); +make_config([{crl_port, Port}|T], C) when is_integer(Port) -> + make_config(T, C#config{crl_port = Port}); +make_config([{ecc_certs, Bool}|T], C) when is_boolean(Bool) -> + make_config(T, C#config{ecc_certs = Bool}); +make_config([{issuing_distribution_point, Bool}|T], C) when is_boolean(Bool) -> + make_config(T, C#config{issuing_distribution_point = Bool}); +make_config([{openssl_cmd, Cmd}|T], C) when is_list(Cmd) -> + make_config(T, C#config{openssl_cmd = Cmd}). + + +all([DataDir, PrivDir]) -> + all(DataDir, PrivDir). all(DataDir, PrivDir) -> - OpenSSLCmd = "openssl", + all(DataDir, PrivDir, #config{}). + +all(DataDir, PrivDir, C) when is_list(C) -> + all(DataDir, PrivDir, make_config(C)); +all(DataDir, PrivDir, C = #config{}) -> + ok = filelib:ensure_dir(filename:join(PrivDir, "erlangCA")), create_rnd(DataDir, PrivDir), % For all requests - rootCA(PrivDir, OpenSSLCmd, "erlangCA"), - intermediateCA(PrivDir, OpenSSLCmd, "otpCA", "erlangCA"), - endusers(PrivDir, OpenSSLCmd, "otpCA", ["client", "server"]), - collect_certs(PrivDir, ["erlangCA", "otpCA"], ["client", "server"]), + rootCA(PrivDir, "erlangCA", C), + intermediateCA(PrivDir, "otpCA", "erlangCA", C), + endusers(PrivDir, "otpCA", ["client", "server", "revoked", "a.server", "b.server"], C), + endusers(PrivDir, "erlangCA", ["localhost"], C), %% Create keycert files SDir = filename:join([PrivDir, "server"]), SC = filename:join([SDir, "cert.pem"]), @@ -46,7 +95,14 @@ all(DataDir, PrivDir) -> CK = filename:join([CDir, "key.pem"]), CKC = filename:join([CDir, "keycert.pem"]), append_files([CK, CC], CKC), - remove_rnd(PrivDir). + RDir = filename:join([PrivDir, "revoked"]), + RC = filename:join([RDir, "cert.pem"]), + RK = filename:join([RDir, "key.pem"]), + RKC = filename:join([RDir, "keycert.pem"]), + revoke(PrivDir, "otpCA", "revoked", C), + append_files([RK, RC], RKC), + remove_rnd(PrivDir), + {ok, C}. append_files(FileNames, ResultFileName) -> {ok, ResultFile} = file:open(ResultFileName, [write]), @@ -59,111 +115,189 @@ do_append_files([F|Fs], RF) -> ok = file:write(RF, Data), do_append_files(Fs, RF). -rootCA(Root, OpenSSLCmd, Name) -> - create_ca_dir(Root, Name, ca_cnf(Name)), - DN = #dn{commonName = Name}, - create_self_signed_cert(Root, OpenSSLCmd, Name, req_cnf(DN)), - ok. +rootCA(Root, Name, C) -> + create_ca_dir(Root, Name, ca_cnf(Root, C#config{commonName = Name})), + create_self_signed_cert(Root, Name, req_cnf(Root, C#config{commonName = Name}), C), + file:copy(filename:join([Root, Name, "cert.pem"]), filename:join([Root, Name, "cacerts.pem"])), + gencrl(Root, Name, C). -intermediateCA(Root, OpenSSLCmd, CA, ParentCA) -> - CA = "otpCA", - create_ca_dir(Root, CA, ca_cnf(CA)), +intermediateCA(Root, CA, ParentCA, C) -> + create_ca_dir(Root, CA, ca_cnf(Root, C#config{commonName = CA})), CARoot = filename:join([Root, CA]), - DN = #dn{commonName = CA}, CnfFile = filename:join([CARoot, "req.cnf"]), - file:write_file(CnfFile, req_cnf(DN)), + file:write_file(CnfFile, req_cnf(Root, C#config{commonName = CA})), KeyFile = filename:join([CARoot, "private", "key.pem"]), ReqFile = filename:join([CARoot, "req.pem"]), - create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), + create_req(Root, CnfFile, KeyFile, ReqFile, C), CertFile = filename:join([CARoot, "cert.pem"]), - sign_req(Root, OpenSSLCmd, ParentCA, "ca_cert", ReqFile, CertFile). - -endusers(Root, OpenSSLCmd, CA, Users) -> - lists:foreach(fun(User) -> enduser(Root, OpenSSLCmd, CA, User) end, Users). - -enduser(Root, OpenSSLCmd, CA, User) -> + sign_req(Root, ParentCA, "ca_cert", ReqFile, CertFile, C), + CACertsFile = filename:join(CARoot, "cacerts.pem"), + file:copy(filename:join([Root, ParentCA, "cacerts.pem"]), CACertsFile), + %% append this CA's cert to the cacerts file + {ok, Bin} = file:read_file(CertFile), + {ok, FD} = file:open(CACertsFile, [append]), + file:write(FD, ["\n", Bin]), + file:close(FD), + gencrl(Root, CA, C). + +endusers(Root, CA, Users, C) -> + [enduser(Root, CA, User, C) || User <- Users]. + +enduser(Root, CA, User, C) -> UsrRoot = filename:join([Root, User]), file:make_dir(UsrRoot), CnfFile = filename:join([UsrRoot, "req.cnf"]), - DN = #dn{commonName = User}, - file:write_file(CnfFile, req_cnf(DN)), + file:write_file(CnfFile, req_cnf(Root, C#config{commonName = User})), KeyFile = filename:join([UsrRoot, "key.pem"]), ReqFile = filename:join([UsrRoot, "req.pem"]), - create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile), + create_req(Root, CnfFile, KeyFile, ReqFile, C), + %create_req(Root, CnfFile, KeyFile, ReqFile), CertFileAllUsage = filename:join([UsrRoot, "cert.pem"]), - sign_req(Root, OpenSSLCmd, CA, "user_cert", ReqFile, CertFileAllUsage), + sign_req(Root, CA, "user_cert", ReqFile, CertFileAllUsage, C), CertFileDigitalSigOnly = filename:join([UsrRoot, "digital_signature_only_cert.pem"]), - sign_req(Root, OpenSSLCmd, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly). - -collect_certs(Root, CAs, Users) -> - Bins = lists:foldr( - fun(CA, Acc) -> - File = filename:join([Root, CA, "cert.pem"]), - {ok, Bin} = file:read_file(File), - [Bin, "\n" | Acc] - end, [], CAs), - lists:foreach( - fun(User) -> - File = filename:join([Root, User, "cacerts.pem"]), - file:write_file(File, Bins) - end, Users). + sign_req(Root, CA, "user_cert_digital_signature_only", ReqFile, CertFileDigitalSigOnly, C), + CACertsFile = filename:join(UsrRoot, "cacerts.pem"), + file:copy(filename:join([Root, CA, "cacerts.pem"]), CACertsFile), + ok. + +revoke(Root, CA, User, C) -> + UsrCert = filename:join([Root, User, "cert.pem"]), + CACnfFile = filename:join([Root, CA, "ca.cnf"]), + Cmd = [C#config.openssl_cmd, " ca" + " -revoke ", UsrCert, + [" -crl_reason keyCompromise" || C#config.v2_crls ], + " -config ", CACnfFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env), + gencrl(Root, CA, C). + +gencrl(Root, CA, C) -> + %% By default, the CRL is valid for 24 hours from now. + gencrl(Root, CA, C, 24). -create_self_signed_cert(Root, OpenSSLCmd, CAName, Cnf) -> +gencrl(Root, CA, C, CrlHours) -> + CACnfFile = filename:join([Root, CA, "ca.cnf"]), + CACRLFile = filename:join([Root, CA, "crl.pem"]), + Cmd = [C#config.openssl_cmd, " ca" + " -gencrl ", + " -crlhours ", integer_to_list(CrlHours), + " -out ", CACRLFile, + " -config ", CACnfFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + +can_generate_expired_crls(C) -> + %% OpenSSL can generate CRLs with an expiration date in the past, + %% if we pass a negative number for -crlhours. However, LibreSSL + %% rejects this with the error "invalid argument -24: too small". + %% Let's check which one we have. + Cmd = [C#config.openssl_cmd, " ca -crlhours -24"], + Output = os:cmd(Cmd), + 0 =:= string:str(Output, "too small"). + +verify(Root, CA, User, C) -> + CAFile = filename:join([Root, User, "cacerts.pem"]), + CACRLFile = filename:join([Root, CA, "crl.pem"]), + CertFile = filename:join([Root, User, "cert.pem"]), + Cmd = [C#config.openssl_cmd, " verify" + " -CAfile ", CAFile, + " -CRLfile ", CACRLFile, %% this is undocumented, but seems to work + " -crl_check ", + CertFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + try cmd(Cmd, Env) catch + exit:{eval_cmd, _, _} -> + invalid + end. + +create_self_signed_cert(Root, CAName, Cnf, C = #config{ecc_certs = true}) -> CARoot = filename:join([Root, CAName]), CnfFile = filename:join([CARoot, "req.cnf"]), file:write_file(CnfFile, Cnf), KeyFile = filename:join([CARoot, "private", "key.pem"]), CertFile = filename:join([CARoot, "cert.pem"]), - Cmd = [OpenSSLCmd, " req" + Cmd = [C#config.openssl_cmd, " ecparam" + " -out ", KeyFile, + " -name secp521r1 ", + %" -name sect283k1 ", + " -genkey "], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env), + + Cmd2 = [C#config.openssl_cmd, " req" " -new" " -x509" " -config ", CnfFile, - " -keyout ", KeyFile, + " -key ", KeyFile, + " -outform PEM ", " -out ", CertFile], - Env = [{"ROOTDIR", Root}], - cmd(Cmd, Env), - fix_key_file(OpenSSLCmd, KeyFile). - -% openssl 1.0 generates key files in pkcs8 format by default and we don't handle this format -fix_key_file(OpenSSLCmd, KeyFile) -> - KeyFileTmp = KeyFile ++ ".tmp", - Cmd = [OpenSSLCmd, " rsa", - " -in ", - KeyFile, - " -out ", - KeyFileTmp], - cmd(Cmd, []), - ok = file:rename(KeyFileTmp, KeyFile). + cmd(Cmd2, Env); +create_self_signed_cert(Root, CAName, Cnf, C) -> + CARoot = filename:join([Root, CAName]), + CnfFile = filename:join([CARoot, "req.cnf"]), + file:write_file(CnfFile, Cnf), + KeyFile = filename:join([CARoot, "private", "key.pem"]), + CertFile = filename:join([CARoot, "cert.pem"]), + Cmd = [C#config.openssl_cmd, " req" + " -new" + " -x509" + " -config ", CnfFile, + " -keyout ", KeyFile, + " -outform PEM", + " -out ", CertFile], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + create_ca_dir(Root, CAName, Cnf) -> CARoot = filename:join([Root, CAName]), + ok = filelib:ensure_dir(CARoot), file:make_dir(CARoot), create_dirs(CARoot, ["certs", "crl", "newcerts", "private"]), create_rnd(Root, filename:join([CAName, "private"])), create_files(CARoot, [{"serial", "01\n"}, + {"crlnumber", "01"}, {"index.txt", ""}, {"ca.cnf", Cnf}]). -create_req(Root, OpenSSLCmd, CnfFile, KeyFile, ReqFile) -> - Cmd = [OpenSSLCmd, " req" +create_req(Root, CnfFile, KeyFile, ReqFile, C = #config{ecc_certs = true}) -> + Cmd = [C#config.openssl_cmd, " ecparam" + " -out ", KeyFile, + " -name secp521r1 ", + %" -name sect283k1 ", + " -genkey "], + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env), + Cmd2 = [C#config.openssl_cmd, " req" + " -new ", + " -key ", KeyFile, + " -outform PEM ", + " -out ", ReqFile, + " -config ", CnfFile], + cmd(Cmd2, Env); + %fix_key_file(KeyFile). +create_req(Root, CnfFile, KeyFile, ReqFile, C) -> + Cmd = [C#config.openssl_cmd, " req" " -new" " -config ", CnfFile, + " -outform PEM ", " -keyout ", KeyFile, " -out ", ReqFile], - Env = [{"ROOTDIR", Root}], - cmd(Cmd, Env), - fix_key_file(OpenSSLCmd, KeyFile). + Env = [{"ROOTDIR", filename:absname(Root)}], + cmd(Cmd, Env). + %fix_key_file(KeyFile). + -sign_req(Root, OpenSSLCmd, CA, CertType, ReqFile, CertFile) -> +sign_req(Root, CA, CertType, ReqFile, CertFile, C) -> CACnfFile = filename:join([Root, CA, "ca.cnf"]), - Cmd = [OpenSSLCmd, " ca" + Cmd = [C#config.openssl_cmd, " ca" " -batch" " -notext" " -config ", CACnfFile, " -extensions ", CertType, " -in ", ReqFile, " -out ", CertFile], - Env = [{"ROOTDIR", Root}], + Env = [{"ROOTDIR", filename:absname(Root)}], cmd(Cmd, Env). %% @@ -194,19 +328,20 @@ cmd(Cmd, Env) -> FCmd = lists:flatten(Cmd), Port = open_port({spawn, FCmd}, [stream, eof, exit_status, stderr_to_stdout, {env, Env}]), - eval_cmd(Port). + eval_cmd(Port, FCmd). -eval_cmd(Port) -> +eval_cmd(Port, Cmd) -> receive {Port, {data, _}} -> - eval_cmd(Port); + eval_cmd(Port, Cmd); {Port, eof} -> ok end, receive - {Port, {exit_status, Status}} when Status /= 0 -> - %% io:fwrite("exit status: ~w~n", [Status]), - exit({eval_cmd, Status}) + {Port, {exit_status, 0}} -> + ok; + {Port, {exit_status, Status}} -> + exit({eval_cmd, Cmd, Status}) after 0 -> ok end. @@ -215,19 +350,19 @@ eval_cmd(Port) -> %% Contents of configuration files %% -req_cnf(DN) -> +req_cnf(Root, C) -> ["# Purpose: Configuration for requests (end users and CAs)." "\n" - "ROOTDIR = $ENV::ROOTDIR\n" + "ROOTDIR = " ++ Root ++ "\n" "\n" "[req]\n" "input_password = secret\n" "output_password = secret\n" - "default_bits = 1024\n" + "default_bits = ", integer_to_list(C#config.default_bits), "\n" "RANDFILE = $ROOTDIR/RAND\n" "encrypt_key = no\n" - "default_md = sha1\n" + "default_md = md5\n" "#string_mask = pkix\n" "x509_extensions = ca_ext\n" "prompt = no\n" @@ -235,12 +370,12 @@ req_cnf(DN) -> "\n" "[name]\n" - "commonName = ", DN#dn.commonName, "\n" - "organizationalUnitName = ", DN#dn.organizationalUnitName, "\n" - "organizationName = ", DN#dn.organizationName, "\n" - "localityName = ", DN#dn.localityName, "\n" - "countryName = ", DN#dn.countryName, "\n" - "emailAddress = ", DN#dn.emailAddress, "\n" + "commonName = ", C#config.commonName, "\n" + "organizationalUnitName = ", C#config.organizationalUnitName, "\n" + "organizationName = ", C#config.organizationName, "\n" + "localityName = ", C#config.localityName, "\n" + "countryName = ", C#config.countryName, "\n" + "emailAddress = ", C#config.emailAddress, "\n" "\n" "[ca_ext]\n" @@ -249,30 +384,31 @@ req_cnf(DN) -> "subjectKeyIdentifier = hash\n" "subjectAltName = email:copy\n"]. - -ca_cnf(CA) -> +ca_cnf(Root, C = #config{issuing_distribution_point = true}) -> ["# Purpose: Configuration for CAs.\n" "\n" - "ROOTDIR = $ENV::ROOTDIR\n" + "ROOTDIR = " ++ Root ++ "\n" "default_ca = ca\n" "\n" "[ca]\n" - "dir = $ROOTDIR/", CA, "\n" + "dir = $ROOTDIR/", C#config.commonName, "\n" "certs = $dir/certs\n" "crl_dir = $dir/crl\n" "database = $dir/index.txt\n" "new_certs_dir = $dir/newcerts\n" "certificate = $dir/cert.pem\n" "serial = $dir/serial\n" - "crl = $dir/crl.pem\n" + "crl = $dir/crl.pem\n", + ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls], "private_key = $dir/private/key.pem\n" "RANDFILE = $dir/private/RAND\n" "\n" - "x509_extensions = user_cert\n" + "x509_extensions = user_cert\n", + ["crl_extensions = crl_ext\n" || C#config.v2_crls], "unique_subject = no\n" "default_days = 3600\n" - "default_md = sha1\n" + "default_md = md5\n" "preserve = no\n" "policy = policy_match\n" "\n" @@ -286,6 +422,13 @@ ca_cnf(CA) -> "emailAddress = supplied\n" "\n" + "[crl_ext]\n" + "authorityKeyIdentifier=keyid:always,issuer:always\n", + ["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point], + + "[idpsec]\n" + "fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n" + "[user_cert]\n" "basicConstraints = CA:false\n" "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" @@ -293,8 +436,93 @@ ca_cnf(CA) -> "authorityKeyIdentifier = keyid,issuer:always\n" "subjectAltName = email:copy\n" "issuerAltName = issuer:copy\n" + "crlDistributionPoints=@crl_section\n" + + "[crl_section]\n" + %% intentionally invalid + "URI.1=http://localhost/",C#config.commonName,"/crl.pem\n" + "URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n" + "\n" + + "[user_cert_digital_signature_only]\n" + "basicConstraints = CA:false\n" + "keyUsage = digitalSignature\n" + "subjectKeyIdentifier = hash\n" + "authorityKeyIdentifier = keyid,issuer:always\n" + "subjectAltName = email:copy\n" + "issuerAltName = issuer:copy\n" "\n" + "[ca_cert]\n" + "basicConstraints = critical,CA:true\n" + "keyUsage = cRLSign, keyCertSign\n" + "subjectKeyIdentifier = hash\n" + "authorityKeyIdentifier = keyid:always,issuer:always\n" + "subjectAltName = email:copy\n" + "issuerAltName = issuer:copy\n" + "crlDistributionPoints=@crl_section\n" + ]; + +ca_cnf(Root, C = #config{issuing_distribution_point = false}) -> + ["# Purpose: Configuration for CAs.\n" + "\n" + "ROOTDIR = " ++ Root ++ "\n" + "default_ca = ca\n" + "\n" + + "[ca]\n" + "dir = $ROOTDIR/", C#config.commonName, "\n" + "certs = $dir/certs\n" + "crl_dir = $dir/crl\n" + "database = $dir/index.txt\n" + "new_certs_dir = $dir/newcerts\n" + "certificate = $dir/cert.pem\n" + "serial = $dir/serial\n" + "crl = $dir/crl.pem\n", + ["crlnumber = $dir/crlnumber\n" || C#config.v2_crls], + "private_key = $dir/private/key.pem\n" + "RANDFILE = $dir/private/RAND\n" + "\n" + "x509_extensions = user_cert\n", + ["crl_extensions = crl_ext\n" || C#config.v2_crls], + "unique_subject = no\n" + "default_days = 3600\n" + "default_md = md5\n" + "preserve = no\n" + "policy = policy_match\n" + "\n" + + "[policy_match]\n" + "commonName = supplied\n" + "organizationalUnitName = optional\n" + "organizationName = match\n" + "countryName = match\n" + "localityName = match\n" + "emailAddress = supplied\n" + "\n" + + "[crl_ext]\n" + "authorityKeyIdentifier=keyid:always,issuer:always\n", + %["issuingDistributionPoint=critical, @idpsec\n" || C#config.issuing_distribution_point], + + %"[idpsec]\n" + %"fullname=URI:http://localhost:8000/",C#config.commonName,"/crl.pem\n" + + "[user_cert]\n" + "basicConstraints = CA:false\n" + "keyUsage = nonRepudiation, digitalSignature, keyEncipherment\n" + "subjectKeyIdentifier = hash\n" + "authorityKeyIdentifier = keyid,issuer:always\n" + "subjectAltName = email:copy\n" + "issuerAltName = issuer:copy\n" + %"crlDistributionPoints=@crl_section\n" + + %%"[crl_section]\n" + %% intentionally invalid + %%"URI.1=http://localhost/",C#config.commonName,"/crl.pem\n" + %%"URI.2=http://localhost:",integer_to_list(C#config.crl_port),"/",C#config.commonName,"/crl.pem\n" + %%"\n" + "[user_cert_digital_signature_only]\n" "basicConstraints = CA:false\n" "keyUsage = digitalSignature\n" @@ -310,4 +538,6 @@ ca_cnf(CA) -> "subjectKeyIdentifier = hash\n" "authorityKeyIdentifier = keyid:always,issuer:always\n" "subjectAltName = email:copy\n" - "issuerAltName = issuer:copy\n"]. + "issuerAltName = issuer:copy\n" + %"crlDistributionPoints=@crl_section\n" + ]. diff --git a/lib/ssl/test/ssl.spec b/lib/ssl/test/ssl.spec index fc7c1bbb82..86e14c033e 100644 --- a/lib/ssl/test/ssl.spec +++ b/lib/ssl/test/ssl.spec @@ -1 +1,4 @@ {suites,"../ssl_test",all}. +{skip_cases, "../ssl_test", + ssl_bench_SUITE, [setup_sequential, setup_concurrent, payload_simple], + "Benchmarks run separately"}. diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl new file mode 100644 index 0000000000..b8a03f578d --- /dev/null +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -0,0 +1,354 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_ECC_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [ + {group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'} + ]. + +groups() -> + [ + {'tlsv1.2', [], all_versions_groups()}, + {'tlsv1.1', [], all_versions_groups()}, + {'tlsv1', [], all_versions_groups()}, + {'erlang_server', [], key_cert_combinations()}, + {'erlang_client', [], key_cert_combinations()}, + {'erlang', [], key_cert_combinations() ++ misc()} + ]. + +all_versions_groups ()-> + [{group, 'erlang_server'}, + {group, 'erlang_client'}, + {group, 'erlang'} + ]. + +key_cert_combinations() -> + [client_ecdh_server_ecdh, + client_rsa_server_ecdh, + client_ecdh_server_rsa, + client_rsa_server_rsa, + client_ecdsa_server_ecdsa, + client_ecdsa_server_rsa, + client_rsa_server_ecdsa + ]. + +misc()-> + [client_ecdsa_server_ecdsa_with_raw_key]. + +%%-------------------------------------------------------------------- +init_per_suite(Config0) -> + end_per_suite(Config0), + try crypto:start() of + ok -> + %% make rsa certs using oppenssl + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), + Config1 = ssl_test_lib:make_ecdsa_cert(Config0), + Config2 = ssl_test_lib:make_ecdh_rsa_cert(Config1), + ssl_test_lib:cert_options(Config2) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + application:stop(ssl), + application:stop(crypto). + +%%-------------------------------------------------------------------- +init_per_group(erlang_client = Group, Config) -> + case ssl_test_lib:is_sane_ecc(openssl) of + true -> + common_init_per_group(Group, [{server_type, openssl}, + {client_type, erlang} | Config]); + false -> + {skip, "Known ECC bug in openssl"} + end; + +init_per_group(erlang_server = Group, Config) -> + case ssl_test_lib:is_sane_ecc(openssl) of + true -> + common_init_per_group(Group, [{server_type, erlang}, + {client_type, openssl} | Config]); + false -> + {skip, "Known ECC bug in openssl"} + end; + +init_per_group(erlang = Group, Config) -> + case ssl_test_lib:sufficient_crypto_support(Group) of + true -> + common_init_per_group(Group, [{server_type, erlang}, + {client_type, erlang} | Config]); + false -> + {skip, "Crypto does not support ECC"} + end; + +init_per_group(openssl = Group, Config) -> + case ssl_test_lib:sufficient_crypto_support(Group) of + true -> + common_init_per_group(Group, [{server_type, openssl}, + {client_type, openssl} | Config]); + false -> + {skip, "Crypto does not support ECC"} + end; + +init_per_group(Group, Config) -> + common_init_per_group(Group, Config). + +common_init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + Config0 = ssl_test_lib:init_tls_version(GroupName, Config), + [{tls_version, GroupName} | Config0]; + _ -> + openssl_check(GroupName, Config) + end. + +end_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- + +init_per_testcase(TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), + end_per_testcase(TestCase, Config), + ssl:start(), + ct:timetrap({seconds, 15}), + Config. + +end_per_testcase(_TestCase, Config) -> + application:stop(ssl), + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +client_ecdh_server_ecdh(Config) when is_list(Config) -> + COpts = proplists:get_value(client_ecdh_rsa_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + basic_test(COpts, SOpts, Config). + +client_ecdh_server_rsa(Config) when is_list(Config) -> + COpts = proplists:get_value(client_ecdh_rsa_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + basic_test(COpts, SOpts, Config). + +client_rsa_server_ecdh(Config) when is_list(Config) -> + COpts = proplists:get_value(client_ecdh_rsa_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + basic_test(COpts, SOpts, Config). + +client_rsa_server_rsa(Config) when is_list(Config) -> + COpts = proplists:get_value(client_verification_opts, Config), + SOpts = proplists:get_value(server_verification_opts, Config), + basic_test(COpts, SOpts, Config). + +client_ecdsa_server_ecdsa(Config) when is_list(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + basic_test(COpts, SOpts, Config). + +client_ecdsa_server_rsa(Config) when is_list(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + basic_test(COpts, SOpts, Config). + +client_rsa_server_ecdsa(Config) when is_list(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + basic_test(COpts, SOpts, Config). + +client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> + COpts = proplists:get_value(client_ecdsa_opts, Config), + SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + ServerCert = proplists:get_value(certfile, SOpts), + ServerKeyFile = proplists:get_value(keyfile, SOpts), + {ok, PemBin} = file:read_file(ServerKeyFile), + PemEntries = public_key:pem_decode(PemBin), + {'ECPrivateKey', Key, not_encrypted} = proplists:lookup('ECPrivateKey', PemEntries), + ServerKey = {'ECPrivateKey', Key}, + ServerCA = proplists:get_value(cacertfile, SOpts), + ClientCert = proplists:get_value(certfile, COpts), + ClientKey = proplists:get_value(keyfile, COpts), + ClientCA = proplists:get_value(cacertfile, COpts), + SType = proplists:get_value(server_type, Config), + CType = proplists:get_value(client_type, Config), + {Server, Port} = start_server_with_raw_key(SType, + ClientCA, ServerCA, + ServerCert, + ServerKey, + Config), + Client = start_client(CType, Port, ServerCA, ClientCA, + ClientCert, + ClientKey, Config), + check_result(Server, SType, Client, CType), + close(Server, Client). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +basic_test(COpts, SOpts, Config) -> + basic_test(proplists:get_value(certfile, COpts), + proplists:get_value(keyfile, COpts), + proplists:get_value(cacertfile, COpts), + proplists:get_value(certfile, SOpts), + proplists:get_value(keyfile, SOpts), + proplists:get_value(cacertfile, SOpts), + Config). + +basic_test(ClientCert, ClientKey, ClientCA, ServerCert, ServerKey, ServerCA, Config) -> + SType = proplists:get_value(server_type, Config), + CType = proplists:get_value(client_type, Config), + {Server, Port} = start_server(SType, + ClientCA, ServerCA, + ServerCert, + ServerKey, + Config), + Client = start_client(CType, Port, ServerCA, ClientCA, + ClientCert, + ClientKey, Config), + check_result(Server, SType, Client, CType), + close(Server, Client). + +start_client(openssl, Port, CA, OwnCa, Cert, Key, Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + NewCA = new_ca(filename:join(PrivDir, "new_ca.pem"), CA, OwnCa), + Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Exe = "openssl", + Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", Cert, "-CAfile", NewCA, + "-key", Key, "-host","localhost", "-msg", "-debug"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + true = port_command(OpenSslPort, "Hello world"), + OpenSslPort; +start_client(erlang, Port, CA, _, Cert, Key, Config) -> + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{verify, verify_peer}, + {cacertfile, CA}, + {certfile, Cert}, {keyfile, Key}]}]). + +start_server(openssl, CA, OwnCa, Cert, Key, Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + NewCA = new_ca(filename:join(PrivDir, "new_ca.pem"), CA, OwnCa), + + Port = ssl_test_lib:inet_port(node()), + Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-verify", "2", "-cert", Cert, "-CAfile", NewCA, + "-key", Key, "-msg", "-debug"], + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + true = port_command(OpenSslPort, "Hello world"), + {OpenSslPort, Port}; +start_server(erlang, CA, _, Cert, Key, Config) -> + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, + []}}, + {options, + [{verify, verify_peer}, {cacertfile, CA}, + {certfile, Cert}, {keyfile, Key}]}]), + {Server, ssl_test_lib:inet_port(Server)}. +start_server_with_raw_key(erlang, CA, _, Cert, Key, Config) -> + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, + []}}, + {options, + [{verify, verify_peer}, {cacertfile, CA}, + {certfile, Cert}, {key, Key}]}]), + {Server, ssl_test_lib:inet_port(Server)}. + +check_result(Server, erlang, Client, erlang) -> + ssl_test_lib:check_result(Server, ok, Client, ok); +check_result(Server, erlang, _, _) -> + ssl_test_lib:check_result(Server, ok); +check_result(_, _, Client, erlang) -> + ssl_test_lib:check_result(Client, ok); +check_result(_,openssl, _, openssl) -> + ok. + +openssl_check(erlang, Config) -> + Config; +openssl_check(_, Config) -> + TLSVersion = proplists:get_value(tls_version, Config), + case ssl_test_lib:check_sane_openssl_version(TLSVersion) of + true -> + Config; + false -> + {skip, "TLS version not supported by openssl"} + end. + +close(Port1, Port2) when is_port(Port1), is_port(Port2) -> + ssl_test_lib:close_port(Port1), + ssl_test_lib:close_port(Port2); +close(Port, Pid) when is_port(Port) -> + ssl_test_lib:close_port(Port), + ssl_test_lib:close(Pid); +close(Pid, Port) when is_port(Port) -> + ssl_test_lib:close_port(Port), + ssl_test_lib:close(Pid); +close(Client, Server) -> + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%% Work around OpenSSL bug, apparently the same bug as we had fixed in +%% 11629690ba61f8e0c93ef9b2b6102fd279825977 +new_ca(FileName, CA, OwnCa) -> + {ok, P1} = file:read_file(CA), + E1 = public_key:pem_decode(P1), + {ok, P2} = file:read_file(OwnCa), + E2 = public_key:pem_decode(P2), + case os:cmd("openssl version") of + "OpenSSL 1.0.1p-freebsd" ++ _ -> + Pem = public_key:pem_encode(E1 ++E2), + file:write_file(FileName, Pem); + _ -> + Pem = public_key:pem_encode(E2 ++E1), + file:write_file(FileName, Pem) + end, + FileName. diff --git a/lib/ssl/test/ssl_alpn_handshake_SUITE.erl b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl new file mode 100644 index 0000000000..da181faf64 --- /dev/null +++ b/lib/ssl/test/ssl_alpn_handshake_SUITE.erl @@ -0,0 +1,419 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% +-module(ssl_alpn_handshake_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). +-include_lib("common_test/include/ct.hrl"). + +-define(SLEEP, 500). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> + [{group, 'tlsv1.2'}, + {group, 'tlsv1.1'}, + {group, 'tlsv1'}, + {group, 'sslv3'}]. + +groups() -> + [ + {'tlsv1.2', [], alpn_tests()}, + {'tlsv1.1', [], alpn_tests()}, + {'tlsv1', [], alpn_tests()}, + {'sslv3', [], alpn_not_supported()} + ]. + +alpn_tests() -> + [empty_protocols_are_not_allowed, + protocols_must_be_a_binary_list, + empty_client, + empty_server, + empty_client_empty_server, + no_matching_protocol, + client_alpn_and_server_alpn, + client_alpn_and_server_no_support, + client_no_support_and_server_alpn, + client_alpn_npn_and_server_alpn, + client_alpn_npn_and_server_alpn_npn, + client_alpn_and_server_alpn_npn, + client_renegotiate, + session_reused + ]. + +alpn_not_supported() -> + [alpn_not_supported_client, + alpn_not_supported_server + ]. + +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl:start(), + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), + proplists:get_value(priv_dir, Config)), + ssl_test_lib:cert_options(Config) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:unload(ssl), + application:stop(crypto). + + +init_per_group(GroupName, Config) -> + case ssl_test_lib:is_tls_version(GroupName) of + true -> + case ssl_test_lib:sufficient_crypto_support(GroupName) of + true -> + ssl_test_lib:init_tls_version(GroupName, Config), + Config; + false -> + {skip, "Missing crypto support"} + end; + _ -> + ssl:start(), + Config + end. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- + +empty_protocols_are_not_allowed(Config) when is_list(Config) -> + {error, {options, {alpn_preferred_protocols, {invalid_protocol, <<>>}}}} + = (catch ssl:listen(9443, + [{alpn_preferred_protocols, [<<"foo/1">>, <<"">>]}])), + {error, {options, {alpn_advertised_protocols, {invalid_protocol, <<>>}}}} + = (catch ssl:connect({127,0,0,1}, 9443, + [{alpn_advertised_protocols, [<<"foo/1">>, <<"">>]}])). + +%-------------------------------------------------------------------------------- + +protocols_must_be_a_binary_list(Config) when is_list(Config) -> + Option1 = {alpn_preferred_protocols, hello}, + {error, {options, Option1}} = (catch ssl:listen(9443, [Option1])), + Option2 = {alpn_preferred_protocols, [<<"foo/1">>, hello]}, + {error, {options, {alpn_preferred_protocols, {invalid_protocol, hello}}}} + = (catch ssl:listen(9443, [Option2])), + Option3 = {alpn_advertised_protocols, hello}, + {error, {options, Option3}} = (catch ssl:connect({127,0,0,1}, 9443, [Option3])), + Option4 = {alpn_advertised_protocols, [<<"foo/1">>, hello]}, + {error, {options, {alpn_advertised_protocols, {invalid_protocol, hello}}}} + = (catch ssl:connect({127,0,0,1}, 9443, [Option4])). + +%-------------------------------------------------------------------------------- + +empty_client(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, []}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +empty_server(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, []}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +empty_client_empty_server(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, []}], + [{alpn_preferred_protocols, []}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +no_matching_protocol(Config) when is_list(Config) -> + run_failing_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"spdy/3">>, <<"http/2">>]}], + {connect_failed,{tls_alert,"no application protocol"}}). + +%-------------------------------------------------------------------------------- + +client_alpn_and_server_alpn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_alpn_and_server_no_support(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [], + {error, protocol_not_negotiated}). + +%-------------------------------------------------------------------------------- + +client_no_support_and_server_alpn(Config) when is_list(Config) -> + run_handshake(Config, + [], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {error, protocol_not_negotiated}). + +%-------------------------------------------------------------------------------- + +client_alpn_npn_and_server_alpn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"spdy/3">>}}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_alpn_npn_and_server_alpn_npn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"spdy/3">>}}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, + {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_alpn_and_server_alpn_npn(Config) when is_list(Config) -> + run_handshake(Config, + [{alpn_advertised_protocols, [<<"http/1.0">>, <<"http/1.1">>]}], + [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, + {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.0">>]}], + {ok, <<"http/1.1">>}). + +%-------------------------------------------------------------------------------- + +client_renegotiate(Config) when is_list(Config) -> + Data = "hello world", + + ClientOpts0 = proplists:get_value(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0, + ServerOpts0 = proplists:get_value(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, + ExpectedProtocol = {ok, <<"http/1.0">>}, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, ssl_receive_and_assert_alpn, [ExpectedProtocol, Data]}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, assert_alpn_and_renegotiate_and_send_data, [ExpectedProtocol, Data]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +%-------------------------------------------------------------------------------- + +session_reused(Config) when is_list(Config)-> + ClientOpts0 = proplists:get_value(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"http/1.0">>]}] ++ ClientOpts0, + ServerOpts0 = proplists:get_value(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result_msg, []}}, + {options, ClientOpts}]), + + SessionInfo = + receive + {Server, Info} -> + Info + end, + + Server ! {listen, {mfa, {ssl_test_lib, no_result, []}}}, + + %% Make sure session is registered + ct:sleep(?SLEEP), + + Client1 = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, session_info_result, []}}, + {from, self()}, {options, ClientOpts}]), + + receive + {Client1, SessionInfo} -> + ok; + {Client1, Other} -> + ct:fail(Other) + end, + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ssl_test_lib:close(Client1). + +%-------------------------------------------------------------------------------- + +alpn_not_supported_client(Config) when is_list(Config) -> + ClientOpts0 = proplists:get_value(client_opts, Config), + PrefProtocols = {client_preferred_next_protocols, + {client, [<<"http/1.0">>], <<"http/1.1">>}}, + ClientOpts = [PrefProtocols] ++ ClientOpts0, + {ClientNode, _ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, + {port, 8888}, {host, Hostname}, + {from, self()}, {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, {error, + {options, + {not_supported_in_sslv3, PrefProtocols}}}). + +%-------------------------------------------------------------------------------- + +alpn_not_supported_server(Config) when is_list(Config)-> + ServerOpts0 = proplists:get_value(server_opts, Config), + AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, + ServerOpts = [AdvProtocols] ++ ServerOpts0, + + {error, {options, {not_supported_in_sslv3, AdvProtocols}}} = ssl:listen(0, ServerOpts). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- + +run_failing_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedResult) -> + ClientOpts = ClientExtraOpts ++ proplists:get_value(client_opts, Config), + ServerOpts = ServerExtraOpts ++ proplists:get_value(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, placeholder, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + ExpectedResult + = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, placeholder, []}}, + {options, ClientOpts}]). + +run_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> + Data = "hello world", + + ClientOpts0 = proplists:get_value(client_opts, Config), + ClientOpts = ClientExtraOpts ++ ClientOpts0, + ServerOpts0 = proplists:get_value(server_opts, Config), + ServerOpts = ServerExtraOpts ++ ServerOpts0, + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, ssl_receive_and_assert_alpn, [ExpectedProtocol, Data]}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, ssl_send_and_assert_alpn, [ExpectedProtocol, Data]}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + +assert_alpn(Socket, Protocol) -> + ct:log("Negotiated Protocol ~p, Expecting: ~p ~n", + [ssl:negotiated_protocol(Socket), Protocol]), + Protocol = ssl:negotiated_protocol(Socket). + +assert_alpn_and_renegotiate_and_send_data(Socket, Protocol, Data) -> + assert_alpn(Socket, Protocol), + ct:log("Renegotiating ~n", []), + ok = ssl:renegotiate(Socket), + ssl:send(Socket, Data), + assert_alpn(Socket, Protocol), + ok. + +ssl_send_and_assert_alpn(Socket, Protocol, Data) -> + assert_alpn(Socket, Protocol), + ssl_send(Socket, Data). + +ssl_receive_and_assert_alpn(Socket, Protocol, Data) -> + assert_alpn(Socket, Protocol), + ssl_receive(Socket, Data). + +ssl_send(Socket, Data) -> + ct:log("Connection info: ~p~n", + [ssl:connection_information(Socket)]), + ssl:send(Socket, Data). + +ssl_receive(Socket, Data) -> + ssl_receive(Socket, Data, []). + +ssl_receive(Socket, Data, Buffer) -> + ct:log("Connection info: ~p~n", + [ssl:connection_information(Socket)]), + receive + {ssl, Socket, MoreData} -> + ct:log("Received ~p~n",[MoreData]), + NewBuffer = Buffer ++ MoreData, + case NewBuffer of + Data -> + ssl:send(Socket, "Got it"), + ok; + _ -> + ssl_receive(Socket, Data, NewBuffer) + end; + Other -> + ct:fail({unexpected_message, Other}) + after 4000 -> + ct:fail({did_not_get, Data}) + end. + +connection_info_result(Socket) -> + ssl:connection_information(Socket). diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index b5cf6d1212..efa5faa218 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -27,15 +28,14 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("public_key/include/public_key.hrl"). +-include("ssl_api.hrl"). -include("ssl_internal.hrl"). -include("ssl_alert.hrl"). -include("ssl_internal.hrl"). -include("tls_record.hrl"). -include("tls_handshake.hrl"). --define('24H_in_sec', 86400). --define(TIMEOUT, 60000). --define(LONG_TIMEOUT, 600000). +-define(TIMEOUT, 20000). -define(EXPIRE, 10). -define(SLEEP, 500). -define(RENEGOTIATION_DISABLE_TIME, 12000). @@ -44,14 +44,15 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, basic}, + {group, basic_tls}, {group, options}, + {group, options_tls}, {group, session}, + %%{group, 'dtlsv1.2'}, + %%{group, 'dtlsv1'}, {group, 'tlsv1.2'}, {group, 'tlsv1.1'}, {group, 'tlsv1'}, @@ -60,19 +61,29 @@ all() -> groups() -> [{basic, [], basic_tests()}, + {basic_tls, [], basic_tests_tls()}, {options, [], options_tests()}, - {'tlsv1.2', [], all_versions_groups()}, - {'tlsv1.1', [], all_versions_groups()}, - {'tlsv1', [], all_versions_groups() ++ rizzo_tests()}, - {'sslv3', [], all_versions_groups() ++ rizzo_tests()}, + {options_tls, [], options_tests_tls()}, + %%{'dtlsv1.2', [], all_versions_groups()}, + %%{'dtlsv1', [], all_versions_groups()}, + {'tlsv1.2', [], all_versions_groups() ++ tls_versions_groups() ++ [conf_signature_algs, no_common_signature_algs]}, + {'tlsv1.1', [], all_versions_groups() ++ tls_versions_groups()}, + {'tlsv1', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests()}, + {'sslv3', [], all_versions_groups() ++ tls_versions_groups() ++ rizzo_tests() ++ [tls_ciphersuite_vs_version]}, {api,[], api_tests()}, + {api_tls,[], api_tests_tls()}, {session, [], session_tests()}, {renegotiate, [], renegotiate_tests()}, {ciphers, [], cipher_tests()}, {ciphers_ec, [], cipher_tests_ec()}, - {error_handling_tests, [], error_handling_tests()} + {error_handling_tests, [], error_handling_tests()}, + {error_handling_tests_tls, [], error_handling_tests_tls()} ]. +tls_versions_groups ()-> + [{group, api_tls}, + {group, error_handling_tests_tls}]. + all_versions_groups ()-> [{group, api}, {group, renegotiate}, @@ -83,16 +94,27 @@ all_versions_groups ()-> basic_tests() -> [app, + appup, alerts, - send_close, + alert_details, + alert_details_not_too_big, + version_option, connect_twice, connect_dist, - clear_pem_cache + clear_pem_cache, + defaults, + fallback, + cipher_format + ]. + +basic_tests_tls() -> + [tls_send_close ]. options_tests() -> [der_input, - misc_ssl_options, + ssl_options_not_proplist, + raw_ssl_option, socket_options, invalid_inet_get_option, invalid_inet_get_option_not_list, @@ -109,27 +131,47 @@ options_tests() -> empty_protocol_versions, ipv6, reuseaddr, - tcp_reuseaddr]. + honor_server_cipher_order, + honor_client_cipher_order, + unordered_protocol_versions_server, + unordered_protocol_versions_client +]. + +options_tests_tls() -> + [tls_misc_ssl_options, + tls_tcp_reuseaddr]. api_tests() -> [connection_info, + connection_information, peername, peercert, peercert_with_client_cert, sockname, versions, controlling_process, - upgrade, - upgrade_with_timeout, - shutdown, - shutdown_write, - shutdown_both, - shutdown_error, + getstat, + close_with_timeout, hibernate, + hibernate_right_away, listen_socket, - ssl_accept_timeout, ssl_recv_timeout, - versions_option + server_name_indication_option, + accept_pool, + new_options_in_accept, + prf + ]. + +api_tests_tls() -> + [tls_versions_option, + tls_upgrade, + tls_upgrade_with_timeout, + tls_ssl_accept_timeout, + tls_downgrade, + tls_shutdown, + tls_shutdown_write, + tls_shutdown_both, + tls_shutdown_error ]. session_tests() -> @@ -142,15 +184,18 @@ session_tests() -> renegotiate_tests() -> [client_renegotiate, server_renegotiate, + client_secure_renegotiate, client_renegotiate_reused_session, server_renegotiate_reused_session, client_no_wrap_sequence_number, server_no_wrap_sequence_number, renegotiate_dos_mitigate_active, - renegotiate_dos_mitigate_passive]. + renegotiate_dos_mitigate_passive, + renegotiate_dos_mitigate_absolute]. cipher_tests() -> [cipher_suites, + cipher_suites_mix, ciphers_rsa_signed_certs, ciphers_rsa_signed_certs_openssl_names, ciphers_dsa_signed_certs, @@ -163,6 +208,11 @@ cipher_tests() -> srp_cipher_suites, srp_anon_cipher_suites, srp_dsa_cipher_suites, + rc4_rsa_cipher_suites, + rc4_ecdh_rsa_cipher_suites, + rc4_ecdsa_cipher_suites, + des_rsa_cipher_suites, + des_ecdh_rsa_cipher_suites, default_reject_anonymous]. cipher_tests_ec() -> @@ -173,35 +223,40 @@ cipher_tests_ec() -> error_handling_tests()-> [controller_dies, - client_closes_socket, - tcp_error_propagation_in_active_mode, - tcp_connect, - tcp_connect_big, - close_transport_accept + close_transport_accept, + recv_active, + recv_active_once, + recv_error_handling + ]. + +error_handling_tests_tls()-> + [tls_client_closes_socket, + tls_tcp_error_propagation_in_active_mode, + tls_tcp_connect, + tls_tcp_connect_big, + tls_dont_crash_on_handshake_garbage ]. rizzo_tests() -> [rizzo, - no_rizzo_rc4]. + no_rizzo_rc4, + rizzo_one_n_minus_one, + rizzo_zero_n, + rizzo_disabled]. %%-------------------------------------------------------------------- init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), catch crypto:stop(), try crypto:start() of ok -> ssl:start(), %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), - + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), Config1 = ssl_test_lib:make_dsa_cert(Config0), Config2 = ssl_test_lib:make_ecdsa_cert(Config1), - Config3 = ssl_test_lib:make_ecdh_rsa_cert(Config2), - Config = ssl_test_lib:cert_options(Config3), - [{watchdog, Dog} | Config] + Config = ssl_test_lib:make_ecdh_rsa_cert(Config2), + ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} end. @@ -212,30 +267,32 @@ end_per_suite(_Config) -> %%-------------------------------------------------------------------- init_per_group(GroupName, Config) -> - case ssl_test_lib:is_tls_version(GroupName) of + case ssl_test_lib:is_tls_version(GroupName) andalso ssl_test_lib:sufficient_crypto_support(GroupName) of true -> + ssl_test_lib:init_tls_version(GroupName, Config); + _ -> case ssl_test_lib:sufficient_crypto_support(GroupName) of true -> - ssl_test_lib:init_tls_version(GroupName), + ssl:start(), Config; false -> {skip, "Missing crypto support"} - end; - _ -> - ssl:start(), - Config + end end. - end_per_group(_GroupName, Config) -> Config. %%-------------------------------------------------------------------- -init_per_testcase(no_authority_key_identifier, Config) -> - %% Clear cach so that root cert will not - %% be found. - ssl:clear_pem_cache(), - Config; +init_per_testcase(Case, Config) when Case == unordered_protocol_versions_client; + Case == unordered_protocol_versions_server-> + case proplists:get_value(supported, ssl:versions()) of + ['tlsv1.2' | _] -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "TLS 1.2 need but not supported on this platform"} + end; init_per_testcase(protocol_versions, Config) -> ssl:stop(), @@ -243,15 +300,16 @@ init_per_testcase(protocol_versions, Config) -> %% For backwards compatibility sslv2 should be filtered out. application:set_env(ssl, protocol_version, [sslv2, sslv3, tlsv1]), ssl:start(), + ct:timetrap({seconds, 5}), Config; -init_per_testcase(reuse_session_expired, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), +init_per_testcase(reuse_session_expired, Config) -> ssl:stop(), application:load(ssl), application:set_env(ssl, session_lifetime, ?EXPIRE), application:set_env(ssl, session_delay_cleanup_time, 500), ssl:start(), + ct:timetrap({seconds, 30}), Config; init_per_testcase(empty_protocol_versions, Config) -> @@ -259,16 +317,124 @@ init_per_testcase(empty_protocol_versions, Config) -> application:load(ssl), application:set_env(ssl, protocol_version, []), ssl:start(), + ct:timetrap({seconds, 5}), + Config; + +init_per_testcase(fallback, Config) -> + case tls_record:highest_protocol_version([]) of + {3, N} when N > 1 -> + ct:timetrap({seconds, 5}), + Config; + _ -> + {skip, "Not relevant if highest supported version is less than 3.2"} + end; + +init_per_testcase(TestCase, Config) when TestCase == client_renegotiate; + TestCase == server_renegotiate; + TestCase == client_secure_renegotiate; + TestCase == client_renegotiate_reused_session; + TestCase == server_renegotiate_reused_session; + TestCase == client_no_wrap_sequence_number; + TestCase == server_no_wrap_sequence_number; + TestCase == renegotiate_dos_mitigate_active; + TestCase == renegotiate_dos_mitigate_passive; + TestCase == renegotiate_dos_mitigate_absolute -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 30}), + Config; + +init_per_testcase(TestCase, Config) when TestCase == psk_cipher_suites; + TestCase == psk_with_hint_cipher_suites; + TestCase == ciphers_rsa_signed_certs; + TestCase == ciphers_rsa_signed_certs_openssl_names; + TestCase == ciphers_dsa_signed_certs; + TestCase == ciphers_dsa_signed_certs_openssl_names; + TestCase == anonymous_cipher_suites; + TestCase == versions_option, + TestCase == tls_tcp_connect_big -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 60}), Config; -%% init_per_testcase(different_ca_peer_sign, Config0) -> -%% ssl_test_lib:make_mix_cert(Config0); +init_per_testcase(rizzo, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 40}), + Config; + +init_per_testcase(rizzo_one_n_minus_one, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 40}), + rizzo_add_mitigation_option(one_n_minus_one, Config); + +init_per_testcase(rizzo_zero_n, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 40}), + rizzo_add_mitigation_option(zero_n, Config); -init_per_testcase(_TestCase, Config0) -> +init_per_testcase(rizzo_disabled, Config) -> ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. + ct:timetrap({seconds, 40}), + rizzo_add_mitigation_option(disabled, Config); + +init_per_testcase(prf, Config) -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]), + ct:timetrap({seconds, 40}), + case proplists:get_value(tc_group_path, Config) of + [] -> Prop = []; + [Prop] -> Prop + end, + case proplists:get_value(name, Prop) of + undefined -> TlsVersions = [sslv3, tlsv1, 'tlsv1.1', 'tlsv1.2']; + TlsVersion when is_atom(TlsVersion) -> + TlsVersions = [TlsVersion] + end, + PRFS=[md5, sha, sha256, sha384, sha512], + %All are the result of running tls_v1:prf(PrfAlgo, <<>>, <<>>, <<>>, 16) + %with the specified PRF algorithm + ExpectedPrfResults= + [{md5, <<96,139,180,171,236,210,13,10,28,32,2,23,88,224,235,199>>}, + {sha, <<95,3,183,114,33,169,197,187,231,243,19,242,220,228,70,151>>}, + {sha256, <<166,249,145,171,43,95,158,232,6,60,17,90,183,180,0,155>>}, + {sha384, <<153,182,217,96,186,130,105,85,65,103,123,247,146,91,47,106>>}, + {sha512, <<145,8,98,38,243,96,42,94,163,33,53,49,241,4,127,28>>}, + %TLS 1.0 and 1.1 PRF: + {md5sha, <<63,136,3,217,205,123,200,177,251,211,17,229,132,4,173,80>>}], + TestPlan = prf_create_plan(TlsVersions, PRFS, ExpectedPrfResults), + [{prf_test_plan, TestPlan} | Config]; + +init_per_testcase(TestCase, Config) when TestCase == tls_ssl_accept_timeout; + TestCase == tls_client_closes_socket; + TestCase == tls_downgrade -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 15}), + Config; +init_per_testcase(clear_pem_cache, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 20}), + Config; +init_per_testcase(raw_ssl_option, Config) -> + ct:timetrap({seconds, 5}), + case os:type() of + {unix,linux} -> + Config; + _ -> + {skip, "Raw options are platform-specific"} + end; + +init_per_testcase(accept_pool, Config) -> + ct:timetrap({seconds, 5}), + case proplists:get_value(protocol, Config) of + dtls -> + {skip, "Not yet supported on DTLS sockets"}; + _ -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + Config + end; + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 5}), + Config. end_per_testcase(reuse_session_expired, Config) -> application:unset_env(ssl, session_lifetime), @@ -286,6 +452,11 @@ app() -> app(Config) when is_list(Config) -> ok = ?t:app_test(ssl). %%-------------------------------------------------------------------- +appup() -> + [{doc, "Test that the ssl appup file is ok"}]. +appup(Config) when is_list(Config) -> + ok = ?t:appup_test(ssl). +%%-------------------------------------------------------------------- alerts() -> [{doc, "Test ssl_alert:alert_txt/1"}]. alerts(Config) when is_list(Config) -> @@ -296,7 +467,11 @@ alerts(Config) when is_list(Config) -> ?ILLEGAL_PARAMETER, ?UNKNOWN_CA, ?ACCESS_DENIED, ?DECODE_ERROR, ?DECRYPT_ERROR, ?EXPORT_RESTRICTION, ?PROTOCOL_VERSION, ?INSUFFICIENT_SECURITY, ?INTERNAL_ERROR, ?USER_CANCELED, - ?NO_RENEGOTIATION], + ?NO_RENEGOTIATION, ?UNSUPPORTED_EXTENSION, ?CERTIFICATE_UNOBTAINABLE, + ?UNRECOGNISED_NAME, ?BAD_CERTIFICATE_STATUS_RESPONSE, + ?BAD_CERTIFICATE_HASH_VALUE, ?UNKNOWN_PSK_IDENTITY, + 255 %% Unsupported/unknow alert will result in a description too + ], Alerts = [?ALERT_REC(?WARNING, ?CLOSE_NOTIFY) | [?ALERT_REC(?FATAL, Desc) || Desc <- Descriptions]], lists:foreach(fun(Alert) -> @@ -308,12 +483,94 @@ alerts(Config) when is_list(Config) -> end end, Alerts). %%-------------------------------------------------------------------- +alert_details() -> + [{doc, "Test that ssl_alert:alert_txt/1 result contains extendend error description"}]. +alert_details(Config) when is_list(Config) -> + Unique = make_ref(), + UniqueStr = lists:flatten(io_lib:format("~w", [Unique])), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Unique), + case string:str(ssl_alert:alert_txt(Alert), UniqueStr) of + 0 -> + ct:fail(error_details_missing); + _ -> + ok + end. + +%%-------------------------------------------------------------------- +alert_details_not_too_big() -> + [{doc, "Test that ssl_alert:alert_txt/1 limits printed depth of extended error description"}]. +alert_details_not_too_big(Config) when is_list(Config) -> + Reason = lists:duplicate(10, lists:duplicate(10, lists:duplicate(10, {some, data}))), + Alert = ?ALERT_REC(?WARNING, ?CLOSE_NOTIFY, Reason), + case length(ssl_alert:alert_txt(Alert)) < 1000 of + true -> + ok; + false -> + ct:fail(ssl_alert_text_too_big) + end. + +%%-------------------------------------------------------------------- +new_options_in_accept() -> + [{doc,"Test that you can set ssl options in ssl_accept/3 and not only in tcp upgrade"}]. +new_options_in_accept(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_dsa_opts, Config), + [_ , _ | ServerSslOpts] = ssl_test_lib:ssl_options(server_opts, Config), %% Remove non ssl opts + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Version = ssl_test_lib:protocol_options(Config, [{tls, sslv3}, {dtls, dtlsv1}]), + Cipher = ssl_test_lib:protocol_options(Config, [{tls, {rsa,rc4_128,sha}}, {dtls, {rsa,aes_128_cbc,sha}}]), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {ssl_extra_opts, [{versions, [Version]}, + {ciphers,[Cipher]} | ServerSslOpts]}, %% To be set in ssl_accept/3 + {mfa, {?MODULE, connection_info_result, []}}, + {options, proplists:delete(cacertfile, ServerOpts0)}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{versions, [Version]}, + {ciphers,[Cipher]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ClientMsg = {ok, {Version, Cipher}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- +prf() -> + [{doc,"Test that ssl:prf/5 uses the negotiated PRF."}]. +prf(Config) when is_list(Config) -> + TestPlan = proplists:get_value(prf_test_plan, Config), + case TestPlan of + [] -> ct:fail({error, empty_prf_test_plan}); + _ -> lists:foreach(fun(Suite) -> + lists:foreach( + fun(Test) -> + V = proplists:get_value(tls_ver, Test), + C = proplists:get_value(ciphers, Test), + E = proplists:get_value(expected, Test), + P = proplists:get_value(prf, Test), + prf_run_test(Config, V, C, E, P) + end, Suite) + end, TestPlan) + end. + +%%-------------------------------------------------------------------- + connection_info() -> - [{doc,"Test the API function ssl:connection_info/1"}]. + [{doc,"Test the API function ssl:connection_information/1"}]. connection_info(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, connection_info_result, []}}, @@ -325,16 +582,15 @@ connection_info(Config) when is_list(Config) -> {from, self()}, {mfa, {?MODULE, connection_info_result, []}}, {options, - [{ciphers,[{rsa,rc4_128,sha,no_export}]} | + [{ciphers,[{rsa, aes_128_cbc, sha}]} | ClientOpts]}]), ct:log("Testcase ~p, Client ~p Server ~p ~n", [self(), Client, Server]), - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), - ServerMsg = ClientMsg = {ok, {Version, {rsa,rc4_128,sha}}}, + ServerMsg = ClientMsg = {ok, {Version, {rsa, aes_128_cbc, sha}}}, ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), @@ -342,11 +598,43 @@ connection_info(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- + +connection_information() -> + [{doc,"Test the API function ssl:connection_information/1"}]. +connection_information(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_information_result, []}}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_information_result, []}}, + {options, ClientOpts}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ServerMsg = ClientMsg = ok, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- protocol_versions() -> [{doc,"Test to set a list of protocol versions in app environment."}]. protocol_versions(Config) when is_list(Config) -> basic_test(Config). + %%-------------------------------------------------------------------- empty_protocol_versions() -> [{doc,"Test to set an empty list of protocol versions in app environment."}]. @@ -360,8 +648,8 @@ controlling_process() -> [{doc,"Test API function controlling_process/2"}]. controlling_process(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), ClientMsg = "Server hello", ServerMsg = "Client hello", @@ -407,11 +695,80 @@ controlling_process(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +getstat() -> + [{doc,"Test API function getstat/2"}]. + +getstat(Config) when is_list(Config) -> + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server1 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port1 = ssl_test_lib:inet_port(Server1), + Server2 = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port2 = ssl_test_lib:inet_port(Server2), + {ok, ActiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port1,[{active, once}|ClientOpts]]), + {ok, PassiveC} = rpc:call(ClientNode, ssl, connect, + [Hostname,Port2,[{active, false}|ClientOpts]]), + + ct:log("Testcase ~p, Client ~p Servers ~p, ~p ~n", + [self(), self(), Server1, Server2]), + + %% We only check that the values are non-zero initially + %% (due to the handshake), and that sending more changes the values. + + %% Passive socket. + + {ok, InitialStats} = ssl:getstat(PassiveC), + ct:pal("InitialStats ~p~n", [InitialStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + ok = ssl:send(PassiveC, "Hello world"), + wait_for_send(PassiveC), + {ok, SStats} = ssl:getstat(PassiveC, [send_cnt, send_oct]), + ct:pal("SStats ~p~n", [SStats]), + [true] = lists:usort([proplists:get_value(Name, SStats) =/= proplists:get_value(Name, InitialStats) + || Name <- [send_cnt, send_oct]]), + + %% Active socket. + + {ok, InitialAStats} = ssl:getstat(ActiveC), + ct:pal("InitialAStats ~p~n", [InitialAStats]), + [true] = lists:usort([0 =/= proplists:get_value(Name, InitialAStats) + || Name <- [recv_cnt, recv_oct, recv_avg, recv_max, send_cnt, send_oct, send_avg, send_max]]), + + _ = receive + {ssl, ActiveC, _} -> + ok + after + ?SLEEP -> + exit(timeout) + end, + + ok = ssl:send(ActiveC, "Hello world"), + wait_for_send(ActiveC), + {ok, ASStats} = ssl:getstat(ActiveC, [send_cnt, send_oct]), + ct:pal("ASStats ~p~n", [ASStats]), + [true] = lists:usort([proplists:get_value(Name, ASStats) =/= proplists:get_value(Name, InitialAStats) + || Name <- [send_cnt, send_oct]]), + + ok. + +%%-------------------------------------------------------------------- controller_dies() -> [{doc,"Test that the socket is closed after controlling process dies"}]. controller_dies(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), ClientMsg = "Hello server", ServerMsg = "Hello client", @@ -499,11 +856,11 @@ controller_dies(Config) when is_list(Config) -> ssl_test_lib:close(LastClient). %%-------------------------------------------------------------------- -client_closes_socket() -> +tls_client_closes_socket() -> [{doc,"Test what happens when client closes socket before handshake is compleated"}]. -client_closes_socket(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), +tls_client_closes_socket(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], @@ -530,9 +887,9 @@ connect_dist() -> [{doc,"Test a simple connect as is used by distribution"}]. connect_dist(Config) when is_list(Config) -> - ClientOpts0 = ?config(client_kc_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_kc_opts, Config), ClientOpts = [{ssl_imp, new},{active, false}, {packet,4}|ClientOpts0], - ServerOpts0 = ?config(server_kc_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_kc_opts, Config), ServerOpts = [{ssl_imp, new},{active, false}, {packet,4}|ServerOpts0], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -562,31 +919,73 @@ clear_pem_cache(Config) when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - [_,FilRefDb, _] = element(5, State), + [_,FilRefDb |_] = element(6, State), {Server, Client} = basic_verify_test_no_close(Config), - 2 = ets:info(FilRefDb, size), + CountReferencedFiles = fun({_,-1}, Acc) -> + Acc; + ({_, N}, Acc) -> + N + Acc + end, + + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), ssl:clear_pem_cache(), _ = sys:get_status(whereis(ssl_manager)), {Server1, Client1} = basic_verify_test_no_close(Config), - 4 = ets:info(FilRefDb, size), + 4 = ets:foldl(CountReferencedFiles, 0, FilRefDb), ssl_test_lib:close(Server), ssl_test_lib:close(Client), - ct:sleep(5000), + ct:sleep(2000), _ = sys:get_status(whereis(ssl_manager)), - 2 = ets:info(FilRefDb, size), + 2 = ets:foldl(CountReferencedFiles, 0, FilRefDb), ssl_test_lib:close(Server1), ssl_test_lib:close(Client1), - ct:sleep(5000), + ct:sleep(2000), _ = sys:get_status(whereis(ssl_manager)), - 0 = ets:info(FilRefDb, size). + 0 = ets:foldl(CountReferencedFiles, 0, FilRefDb). + +%%-------------------------------------------------------------------- + +fallback() -> + [{doc, "Test TLS_FALLBACK_SCSV downgrade prevention"}]. + +fallback(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + + Port = ssl_test_lib:inet_port(Server), + + Client = + ssl_test_lib:start_client_error([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {from, self()}, {options, + [{fallback, true}, + {versions, ['tlsv1']} + | ClientOpts]}]), + + ssl_test_lib:check_result(Server, {error,{tls_alert,"inappropriate fallback"}}, + Client, {error,{tls_alert,"inappropriate fallback"}}). %%-------------------------------------------------------------------- +cipher_format() -> + [{doc, "Test that cipher conversion from tuples to binarys works"}]. +cipher_format(Config) when is_list(Config) -> + {ok, Socket} = ssl:listen(0, [{ciphers, ssl:cipher_suites()}]), + ssl:close(Socket). + +%%-------------------------------------------------------------------- + peername() -> [{doc,"Test API function peername/1"}]. peername(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -617,8 +1016,8 @@ peername(Config) when is_list(Config) -> peercert() -> [{doc,"Test API function peercert/1"}]. peercert(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, @@ -653,8 +1052,8 @@ peercert_result(Socket) -> peercert_with_client_cert() -> [{doc,"Test API function peercert/1"}]. peercert_with_client_cert(Config) when is_list(Config) -> - ClientOpts = ?config(client_dsa_opts, Config), - ServerOpts = ?config(server_dsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, @@ -688,8 +1087,8 @@ peercert_with_client_cert(Config) when is_list(Config) -> sockname() -> [{doc,"Test API function sockname/1"}]. sockname(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -703,7 +1102,16 @@ sockname(Config) when is_list(Config) -> {options, [{port, 0} | ClientOpts]}]), ClientPort = ssl_test_lib:inet_port(Client), - ServerIp = ssl_test_lib:node_to_hostip(ServerNode), + ServerIp = + case proplists:get_value(protocol, Config) of + dtls -> + %% DTLS sockets are not connected on the server side, + %% so we can only get a ClientIP, ServerIP will always be 0.0.0.0 + {0,0,0,0}; + _ -> + ssl_test_lib:node_to_hostip(ServerNode) + end, + ClientIp = ssl_test_lib:node_to_hostip(ClientNode), ServerMsg = {ok, {ServerIp, Port}}, ClientMsg = {ok, {ClientIp, ClientPort}}, @@ -731,12 +1139,37 @@ cipher_suites(Config) when is_list(Config) -> [_|_] =ssl:cipher_suites(openssl). %%-------------------------------------------------------------------- +cipher_suites_mix() -> + [{doc,"Test to have old and new cipher suites at the same time"}]. + +cipher_suites_mix(Config) when is_list(Config) -> + CipherSuites = [{ecdh_rsa,aes_128_cbc,sha256,sha256}, {rsa,aes_128_cbc,sha}], + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{ciphers, CipherSuites} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- socket_options() -> [{doc,"Test API function getopts/2 and setopts/2"}]. socket_options(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Values = [{mode, list}, {packet, 0}, {header, 0}, {active, true}], @@ -790,8 +1223,8 @@ invalid_inet_get_option() -> [{doc,"Test handling of invalid inet options in getopts"}]. invalid_inet_get_option(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -816,8 +1249,8 @@ invalid_inet_get_option_not_list() -> [{doc,"Test handling of invalid type in getopts"}]. invalid_inet_get_option_not_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -848,8 +1281,8 @@ invalid_inet_get_option_improper_list() -> [{doc,"Test handling of invalid type in getopts"}]. invalid_inet_get_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -879,8 +1312,8 @@ invalid_inet_set_option() -> [{doc,"Test handling of invalid inet options in setopts"}]. invalid_inet_set_option(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -911,8 +1344,8 @@ invalid_inet_set_option_not_list() -> [{doc,"Test handling of invalid type in setopts"}]. invalid_inet_set_option_not_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -943,8 +1376,8 @@ invalid_inet_set_option_improper_list() -> [{doc,"Test handling of invalid tye in setopts"}]. invalid_inet_set_option_improper_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -970,15 +1403,15 @@ set_invalid_inet_option_improper_list(Socket) -> ok. %%-------------------------------------------------------------------- -misc_ssl_options() -> +tls_misc_ssl_options() -> [{doc,"Test what happens when we give valid options"}]. -misc_ssl_options(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_misc_ssl_options(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - %% Chek that ssl options not tested elsewhere are filtered away e.i. not passed to inet. + %% Check that ssl options not tested elsewhere are filtered away e.i. not passed to inet. TestOpts = [{depth, 1}, {key, undefined}, {password, []}, @@ -1006,6 +1439,34 @@ misc_ssl_options(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +ssl_options_not_proplist() -> + [{doc,"Test what happens if an option is not a key value tuple"}]. + +ssl_options_not_proplist(Config) when is_list(Config) -> + BadOption = {client_preferred_next_protocols, + client, [<<"spdy/3">>,<<"http/1.1">>], <<"http/1.1">>}, + {option_not_a_key_value_tuple, BadOption} = + ssl:connect("twitter.com", 443, [binary, {active, false}, + BadOption]). + +%%-------------------------------------------------------------------- +raw_ssl_option() -> + [{doc,"Ensure that a single 'raw' option is passed to ssl:listen correctly."}]. + +raw_ssl_option(Config) when is_list(Config) -> + % 'raw' option values are platform-specific; these are the Linux values: + IpProtoTcp = 6, + % Use TCP_KEEPIDLE, because (e.g.) TCP_MAXSEG can't be read back reliably. + TcpKeepIdle = 4, + KeepAliveTimeSecs = 55, + LOptions = [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}], + {ok, LSocket} = ssl:listen(0, LOptions), + % Per http://www.erlang.org/doc/man/inet.html#getopts-2, we have to specify + % exactly which raw option we want, and the size of the buffer. + {ok, [{raw, IpProtoTcp, TcpKeepIdle, <<KeepAliveTimeSecs:32/native>>}]} = ssl:getopts(LSocket, [{raw, IpProtoTcp, TcpKeepIdle, 4}]). + + +%%-------------------------------------------------------------------- versions() -> [{doc,"Test API function versions/0"}]. @@ -1017,8 +1478,8 @@ versions(Config) when is_list(Config) -> send_recv() -> [{doc,""}]. send_recv(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1042,11 +1503,11 @@ send_recv(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -send_close() -> +tls_send_close() -> [{doc,""}]. -send_close(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_send_close(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1067,11 +1528,18 @@ send_close(Config) when is_list(Config) -> {error, _} = ssl:send(SslS, "Hello world"). %%-------------------------------------------------------------------- +version_option() -> + [{doc, "Use version option and do no specify ciphers list. Bug specified incorrect ciphers"}]. +version_option(Config) when is_list(Config) -> + Versions = proplists:get_value(supported, ssl:versions()), + [version_option_test(Config, Version) || Version <- Versions]. + +%%-------------------------------------------------------------------- close_transport_accept() -> [{doc,"Tests closing ssl socket when waiting on ssl:transport_accept/1"}]. close_transport_accept(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), Port = 0, @@ -1087,15 +1555,66 @@ close_transport_accept(Config) when is_list(Config) -> Other -> exit({?LINE, Other}) end. +%%-------------------------------------------------------------------- +recv_active() -> + [{doc,"Test recv on active socket"}]. + +recv_active(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active, []}}, + {options, [{active, true} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +recv_active_once() -> + [{doc,"Test recv on active socket"}]. + +recv_active_once(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, once} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). %%-------------------------------------------------------------------- dh_params() -> [{doc,"Test to specify DH-params file in server."}]. dh_params(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), - DataDir = ?config(data_dir, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + DataDir = proplists:get_value(data_dir, Config), DHParamFile = filename:join(DataDir, "dHParam.pem"), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1110,7 +1629,7 @@ dh_params(Config) when is_list(Config) -> {from, self()}, {mfa, {ssl_test_lib, send_recv_result_active, []}}, {options, - [{ciphers,[{dhe_rsa,aes_256_cbc,sha,ignore}]} | + [{ciphers,[{dhe_rsa,aes_256_cbc,sha}]} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), @@ -1119,12 +1638,12 @@ dh_params(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -upgrade() -> +tls_upgrade() -> [{doc,"Test that you can upgrade an tcp connection to an ssl connection"}]. -upgrade(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_upgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], @@ -1168,12 +1687,12 @@ upgrade_result(Socket) -> end. %%-------------------------------------------------------------------- -upgrade_with_timeout() -> +tls_upgrade_with_timeout() -> [{doc,"Test ssl_accept/3"}]. -upgrade_with_timeout(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_upgrade_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], @@ -1203,13 +1722,60 @@ upgrade_with_timeout(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -tcp_connect() -> +tls_downgrade() -> + [{doc,"Test that you can downgarde an ssl connection to an tcp connection"}]. +tls_downgrade(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_downgrade_result, []}}, + {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +close_with_timeout() -> + [{doc,"Test normal (not downgrade) ssl:close/2"}]. +close_with_timeout(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options,[{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, tls_close, []}}, + {options, [{active, false} |ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok). + + +%%-------------------------------------------------------------------- +tls_tcp_connect() -> [{doc,"Test what happens when a tcp tries to connect, i,e. a bad (ssl) packet is sent first"}]. -tcp_connect(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), +tls_tcp_connect(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - TcpOpts = [binary, {reuseaddr, true}], + TcpOpts = [binary, {reuseaddr, true}, {active, false}], Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -1231,14 +1797,16 @@ tcp_connect(Config) when is_list(Config) -> end end. %%-------------------------------------------------------------------- -tcp_connect_big() -> +tls_tcp_connect_big() -> [{doc,"Test what happens when a tcp tries to connect, i,e. a bad big (ssl) packet is sent first"}]. -tcp_connect_big(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), +tls_tcp_connect_big(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], + Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {timeout, 5000}, @@ -1250,7 +1818,6 @@ tcp_connect_big(Config) when is_list(Config) -> {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {packet, 0}]), ct:log("Testcase ~p connected to Server ~p ~n", [self(), Server]), - Rand = crypto:rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), gen_tcp:send(Socket, <<?BYTE(0), ?BYTE(3), ?BYTE(1), ?UINT16(?MAX_CIPHER_TEXT_LENGTH), Rand/binary>>), @@ -1260,7 +1827,9 @@ tcp_connect_big(Config) when is_list(Config) -> {Server, {error, timeout}} -> ct:fail("hangs"); {Server, {error, Error}} -> - ct:log("Error ~p", [Error]) + ct:log("Error ~p", [Error]); + {'EXIT', Server, _} -> + ok end end. @@ -1273,8 +1842,8 @@ ipv6(Config) when is_list(Config) -> case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts)) of true -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config, ipv6), Server = ssl_test_lib:start_server([{node, ServerNode}, @@ -1306,8 +1875,8 @@ ipv6(Config) when is_list(Config) -> invalid_keyfile() -> [{doc,"Test what happens with an invalid key file"}]. invalid_keyfile(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - BadOpts = ?config(server_bad_key, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + BadOpts = ssl_test_lib:ssl_options(server_bad_key, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -1332,8 +1901,8 @@ invalid_certfile() -> [{doc,"Test what happens with an invalid cert file"}]. invalid_certfile(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerBadOpts = ?config(server_bad_cert, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerBadOpts = ssl_test_lib:ssl_options(server_bad_cert, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -1358,8 +1927,8 @@ invalid_cacertfile() -> [{doc,"Test what happens with an invalid cacert file"}]. invalid_cacertfile(Config) when is_list(Config) -> - ClientOpts = [{reuseaddr, true}|?config(client_opts, Config)], - ServerBadOpts = [{reuseaddr, true}|?config(server_bad_ca, Config)], + ClientOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(client_opts, Config)], + ServerBadOpts = [{reuseaddr, true}|ssl_test_lib:ssl_options(server_bad_ca, Config)], {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server0 = @@ -1409,8 +1978,8 @@ invalid_options() -> [{doc,"Test what happens when we give invalid options"}]. invalid_options(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Check = fun(Client, Server, {versions, [sslv2, sslv3]} = Option) -> @@ -1463,15 +2032,15 @@ invalid_options(Config) when is_list(Config) -> ok. %%-------------------------------------------------------------------- -shutdown() -> +tls_shutdown() -> [{doc,"Test API function ssl:shutdown/2"}]. -shutdown(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_shutdown(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, shutdown_result, [server]}}, + {mfa, {?MODULE, tls_shutdown_result, [server]}}, {options, [{exit_on_close, false}, {active, false} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), @@ -1479,7 +2048,7 @@ shutdown(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, - {?MODULE, shutdown_result, [client]}}, + {?MODULE, tls_shutdown_result, [client]}}, {options, [{exit_on_close, false}, {active, false} | ClientOpts]}]), @@ -1490,50 +2059,50 @@ shutdown(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -shutdown_write() -> +tls_shutdown_write() -> [{doc,"Test API function ssl:shutdown/2 with option write."}]. -shutdown_write(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_shutdown_write(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, shutdown_write_result, [server]}}, + {mfa, {?MODULE, tls_shutdown_write_result, [server]}}, {options, [{active, false} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {?MODULE, shutdown_write_result, [client]}}, + {mfa, {?MODULE, tls_shutdown_write_result, [client]}}, {options, [{active, false} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, {error, closed}). %%-------------------------------------------------------------------- -shutdown_both() -> +tls_shutdown_both() -> [{doc,"Test API function ssl:shutdown/2 with option both."}]. -shutdown_both(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_shutdown_both(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, shutdown_both_result, [server]}}, + {mfa, {?MODULE, tls_shutdown_both_result, [server]}}, {options, [{active, false} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {?MODULE, shutdown_both_result, [client]}}, + {mfa, {?MODULE, tls_shutdown_both_result, [client]}}, {options, [{active, false} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, {error, closed}). %%-------------------------------------------------------------------- -shutdown_error() -> +tls_shutdown_error() -> [{doc,"Test ssl:shutdown/2 error handling"}]. -shutdown_error(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), +tls_shutdown_error(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), Port = ssl_test_lib:inet_port(node()), {ok, Listen} = ssl:listen(Port, ServerOpts), {error, enotconn} = ssl:shutdown(Listen, read_write), @@ -1545,9 +2114,7 @@ ciphers_rsa_signed_certs() -> [{doc,"Test all rsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_rsa_signed_certs(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), - + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:rsa_suites(crypto), ct:log("~p erlang cipher suites ~p~n", [Version, Ciphers]), run_suites(Ciphers, Version, Config, rsa). @@ -1556,8 +2123,7 @@ ciphers_rsa_signed_certs_openssl_names() -> [{doc,"Test all rsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:openssl_rsa_suites(crypto), ct:log("tls1 openssl cipher suites ~p~n", [Ciphers]), run_suites(Ciphers, Version, Config, rsa). @@ -1567,9 +2133,7 @@ ciphers_dsa_signed_certs() -> [{doc,"Test all dsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_dsa_signed_certs(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), - + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:dsa_suites(), ct:log("~p erlang cipher suites ~p~n", [Version, Ciphers]), run_suites(Ciphers, Version, Config, dsa). @@ -1578,9 +2142,7 @@ ciphers_dsa_signed_certs_openssl_names() -> [{doc,"Test all dsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_dsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), - + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:openssl_dsa_suites(), ct:log("tls1 openssl cipher suites ~p~n", [Ciphers]), run_suites(Ciphers, Version, Config, dsa). @@ -1588,65 +2150,108 @@ ciphers_dsa_signed_certs_openssl_names(Config) when is_list(Config) -> anonymous_cipher_suites()-> [{doc,"Test the anonymous ciphersuites"}]. anonymous_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:anonymous_suites(), run_suites(Ciphers, Version, Config, anonymous). %%------------------------------------------------------------------- psk_cipher_suites() -> [{doc, "Test the PSK ciphersuites WITHOUT server supplied identity hint"}]. psk_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:psk_suites(), run_suites(Ciphers, Version, Config, psk). %%------------------------------------------------------------------- psk_with_hint_cipher_suites()-> [{doc, "Test the PSK ciphersuites WITH server supplied identity hint"}]. psk_with_hint_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:psk_suites(), run_suites(Ciphers, Version, Config, psk_with_hint). %%------------------------------------------------------------------- psk_anon_cipher_suites() -> [{doc, "Test the anonymous PSK ciphersuites WITHOUT server supplied identity hint"}]. psk_anon_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:psk_anon_suites(), run_suites(Ciphers, Version, Config, psk_anon). %%------------------------------------------------------------------- psk_anon_with_hint_cipher_suites()-> [{doc, "Test the anonymous PSK ciphersuites WITH server supplied identity hint"}]. psk_anon_with_hint_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:psk_anon_suites(), run_suites(Ciphers, Version, Config, psk_anon_with_hint). %%------------------------------------------------------------------- srp_cipher_suites()-> [{doc, "Test the SRP ciphersuites"}]. srp_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:srp_suites(), run_suites(Ciphers, Version, Config, srp). %%------------------------------------------------------------------- srp_anon_cipher_suites()-> [{doc, "Test the anonymous SRP ciphersuites"}]. srp_anon_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:srp_anon_suites(), run_suites(Ciphers, Version, Config, srp_anon). %%------------------------------------------------------------------- srp_dsa_cipher_suites()-> [{doc, "Test the SRP DSA ciphersuites"}]. srp_dsa_cipher_suites(Config) when is_list(Config) -> - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:srp_dss_suites(), run_suites(Ciphers, Version, Config, srp_dsa). +%%------------------------------------------------------------------- +rc4_rsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +rc4_rsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:rc4_suites(NVersion), + run_suites(Ciphers, Version, Config, rc4_rsa). +%------------------------------------------------------------------- +rc4_ecdh_rsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +rc4_ecdh_rsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:rc4_suites(NVersion), + run_suites(Ciphers, Version, Config, rc4_ecdh_rsa). + +%%------------------------------------------------------------------- +rc4_ecdsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +rc4_ecdsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:rc4_suites(NVersion), + run_suites(Ciphers, Version, Config, rc4_ecdsa). + +%%------------------------------------------------------------------- +des_rsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +des_rsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:des_suites(NVersion), + run_suites(Ciphers, Version, Config, des_rsa). +%------------------------------------------------------------------- +des_ecdh_rsa_cipher_suites()-> + [{doc, "Test the RC4 ciphersuites"}]. +des_ecdh_rsa_cipher_suites(Config) when is_list(Config) -> + NVersion = tls_record:highest_protocol_version([]), + Version = tls_record:protocol_version(NVersion), + Ciphers = ssl_test_lib:des_suites(NVersion), + run_suites(Ciphers, Version, Config, des_dhe_rsa). + %%-------------------------------------------------------------------- default_reject_anonymous()-> [{doc,"Test that by default anonymous cipher suites are rejected "}]. default_reject_anonymous(Config) when is_list(Config) -> {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), [Cipher | _] = ssl_test_lib:anonymous_suites(), @@ -1669,9 +2274,7 @@ ciphers_ecdsa_signed_certs() -> [{doc, "Test all ecdsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_ecdsa_signed_certs(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), - + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:ecdsa_suites(), ct:log("~p erlang cipher suites ~p~n", [Version, Ciphers]), run_suites(Ciphers, Version, Config, ecdsa). @@ -1680,8 +2283,7 @@ ciphers_ecdsa_signed_certs_openssl_names() -> [{doc, "Test all ecdsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_ecdsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:openssl_ecdsa_suites(), ct:log("tls1 openssl cipher suites ~p~n", [Ciphers]), run_suites(Ciphers, Version, Config, ecdsa). @@ -1690,9 +2292,7 @@ ciphers_ecdh_rsa_signed_certs() -> [{doc, "Test all ecdh_rsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_ecdh_rsa_signed_certs(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), - + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:ecdh_rsa_suites(), ct:log("~p erlang cipher suites ~p~n", [Version, Ciphers]), run_suites(Ciphers, Version, Config, ecdh_rsa). @@ -1701,8 +2301,7 @@ ciphers_ecdh_rsa_signed_certs_openssl_names() -> [{doc, "Test all ecdh_rsa ssl cipher suites in highest support ssl/tls version"}]. ciphers_ecdh_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:openssl_ecdh_rsa_suites(), ct:log("tls1 openssl cipher suites ~p~n", [Ciphers]), run_suites(Ciphers, Version, Config, ecdh_rsa). @@ -1710,8 +2309,8 @@ ciphers_ecdh_rsa_signed_certs_openssl_names(Config) when is_list(Config) -> reuse_session() -> [{doc,"Test reuse of sessions (short handshake)"}]. reuse_session(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -1818,8 +2417,8 @@ reuse_session(Config) when is_list(Config) -> reuse_session_expired() -> [{doc,"Test sessions is not reused when it has expired"}]. reuse_session_expired(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -1903,8 +2502,8 @@ make_sure_expired(Host, Port, Id) -> server_does_not_want_to_reuse_session() -> [{doc,"Test reuse of sessions (short handshake)"}]. server_does_not_want_to_reuse_session(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -1952,8 +2551,8 @@ server_does_not_want_to_reuse_session(Config) when is_list(Config) -> client_renegotiate() -> [{doc,"Test ssl:renegotiate/1 on client."}]. client_renegotiate(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -1978,11 +2577,42 @@ client_renegotiate(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +client_secure_renegotiate() -> + [{doc,"Test ssl:renegotiate/1 on client."}]. +client_secure_renegotiate(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From erlang to erlang", + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options, [{secure_renegotiate, true} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate, [Data]}}, + {options, [{reuse_sessions, false}, + {secure_renegotiate, true}| ClientOpts]}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- server_renegotiate() -> [{doc,"Test ssl:renegotiate/1 on server."}]. server_renegotiate(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2009,8 +2639,8 @@ server_renegotiate(Config) when is_list(Config) -> client_renegotiate_reused_session() -> [{doc,"Test ssl:renegotiate/1 on client when the ssl session will be reused."}]. client_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2037,8 +2667,8 @@ client_renegotiate_reused_session(Config) when is_list(Config) -> server_renegotiate_reused_session() -> [{doc,"Test ssl:renegotiate/1 on server when the ssl session will be reused."}]. server_renegotiate_reused_session(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2068,13 +2698,13 @@ client_no_wrap_sequence_number() -> " to lower treashold substantially."}]. client_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), ErlData = "From erlang to erlang", - N = 10, + N = 12, Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -2083,7 +2713,7 @@ client_no_wrap_sequence_number(Config) when is_list(Config) -> {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Version = tls_record:highest_protocol_version(tls_record:supported_protocol_versions()), + Version = ssl_test_lib:protocol_version(Config, tuple), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -2105,13 +2735,13 @@ server_no_wrap_sequence_number() -> " to lower treashold substantially."}]. server_no_wrap_sequence_number(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From erlang to erlang", - N = 10, + N = 12, Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -2135,13 +2765,20 @@ der_input() -> [{doc,"Test to input certs and key as der"}]. der_input(Config) when is_list(Config) -> - DataDir = ?config(data_dir, Config), + DataDir = proplists:get_value(data_dir, Config), DHParamFile = filename:join(DataDir, "dHParam.pem"), - SeverVerifyOpts = ?config(server_verification_opts, Config), + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + [CADb | _] = element(6, State), + + Size = ets:info(CADb, size), + + SeverVerifyOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ServerCert, ServerKey, ServerCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | SeverVerifyOpts]), - ClientVerifyOpts = ?config(client_verification_opts, Config), + ClientVerifyOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), {ClientCert, ClientKey, ClientCaCerts, DHParams} = der_input_opts([{dhfile, DHParamFile} | ClientVerifyOpts]), ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true}, @@ -2164,7 +2801,9 @@ der_input(Config) when is_list(Config) -> ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), - ssl_test_lib:close(Client). + ssl_test_lib:close(Client), + Size = ets:info(CADb, size). + %%-------------------------------------------------------------------- der_input_opts(Opts) -> Certfile = proplists:get_value(certfile, Opts), @@ -2186,8 +2825,8 @@ der_input_opts(Opts) -> %% ["Check that a CA can have a different signature algorithm than the peer cert."]; %% different_ca_peer_sign(Config) when is_list(Config) -> -%% ClientOpts = ?config(client_mix_opts, Config), -%% ServerOpts = ?config(server_mix_verify_opts, Config), +%% ClientOpts = ssl_test_lib:ssl_options(client_mix_opts, Config), +%% ServerOpts = ssl_test_lib:ssl_options(server_mix_verify_opts, Config), %% {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), %% Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -2217,9 +2856,9 @@ no_reuses_session_server_restart_new_cert() -> [{doc,"Check that a session is not reused if the server is restarted with a new cert."}]. no_reuses_session_server_restart_new_cert(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), - DsaServerOpts = ?config(server_dsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -2275,10 +2914,10 @@ no_reuses_session_server_restart_new_cert_file() -> "cert contained in a file with the same name as the old cert."}]. no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - DsaServerOpts = ?config(server_dsa_opts, Config), - PrivDir = ?config(priv_dir, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + DsaServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), NewServerOpts = new_config(PrivDir, ServerOpts), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2330,12 +2969,27 @@ no_reuses_session_server_restart_new_cert_file(Config) when is_list(Config) -> ssl_test_lib:close(Client1). %%-------------------------------------------------------------------- +defaults(Config) when is_list(Config)-> + [_, + {supported, Supported}, + {available, Available}] + = ssl:versions(), + true = lists:member(sslv3, Available), + false = lists:member(sslv3, Supported), + false = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites()), + true = lists:member({rsa,rc4_128,sha}, ssl:cipher_suites(all)), + false = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({rsa,des_cbc,sha}, ssl:cipher_suites(all)), + false = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites()), + true = lists:member({dhe_rsa,des_cbc,sha}, ssl:cipher_suites(all)). + +%%-------------------------------------------------------------------- reuseaddr() -> [{doc,"Test reuseaddr option"}]. reuseaddr(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -2369,9 +3023,9 @@ reuseaddr(Config) when is_list(Config) -> ssl_test_lib:close(Client1). %%-------------------------------------------------------------------- -tcp_reuseaddr() -> +tls_tcp_reuseaddr() -> [{doc, "Reference test case."}]. -tcp_reuseaddr(Config) when is_list(Config) -> +tls_tcp_reuseaddr(Config) when is_list(Config) -> {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -2410,14 +3064,189 @@ tcp_reuseaddr(Config) when is_list(Config) -> %%-------------------------------------------------------------------- +honor_server_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_server_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [{rsa, aes_128_cbc, sha}, {rsa, aes_256_cbc, sha}], + ServerCiphers = [{rsa, aes_256_cbc, sha}, {rsa, aes_128_cbc, sha}], +honor_cipher_order(Config, true, ServerCiphers, ClientCiphers, {rsa, aes_256_cbc, sha}). + +honor_client_cipher_order() -> + [{doc,"Test API honor server cipher order."}]. +honor_client_cipher_order(Config) when is_list(Config) -> + ClientCiphers = [{rsa, aes_128_cbc, sha}, {rsa, aes_256_cbc, sha}], + ServerCiphers = [{rsa, aes_256_cbc, sha}, {rsa, aes_128_cbc, sha}], +honor_cipher_order(Config, false, ServerCiphers, ClientCiphers, {rsa, aes_128_cbc, sha}). + +honor_cipher_order(Config, Honor, ServerCiphers, ClientCiphers, Expected) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, ServerCiphers}, {honor_cipher_order, Honor} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{ciphers, ClientCiphers}, {honor_cipher_order, Honor} + | ClientOpts]}]), + + Version = ssl_test_lib:protocol_version(Config), + + ServerMsg = ClientMsg = {ok, {Version, Expected}}, + + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_ciphersuite_vs_version() -> + [{doc,"Test a SSLv3 client can not negotiate a TLSv* cipher suite."}]. +tls_ciphersuite_vs_version(Config) when is_list(Config) -> + + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + ok = gen_tcp:send(Socket, + <<22, 3,0, 49:16, % handshake, SSL 3.0, length + 1, 45:24, % client_hello, length + 3,0, % SSL 3.0 + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, % no session ID + %% three cipher suites -- null, one with sha256 hash and one with sha hash + 6:16, 0,255, 0,61, 0,57, + 1, 0 % no compression + >>), + {ok, <<22, RecMajor:8, RecMinor:8, _RecLen:16, 2, HelloLen:24>>} = gen_tcp:recv(Socket, 9, 10000), + {ok, <<HelloBin:HelloLen/binary>>} = gen_tcp:recv(Socket, HelloLen, 5000), + ServerHello = tls_handshake:decode_handshake({RecMajor, RecMinor}, 2, HelloBin, false), + case ServerHello of + #server_hello{server_version = {3,0}, cipher_suite = <<0,57>>} -> + ok; + _ -> + ct:fail({unexpected_server_hello, ServerHello}) + end. + +%%-------------------------------------------------------------------- +conf_signature_algs() -> + [{doc,"Test to set the signature_algs option on both client and server"}]. +conf_signature_algs(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {signature_algs, [{sha256, rsa}]} | ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + +%%-------------------------------------------------------------------- +no_common_signature_algs() -> + [{doc,"Set the signature_algs option so that there client and server does not share any hash sign algorithms"}]. +no_common_signature_algs(Config) when is_list(Config) -> + + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, [{signature_algs, [{sha256, rsa}]} + | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, [{signature_algs, [{sha384, rsa}]} + | ClientOpts]}]), + + ssl_test_lib:check_result(Server, {error, {tls_alert, "insufficient security"}}, + Client, {error, {tls_alert, "insufficient security"}}). + +%%-------------------------------------------------------------------- + +tls_dont_crash_on_handshake_garbage() -> + [{doc, "Ensure SSL server worker thows an alert on garbage during handshake " + "instead of crashing and exposing state to user code"}]. + +tls_dont_crash_on_handshake_garbage(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {_ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + unlink(Server), monitor(process, Server), + Port = ssl_test_lib:inet_port(Server), + + {ok, Socket} = gen_tcp:connect(Hostname, Port, [binary, {active, false}]), + + % Send hello and garbage record + ok = gen_tcp:send(Socket, + [<<22, 3,3, 49:16, 1, 45:24, 3,3, % client_hello + 16#deadbeef:256, % 32 'random' bytes = 256 bits + 0, 6:16, 0,255, 0,61, 0,57, 1, 0 >>, % some hello values + + <<22, 3,3, 5:16, 92,64,37,228,209>> % garbage + ]), + % Send unexpected change_cipher_spec + ok = gen_tcp:send(Socket, <<20, 0,0,12, 111,40,244,7,137,224,16,109,197,110,249,152>>), + + % Ensure we receive an alert, not sudden disconnect + {ok, <<21, _/binary>>} = drop_handshakes(Socket, 1000). + +drop_handshakes(Socket, Timeout) -> + {ok, <<RecType:8, _RecMajor:8, _RecMinor:8, RecLen:16>> = Header} = gen_tcp:recv(Socket, 5, Timeout), + {ok, <<Frag:RecLen/binary>>} = gen_tcp:recv(Socket, RecLen, Timeout), + case RecType of + 22 -> drop_handshakes(Socket, Timeout); + _ -> {ok, <<Header/binary, Frag/binary>>} + end. + + +%%-------------------------------------------------------------------- + hibernate() -> [{doc,"Check that an SSL connection that is started with option " "{hibernate_after, 1000} indeed hibernates after 1000ms of " "inactivity"}]. hibernate(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2435,6 +3264,7 @@ hibernate(Config) -> {current_function, _} = process_info(Pid, current_function), + ssl_test_lib:check_result(Server, ok, Client, ok), timer:sleep(1100), {current_function, {erlang, hibernate, 3}} = @@ -2444,11 +3274,62 @@ hibernate(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- + +hibernate_right_away() -> + [{doc,"Check that an SSL connection that is configured to hibernate " + "after 0 or 1 milliseconds hibernates as soon as possible and not " + "crashes"}]. + +hibernate_right_away(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + StartServerOpts = [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}], + StartClientOpts = [return_socket, + {node, ClientNode}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}], + + Server1 = ssl_test_lib:start_server(StartServerOpts), + Port1 = ssl_test_lib:inet_port(Server1), + {Client1, #sslsocket{pid = Pid1}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]), + + ssl_test_lib:check_result(Server1, ok, Client1, ok), + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid1, current_function), + + ssl_test_lib:close(Server1), + ssl_test_lib:close(Client1), + + Server2 = ssl_test_lib:start_server(StartServerOpts), + Port2 = ssl_test_lib:inet_port(Server2), + {Client2, #sslsocket{pid = Pid2}} = ssl_test_lib:start_client(StartClientOpts ++ + [{port, Port2}, {options, [{hibernate_after, 1}|ClientOpts]}]), + + ssl_test_lib:check_result(Server2, ok, Client2, ok), + + ct:sleep(100), %% Schedule out + + {current_function, {erlang, hibernate, 3}} = + process_info(Pid2, current_function), + + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client2). + +%%-------------------------------------------------------------------- listen_socket() -> [{doc,"Check error handling and inet compliance when calling API functions with listen sockets."}]. listen_socket(Config) -> - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ok, ListenSocket} = ssl:listen(0, ServerOpts), %% This can be a valid thing to do as @@ -2459,7 +3340,7 @@ listen_socket(Config) -> {error, enotconn} = ssl:send(ListenSocket, <<"data">>), {error, enotconn} = ssl:recv(ListenSocket, 0), - {error, enotconn} = ssl:connection_info(ListenSocket), + {error, enotconn} = ssl:connection_information(ListenSocket), {error, enotconn} = ssl:peername(ListenSocket), {error, enotconn} = ssl:peercert(ListenSocket), {error, enotconn} = ssl:session_info(ListenSocket), @@ -2469,12 +3350,12 @@ listen_socket(Config) -> ok = ssl:close(ListenSocket). %%-------------------------------------------------------------------- -ssl_accept_timeout() -> +tls_ssl_accept_timeout() -> [{doc,"Test ssl:ssl_accept timeout"}]. -ssl_accept_timeout(Config) -> +tls_ssl_accept_timeout(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -2490,7 +3371,10 @@ ssl_accept_timeout(Config) -> ssl_test_lib:check_result(Server, {error, timeout}), receive {'EXIT', Server, _} -> - [] = supervisor:which_children(ssl_connection_sup) + %% Make sure supervisor had time to react on process exit + %% Could we come up with a better solution to this? + ct:sleep(500), + [] = supervisor:which_children(tls_connection_sup) end end. @@ -2499,8 +3383,8 @@ ssl_recv_timeout() -> [{doc,"Test ssl:ssl_accept timeout"}]. ssl_recv_timeout(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2526,8 +3410,8 @@ ssl_recv_timeout(Config) -> connect_twice() -> [{doc,""}]. connect_twice(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2571,8 +3455,8 @@ renegotiate_dos_mitigate_active() -> [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", "immediately after each other"}]. renegotiate_dos_mitigate_active(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2580,7 +3464,7 @@ renegotiate_dos_mitigate_active(Config) when is_list(Config) -> ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [ServerOpts]}]), + {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, @@ -2599,8 +3483,8 @@ renegotiate_dos_mitigate_passive() -> [{doc, "Mitigate DOS computational attack by not allowing client to renegotiate many times in a row", "immediately after each other"}]. renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2623,11 +3507,39 @@ renegotiate_dos_mitigate_passive(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -tcp_error_propagation_in_active_mode() -> +renegotiate_dos_mitigate_absolute() -> + [{doc, "Mitigate DOS computational attack by not allowing client to initiate renegotiation"}]. +renegotiate_dos_mitigate_absolute(Config) when is_list(Config) -> + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{client_renegotiation, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + renegotiate_rejected, + []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +tls_tcp_error_propagation_in_active_mode() -> [{doc,"Test that process recives {ssl_error, Socket, closed} when tcp error ocurres"}]. -tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2646,7 +3558,7 @@ tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(Pid), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - Socket = element(10, State), + Socket = element(11, State), %% Fake tcp error Pid ! {tcp_error, Socket, etimedout}, @@ -2657,8 +3569,8 @@ tcp_error_propagation_in_active_mode(Config) when is_list(Config) -> recv_error_handling() -> [{doc,"Special case of call error handling"}]. recv_error_handling(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -2683,7 +3595,7 @@ rizzo() -> rizzo(Config) when is_list(Config) -> Ciphers = [X || X ={_,Y,_} <- ssl:cipher_suites(), Y =/= rc4_128], - Prop = ?config(tc_group_properties, Config), + Prop = proplists:get_value(tc_group_properties, Config), Version = proplists:get_value(name, Prop), run_send_recv_rizzo(Ciphers, Config, Version, {?MODULE, send_recv_result_active_rizzo, []}). @@ -2693,20 +3605,50 @@ no_rizzo_rc4() -> no_rizzo_rc4(Config) when is_list(Config) -> Ciphers = [X || X ={_,Y,_} <- ssl:cipher_suites(),Y == rc4_128], - Prop = ?config(tc_group_properties, Config), + Prop = proplists:get_value(tc_group_properties, Config), Version = proplists:get_value(name, Prop), run_send_recv_rizzo(Ciphers, Config, Version, {?MODULE, send_recv_result_active_no_rizzo, []}). +rizzo_one_n_minus_one() -> + [{doc,"Test that the 1/n-1-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. + +rizzo_one_n_minus_one(Config) when is_list(Config) -> + Ciphers = [X || X ={_,Y,_} <- ssl:cipher_suites(), Y =/= rc4_128], + Prop = proplists:get_value(tc_group_properties, Config), + Version = proplists:get_value(name, Prop), + run_send_recv_rizzo(Ciphers, Config, Version, + {?MODULE, send_recv_result_active_rizzo, []}). + +rizzo_zero_n() -> + [{doc,"Test that the 0/n-split mitigation of Rizzo/Dungon attack can be explicitly selected"}]. + +rizzo_zero_n(Config) when is_list(Config) -> + Ciphers = [X || X ={_,Y,_} <- ssl:cipher_suites(), Y =/= rc4_128], + Prop = proplists:get_value(tc_group_properties, Config), + Version = proplists:get_value(name, Prop), + run_send_recv_rizzo(Ciphers, Config, Version, + {?MODULE, send_recv_result_active_no_rizzo, []}). + +rizzo_disabled() -> + [{doc,"Test that the mitigation of Rizzo/Dungon attack can be explicitly disabled"}]. + +rizzo_disabled(Config) when is_list(Config) -> + Ciphers = [X || X ={_,Y,_} <- ssl:cipher_suites(), Y =/= rc4_128], + Prop = proplists:get_value(tc_group_properties, Config), + Version = proplists:get_value(name, Prop), + run_send_recv_rizzo(Ciphers, Config, Version, + {?MODULE, send_recv_result_active_no_rizzo, []}). + %%-------------------------------------------------------------------- new_server_wants_peer_cert() -> [{doc, "Test that server configured to do client certification does" " not reuse session without a client certificate."}]. new_server_wants_peer_cert(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), VServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ?config(server_verification_opts, Config)], - ClientOpts = ?config(client_verification_opts, Config), + | ssl_test_lib:ssl_options(server_verification_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2767,11 +3709,11 @@ session_cache_process_mnesia(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -versions_option() -> +tls_versions_option() -> [{doc,"Test API versions option to connect/listen."}]. -versions_option(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), +tls_versions_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), Supported = proplists:get_value(supported, ssl:versions()), Available = proplists:get_value(available, ssl:versions()), @@ -2801,6 +3743,145 @@ versions_option(Config) when is_list(Config) -> end, ssl_test_lib:check_result(ErrClient, {error, {tls_alert, "protocol version"}}). + + +%%-------------------------------------------------------------------- +unordered_protocol_versions_server() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. + +unordered_protocol_versions_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, ClientOpts}]), + CipherSuite = first_rsa_suite(ssl:cipher_suites()), + ServerMsg = ClientMsg = {ok, {'tlsv1.2', CipherSuite}}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). + +%%-------------------------------------------------------------------- +unordered_protocol_versions_client() -> + [{doc,"Test that the highest protocol is selected even" + " when it is not first in the versions list."}]. + +unordered_protocol_versions_client(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, ServerOpts }]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, connection_info_result, []}}, + {options, [{versions, ['tlsv1.1', 'tlsv1.2']} | ClientOpts]}]), + + CipherSuite = first_rsa_suite(ssl:cipher_suites()), + ServerMsg = ClientMsg = {ok, {'tlsv1.2', CipherSuite}}, + ssl_test_lib:check_result(Server, ServerMsg, Client, ClientMsg). + +%%-------------------------------------------------------------------- + +server_name_indication_option() -> + [{doc,"Test API server_name_indication option to connect."}]. +server_name_indication_option(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{server_name_indication, disable} | + ClientOpts]} + ]), + + ssl_test_lib:check_result(Server, ok, Client0, ok), + Server ! listen, + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, + [{server_name_indication, Hostname} | ClientOpts] + }]), + ssl_test_lib:check_result(Server, ok, Client1, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1). +%%-------------------------------------------------------------------- + +accept_pool() -> + [{doc,"Test having an accept pool."}]. +accept_pool(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server0 = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {accepters, 3}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server0), + [Server1, Server2] = ssl_test_lib:accepters(2), + + Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client1 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + Client2 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts} + ]), + + ssl_test_lib:check_ok([Server0, Server1, Server2, Client0, Client1, Client2]), + + ssl_test_lib:close(Server0), + ssl_test_lib:close(Server1), + ssl_test_lib:close(Server2), + ssl_test_lib:close(Client0), + ssl_test_lib:close(Client1), + ssl_test_lib:close(Client2). + + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- @@ -2814,8 +3895,8 @@ tcp_send_recv_result(Socket) -> ok. basic_verify_test_no_close(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2834,8 +3915,8 @@ basic_verify_test_no_close(Config) -> {Server, Client}. basic_test(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -2854,8 +3935,84 @@ basic_test(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). +prf_create_plan(TlsVersions, PRFs, Results) -> + lists:foldl(fun(Ver, Acc) -> + A = prf_ciphers_and_expected(Ver, PRFs, Results), + [A|Acc] + end, [], TlsVersions). +prf_ciphers_and_expected(TlsVer, PRFs, Results) -> + case TlsVer of + TlsVer when TlsVer == sslv3 orelse TlsVer == tlsv1 + orelse TlsVer == 'tlsv1.1' -> + Ciphers = ssl:cipher_suites(), + {_, Expected} = lists:keyfind(md5sha, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, {prf, md5sha}]]; + 'tlsv1.2' -> + lists:foldl( + fun(PRF, Acc) -> + Ciphers = prf_get_ciphers(TlsVer, PRF), + case Ciphers of + [] -> + ct:log("No ciphers for PRF algorithm ~p. Skipping.", [PRF]), + Acc; + Ciphers -> + {_, Expected} = lists:keyfind(PRF, 1, Results), + [[{tls_ver, TlsVer}, {ciphers, Ciphers}, {expected, Expected}, + {prf, PRF}] | Acc] + end + end, [], PRFs) + end. +prf_get_ciphers(TlsVer, PRF) -> + case TlsVer of + 'tlsv1.2' -> + lists:filter( + fun(C) when tuple_size(C) == 4 andalso + element(4, C) == PRF -> + true; + (_) -> false + end, ssl:cipher_suites()) + end. +prf_run_test(_, TlsVer, [], _, Prf) -> + ct:fail({error, cipher_list_empty, TlsVer, Prf}); +prf_run_test(Config, TlsVer, Ciphers, Expected, Prf) -> + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + BaseOpts = [{active, true}, {versions, [TlsVer]}, {ciphers, Ciphers}], + ServerOpts = BaseOpts ++ proplists:get_value(server_opts, Config), + ClientOpts = BaseOpts ++ proplists:get_value(client_opts, Config), + Server = ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, {from, self()}, + {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {?MODULE, prf_verify_value, [TlsVer, Expected, Prf]}}, + {options, ClientOpts}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +prf_verify_value(Socket, TlsVer, Expected, Algo) -> + Ret = ssl:prf(Socket, <<>>, <<>>, [<<>>], 16), + case TlsVer of + sslv3 -> + case Ret of + {error, undefined} -> ok; + _ -> + {error, {expected, {error, undefined}, + got, Ret, tls_ver, TlsVer, prf_algorithm, Algo}} + end; + _ -> + case Ret of + {ok, Expected} -> ok; + {ok, Val} -> {error, {expected, Expected, got, Val, tls_ver, TlsVer, + prf_algorithm, Algo}} + end + end. + send_recv_result_timeout_client(Socket) -> {error, timeout} = ssl:recv(Socket, 11, 500), + {error, timeout} = ssl:recv(Socket, 11, 0), ssl:send(Socket, "Hello world"), receive Msg -> @@ -2919,23 +4076,55 @@ renegotiate_reuse_session(Socket, Data) -> renegotiate(Socket, Data). renegotiate_immediately(Socket) -> - receive + receive {ssl, Socket, "Hello world"} -> ok; %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast {ssl, Socket, "H"} -> - receive + receive {ssl, Socket, "ello world"} -> ok end end, ok = ssl:renegotiate(Socket), {error, renegotiation_rejected} = ssl:renegotiate(Socket), - ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), + ct:sleep(?RENEGOTIATION_DISABLE_TIME + ?SLEEP), ok = ssl:renegotiate(Socket), ct:log("Renegotiated again"), ssl:send(Socket, "Hello world"), ok. + +renegotiate_rejected(Socket) -> + receive + {ssl, Socket, "Hello world"} -> + ok; + %% Handle 1/n-1 splitting countermeasure Rizzo/Duong-Beast + {ssl, Socket, "H"} -> + receive + {ssl, Socket, "ello world"} -> + ok + end + end, + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:sleep(?RENEGOTIATION_DISABLE_TIME +1), + {error, renegotiation_rejected} = ssl:renegotiate(Socket), + ct:log("Failed to renegotiate again"), + ssl:send(Socket, "Hello world"), + ok. + +rizzo_add_mitigation_option(Value, Config) -> + lists:foldl(fun(Opt, Acc) -> + case proplists:get_value(Opt, Acc) of + undefined -> Acc; + C -> + N = lists:keystore(beast_mitigation, 1, C, + {beast_mitigation, Value}), + lists:keystore(Opt, 1, Acc, {Opt, N}) + end + end, Config, + [client_opts, client_dsa_opts, server_opts, server_dsa_opts, + server_ecdsa_opts, server_ecdh_rsa_opts]). new_config(PrivDir, ServerOpts0) -> CaCertFile = proplists:get_value(cacertfile, ServerOpts0), @@ -3204,63 +4393,83 @@ client_server_opts({KeyAlgo,_,_}, Config) when KeyAlgo == rsa orelse KeyAlgo == dhe_rsa orelse KeyAlgo == ecdhe_rsa -> - {?config(client_opts, Config), - ?config(server_opts, Config)}; + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_opts, Config)}; client_server_opts({KeyAlgo,_,_}, Config) when KeyAlgo == dss orelse KeyAlgo == dhe_dss -> - {?config(client_dsa_opts, Config), - ?config(server_dsa_opts, Config)}; + {ssl_test_lib:ssl_options(client_dsa_opts, Config), + ssl_test_lib:ssl_options(server_dsa_opts, Config)}; client_server_opts({KeyAlgo,_,_}, Config) when KeyAlgo == ecdh_ecdsa orelse KeyAlgo == ecdhe_ecdsa -> - {?config(client_opts, Config), - ?config(server_ecdsa_opts, Config)}; + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; client_server_opts({KeyAlgo,_,_}, Config) when KeyAlgo == ecdh_rsa -> - {?config(client_opts, Config), - ?config(server_ecdh_rsa_opts, Config)}. + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}. run_suites(Ciphers, Version, Config, Type) -> {ClientOpts, ServerOpts} = case Type of rsa -> - {?config(client_opts, Config), - ?config(server_opts, Config)}; + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_opts, Config)}; dsa -> - {?config(client_opts, Config), - ?config(server_dsa_opts, Config)}; + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_dsa_opts, Config)}; anonymous -> %% No certs in opts! - {?config(client_opts, Config), - ?config(server_anon, Config)}; + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_anon, Config)}; psk -> - {?config(client_psk, Config), - ?config(server_psk, Config)}; + {ssl_test_lib:ssl_options(client_psk, Config), + ssl_test_lib:ssl_options(server_psk, Config)}; psk_with_hint -> - {?config(client_psk, Config), - ?config(server_psk_hint, Config)}; + {ssl_test_lib:ssl_options(client_psk, Config), + ssl_test_lib:ssl_options(server_psk_hint, Config)}; psk_anon -> - {?config(client_psk, Config), - ?config(server_psk_anon, Config)}; + {ssl_test_lib:ssl_options(client_psk, Config), + ssl_test_lib:ssl_options(server_psk_anon, Config)}; psk_anon_with_hint -> - {?config(client_psk, Config), - ?config(server_psk_anon_hint, Config)}; + {ssl_test_lib:ssl_options(client_psk, Config), + ssl_test_lib:ssl_options(server_psk_anon_hint, Config)}; srp -> - {?config(client_srp, Config), - ?config(server_srp, Config)}; + {ssl_test_lib:ssl_options(client_srp, Config), + ssl_test_lib:ssl_options(server_srp, Config)}; srp_anon -> - {?config(client_srp, Config), - ?config(server_srp_anon, Config)}; + {ssl_test_lib:ssl_options(client_srp, Config), + ssl_test_lib:ssl_options(server_srp_anon, Config)}; srp_dsa -> - {?config(client_srp_dsa, Config), - ?config(server_srp_dsa, Config)}; + {ssl_test_lib:ssl_options(client_srp_dsa, Config), + ssl_test_lib:ssl_options(server_srp_dsa, Config)}; ecdsa -> - {?config(client_opts, Config), - ?config(server_ecdsa_opts, Config)}; + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; ecdh_rsa -> - {?config(client_opts, Config), - ?config(server_ecdh_rsa_opts, Config)} - end, + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}; + rc4_rsa -> + {ssl_test_lib:ssl_options(client_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_opts, Config)]}; + rc4_ecdh_rsa -> + {ssl_test_lib:ssl_options(client_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; + rc4_ecdsa -> + {ssl_test_lib:ssl_options(client_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; + des_dhe_rsa -> + {ssl_test_lib:ssl_options(client_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_opts, Config)]}; + des_rsa -> + {ssl_test_lib:ssl_options(client_opts, Config), + [{ciphers, Ciphers} | + ssl_test_lib:ssl_options(server_opts, Config)]} + end, Result = lists:map(fun(Cipher) -> cipher(Cipher, Version, Config, ClientOpts, ServerOpts) end, - Ciphers), + ssl_test_lib:filter_suites(Ciphers)), case lists:flatten(Result) of [] -> ok; @@ -3270,13 +4479,15 @@ run_suites(Ciphers, Version, Config, Type) -> end. erlang_cipher_suite(Suite) when is_list(Suite)-> - ssl:suite_definition(ssl_cipher:openssl_suite(Suite)); + ssl_cipher:erl_suite_definition(ssl_cipher:openssl_suite(Suite)); erlang_cipher_suite(Suite) -> Suite. cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> %% process_flag(trap_exit, true), ct:log("Testing CipherSuite ~p~n", [CipherSuite]), + ct:log("Server Opts ~p~n", [ServerOpts]), + ct:log("Client Opts ~p~n", [ClientOpts]), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), ErlangCipherSuite = erlang_cipher_suite(CipherSuite), @@ -3290,11 +4501,11 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, - {options, - [{ciphers,[CipherSuite]} | - ClientOpts]}]), + {from, self()}, + {mfa, {ssl_test_lib, cipher_result, [ConnectionInfo]}}, + {options, + [{ciphers,[CipherSuite]} | + ClientOpts]}]), Result = ssl_test_lib:wait_for_result(Server, ok, Client, ok), @@ -3308,8 +4519,23 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> [{ErlangCipherSuite, Error}] end. +connection_information_result(Socket) -> + {ok, Info = [_ | _]} = ssl:connection_information(Socket), + case length(Info) > 3 of + true -> + %% Atleast one ssl_option() is set + ct:log("Info ~p", [Info]), + ok; + false -> + ct:fail(no_ssl_options_returned) + end. + connection_info_result(Socket) -> - ssl:connection_info(Socket). + {ok, Info} = ssl:connection_information(Socket, [protocol, cipher_suite]), + {ok, {proplists:get_value(protocol, Info), proplists:get_value(cipher_suite, Info)}}. +version_info_result(Socket) -> + {ok, [{version, Version}]} = ssl:connection_information(Socket, [version]), + {ok, Version}. connect_dist_s(S) -> Msg = term_to_binary({erlang,term}), @@ -3320,6 +4546,33 @@ connect_dist_c(S) -> {ok, Test} = ssl:recv(S, 0, 10000), ok. +tls_downgrade_result(Socket) -> + ok = ssl_test_lib:send_recv_result(Socket), + case ssl:close(Socket, {self(), 10000}) of + {ok, TCPSocket} -> + inet:setopts(TCPSocket, [{active, true}]), + gen_tcp:send(TCPSocket, "Downgraded"), + receive + {tcp, TCPSocket, <<"Downgraded">>} -> + ok; + {tcp_closed, TCPSocket} -> + ct:pal("Peer timed out, downgrade aborted"), + ok; + Other -> + {error, Other} + end; + {error, timeout} -> + ct:pal("Timed out, downgrade aborted"), + ok; + Fail -> + {error, Fail} + end. + +tls_close(Socket) -> + ok = ssl_test_lib:send_recv_result(Socket), + ok = ssl:close(Socket, 5000). + + %% First two clauses handles 1/n-1 splitting countermeasure Rizzo/Duong-Beast treashold(N, {3,0}) -> (N div 2) + 1; @@ -3332,22 +4585,22 @@ get_invalid_inet_option(Socket) -> {error, {options, {socket_options, foo, _}}} = ssl:getopts(Socket, [foo]), ok. -shutdown_result(Socket, server) -> +tls_shutdown_result(Socket, server) -> ssl:send(Socket, "Hej"), ssl:shutdown(Socket, write), {ok, "Hej hopp"} = ssl:recv(Socket, 8), ok; -shutdown_result(Socket, client) -> +tls_shutdown_result(Socket, client) -> {ok, "Hej"} = ssl:recv(Socket, 3), ssl:send(Socket, "Hej hopp"), ssl:shutdown(Socket, write), ok. -shutdown_write_result(Socket, server) -> +tls_shutdown_write_result(Socket, server) -> ct:sleep(?SLEEP), ssl:shutdown(Socket, write); -shutdown_write_result(Socket, client) -> +tls_shutdown_write_result(Socket, client) -> ssl:recv(Socket, 0). dummy(_Socket) -> @@ -3355,11 +4608,63 @@ dummy(_Socket) -> %% due to fatal handshake failiure exit(kill). -shutdown_both_result(Socket, server) -> +tls_shutdown_both_result(Socket, server) -> ct:sleep(?SLEEP), ssl:shutdown(Socket, read_write); -shutdown_both_result(Socket, client) -> +tls_shutdown_both_result(Socket, client) -> ssl:recv(Socket, 0). peername_result(S) -> ssl:peername(S). + +version_option_test(Config, Version) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {versions, [Version]}| ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result, []}}, + {options, [{active, false}, {versions, [Version]}| ClientOpts]}]), + + ct:log("Testcase ~p, Client ~p Server ~p ~n", + [self(), Client, Server]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +try_recv_active(Socket) -> + ssl:send(Socket, "Hello world"), + {error, einval} = ssl:recv(Socket, 11), + ok. +try_recv_active_once(Socket) -> + {error, einval} = ssl:recv(Socket, 11), + ok. + +first_rsa_suite([{ecdhe_rsa, _, _} = Suite | _]) -> + Suite; +first_rsa_suite([{dhe_rsa, _, _} = Suite| _]) -> + Suite; +first_rsa_suite([{rsa, _, _} = Suite| _]) -> + Suite; +first_rsa_suite([{ecdhe_rsa, _, _, _} = Suite | _]) -> + Suite; +first_rsa_suite([{dhe_rsa, _, _, _} = Suite| _]) -> + Suite; +first_rsa_suite([{rsa, _, _, _} = Suite| _]) -> + Suite; +first_rsa_suite([_ | Rest]) -> + first_rsa_suite(Rest). + +wait_for_send(Socket) -> + %% Make sure TLS process processed send message event + _ = ssl:connection_information(Socket). diff --git a/lib/ssl/test/ssl_bench.spec b/lib/ssl/test/ssl_bench.spec new file mode 100644 index 0000000000..d2f75b4203 --- /dev/null +++ b/lib/ssl/test/ssl_bench.spec @@ -0,0 +1 @@ +{suites,"../ssl_test",[ssl_bench_SUITE]}. diff --git a/lib/ssl/test/ssl_bench_SUITE.erl b/lib/ssl/test/ssl_bench_SUITE.erl new file mode 100644 index 0000000000..ed439a425f --- /dev/null +++ b/lib/ssl/test/ssl_bench_SUITE.erl @@ -0,0 +1,367 @@ +%%%------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(ssl_bench_SUITE). +-compile(export_all). +-include_lib("common_test/include/ct_event.hrl"). + +-define(remote_host, "NETMARKS_REMOTE_HOST"). + +suite() -> [{ct_hooks,[{ts_install_cth,[{nodenames,2}]}]}]. + +all() -> [{group, setup}, {group, payload}]. + +groups() -> + [{setup, [{repeat, 3}], [setup_sequential, setup_concurrent]}, + {payload, [{repeat, 3}], [payload_simple]} + ]. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, _Config) -> + ok. + +init_per_suite(Config) -> + try + Server = setup(ssl, node()), + [{server_node, Server}|Config] + catch _:_ -> + {skipped, "Benchmark machines only"} + end. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_Func, Conf) -> + Conf. + +end_per_testcase(_Func, _Conf) -> + ok. + + +-define(COUNT, 400). +-define(TC(Cmd), tc(fun() -> Cmd end, ?MODULE, ?LINE)). + +-define(FPROF_CLIENT, false). +-define(FPROF_SERVER, false). +-define(EPROF_CLIENT, false). +-define(EPROF_SERVER, false). +-define(PERCEPT_SERVER, false). + +%% Current numbers gives roughly a testcase per minute on todays hardware.. + +setup_sequential(Config) -> + Server = proplists:get_value(server_node, Config), + Server =/= undefined orelse error(no_server), + {ok, Result} = do_test(ssl, setup_connection, ?COUNT * 20, 1, Server), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Sequential setup"}]}), + ok. + +setup_concurrent(Config) -> + Server = proplists:get_value(server_node, Config), + Server =/= undefined orelse error(no_server), + {ok, Result} = do_test(ssl, setup_connection, ?COUNT, 100, Server), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Concurrent setup"}]}), + ok. + +payload_simple(Config) -> + Server = proplists:get_value(server_node, Config), + Server =/= undefined orelse error(no_server), + {ok, Result} = do_test(ssl, payload, ?COUNT*300, 10, Server), + ct_event:notify(#event{name = benchmark_data, + data=[{value, Result}, + {suite, "ssl"}, {name, "Payload simple"}]}), + ok. + + +ssl() -> + test(ssl, ?COUNT, node()). + +test(Type, Count, Host) -> + Server = setup(Type, Host), + (do_test(Type, setup_connection, Count * 20, 1, Server)), + (do_test(Type, setup_connection, Count, 100, Server)), + (do_test(Type, payload, Count*300, 10, Server)), + ok. + +do_test(Type, TC, Loop, ParallellConnections, Server) -> + _ = ssl:stop(), + {ok, _} = ensure_all_started(ssl, []), + + {ok, {SPid, Host, Port}} = rpc:call(Server, ?MODULE, setup_server_init, + [Type, TC, Loop, ParallellConnections]), + link(SPid), + Me = self(), + Test = fun(Id) -> + CData = client_init(Me, Type, TC, Host, Port), + receive + go -> + ?FPROF_CLIENT andalso Id =:= 1 andalso + start_profile(fprof, [self(),new]), + ?EPROF_CLIENT andalso Id =:= 1 andalso + start_profile(eprof, [ssl_connection_sup, ssl_manager]), + ok = ?MODULE:TC(Loop, Type, CData), + ?FPROF_CLIENT andalso Id =:= 1 andalso + stop_profile(fprof, "test_connection_client_res.fprof"), + ?EPROF_CLIENT andalso Id =:= 1 andalso + stop_profile(eprof, "test_connection_client_res.eprof"), + Me ! self() + end + end, + Spawn = fun(Id) -> + Pid = spawn(fun() -> Test(Id) end), + receive {Pid, init} -> Pid end + end, + Pids = [Spawn(Id) || Id <- lists:seq(ParallellConnections, 1, -1)], + Run = fun() -> + [Pid ! go || Pid <- Pids], + [receive Pid -> ok end || Pid <- Pids] + end, + {TimeInMicro, _} = timer:tc(Run), + TotalTests = ParallellConnections * Loop, + TestPerSecond = 1000000 * TotalTests div TimeInMicro, + io:format("TC ~p ~p ~p ~p 1/s~n", [TC, Type, ParallellConnections, TestPerSecond]), + unlink(SPid), + SPid ! quit, + {ok, TestPerSecond}. + +server_init(ssl, setup_connection, _, _, Server) -> + {ok, Socket} = ssl:listen(0, ssl_opts(listen)), + {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, Host} = inet:gethostname(), + ?FPROF_SERVER andalso start_profile(fprof, [whereis(ssl_manager), new]), + %%?EPROF_SERVER andalso start_profile(eprof, [ssl_connection_sup, ssl_manager]), + ?EPROF_SERVER andalso start_profile(eprof, [ssl_manager]), + ?PERCEPT_SERVER andalso percept:profile("/tmp/ssl_server.percept"), + Server ! {self(), {init, Host, Port}}, + Test = fun(TSocket) -> + ok = ssl:ssl_accept(TSocket), + ssl:close(TSocket) + end, + setup_server_connection(Socket, Test); +server_init(ssl, payload, Loop, _, Server) -> + {ok, Socket} = ssl:listen(0, ssl_opts(listen)), + {ok, {_Host, Port}} = ssl:sockname(Socket), + {ok, Host} = inet:gethostname(), + Server ! {self(), {init, Host, Port}}, + Test = fun(TSocket) -> + ok = ssl:ssl_accept(TSocket), + Size = byte_size(msg()), + server_echo(TSocket, Size, Loop), + ssl:close(TSocket) + end, + setup_server_connection(Socket, Test); + +server_init(Type, Tc, _, _, Server) -> + io:format("No server init code for ~p ~p~n",[Type, Tc]), + Server ! {self(), no_init}. + +client_init(Master, ssl, setup_connection, Host, Port) -> + Master ! {self(), init}, + {Host, Port, ssl_opts(connect)}; +client_init(Master, ssl, payload, Host, Port) -> + {ok, Sock} = ssl:connect(Host, Port, ssl_opts(connect)), + Master ! {self(), init}, + Size = byte_size(msg()), + {Sock, Size}; +client_init(_Me, Type, Tc, Host, Port) -> + io:format("No client init code for ~p ~p~n",[Type, Tc]), + {Host, Port}. + +setup_server_connection(LSocket, Test) -> + receive quit -> + ?FPROF_SERVER andalso stop_profile(fprof, "test_server_res.fprof"), + ?EPROF_SERVER andalso stop_profile(eprof, "test_server_res.eprof"), + ?PERCEPT_SERVER andalso stop_profile(percept, "/tmp/ssl_server.percept"), + ok + after 0 -> + case ssl:transport_accept(LSocket, 2000) of + {ok, TSocket} -> spawn_link(fun() -> Test(TSocket) end); + {error, timeout} -> ok + end, + setup_server_connection(LSocket, Test) + end. + +server_echo(Socket, Size, Loop) when Loop > 0 -> + {ok, Msg} = ssl:recv(Socket, Size), + ok = ssl:send(Socket, Msg), + server_echo(Socket, Size, Loop-1); +server_echo(_, _, _) -> ok. + +setup_connection(N, ssl, Env = {Host, Port, Opts}) when N > 0 -> + case ssl:connect(Host, Port, Opts) of + {ok, Sock} -> + ssl:close(Sock), + setup_connection(N-1, ssl, Env); + {error, Error} -> + io:format("Error: ~p (~p)~n",[Error, length(erlang:ports())]), + setup_connection(N, ssl, Env) + end; +setup_connection(_, _, _) -> + ok. + +payload(Loop, ssl, D = {Socket, Size}) when Loop > 0 -> + ok = ssl:send(Socket, msg()), + {ok, _} = ssl:recv(Socket, Size), + payload(Loop-1, ssl, D); +payload(_, _, {Socket, _}) -> + ssl:close(Socket). + +msg() -> + <<"Hello", + 0:(512*8), + "asdlkjsafsdfoierwlejsdlkfjsdf", + 1:(512*8), + "asdlkjsafsdfoierwlejsdlkfjsdf">>. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +setup(_Type, nonode@nohost) -> + exit(dist_not_enabled); +setup(Type, _This) -> + Host = case os:getenv(?remote_host) of + false -> + {ok, This} = inet:gethostname(), + This; + RemHost -> + RemHost + end, + Node = list_to_atom("perf_server@" ++ Host), + SlaveArgs = case init:get_argument(pa) of + {ok, PaPaths} -> + lists:append([" -pa " ++ P || [P] <- PaPaths]); + _ -> [] + end, + %% io:format("Slave args: ~p~n",[SlaveArgs]), + Prog = + case os:find_executable("erl") of + false -> "erl"; + P -> P + end, + io:format("Prog = ~p~n", [Prog]), + + case net_adm:ping(Node) of + pong -> ok; + pang -> + {ok, Node} = slave:start(Host, perf_server, SlaveArgs, no_link, Prog) + end, + Path = code:get_path(), + true = rpc:call(Node, code, set_path, [Path]), + ok = rpc:call(Node, ?MODULE, setup_server, [Type, node()]), + io:format("Client (~p) using ~s~n",[node(), code:which(ssl)]), + (Node =:= node()) andalso restrict_schedulers(client), + Node. + +setup_server(_Type, ClientNode) -> + (ClientNode =:= node()) andalso restrict_schedulers(server), + io:format("Server (~p) using ~s~n",[node(), code:which(ssl)]), + ok. + + +ensure_all_started(App, Ack) -> + case application:start(App) of + ok -> {ok, [App|Ack]}; + {error, {not_started, Dep}} -> + {ok, Ack1} = ensure_all_started(Dep, Ack), + ensure_all_started(App, Ack1); + {error, {already_started, _}} -> + {ok, Ack} + end. + +setup_server_init(Type, Tc, Loop, PC) -> + _ = ssl:stop(), + {ok, _} = ensure_all_started(ssl, []), + Me = self(), + Pid = spawn_link(fun() -> server_init(Type, Tc, Loop, PC, Me) end), + Res = receive + {Pid, {init, Host, Port}} -> {ok, {Pid, Host, Port}}; + {Pid, Error} -> {error, Error} + end, + unlink(Pid), + Res. + +restrict_schedulers(Type) -> + %% We expect this to run on 8 core machine + Extra0 = 1, + Extra = if (Type =:= server) -> -Extra0; true -> Extra0 end, + Scheds = erlang:system_info(schedulers), + erlang:system_flag(schedulers_online, (Scheds div 2) + Extra). + +tc(Fun, Mod, Line) -> + case timer:tc(Fun) of + {_,{'EXIT',Reason}} -> + io:format("Process EXITED ~p:~p \n", [Mod, Line]), + exit(Reason); + {_T,R={error,_}} -> + io:format("Process Error ~p:~p \n", [Mod, Line]), + R; + {T,R} -> + io:format("~p:~p: Time: ~p\n", [Mod, Line, T]), + R + end. + +start_profile(eprof, Procs) -> + profiling = eprof:start_profiling(Procs), + io:format("(E)Profiling ...",[]); +start_profile(fprof, Procs) -> + fprof:trace([start, {procs, Procs}]), + io:format("(F)Profiling ...",[]). + +stop_profile(percept, File) -> + percept:stop_profile(), + percept:analyze(File), + {started, _Host, Port} = percept:start_webserver(), + wx:new(), + wx_misc:launchDefaultBrowser("http://" ++ net_adm:localhost() ++ ":" ++ integer_to_list(Port)), + ok; +stop_profile(eprof, File) -> + profiling_stopped = eprof:stop_profiling(), + eprof:log(File), + io:format(".analysed => ~s ~n",[File]), + eprof:analyze(total), + eprof:stop(); +stop_profile(fprof, File) -> + fprof:trace(stop), + io:format("..collect..",[]), + fprof:profile(), + fprof:analyse([{dest, File},{totals, true}]), + io:format(".analysed => ~s ~n",[File]), + fprof:stop(), + ok. + +ssl_opts(listen) -> + [{backlog, 500} | ssl_opts("server")]; +ssl_opts(connect) -> + [{verify, verify_peer} + | ssl_opts("client")]; +ssl_opts(Role) -> + Dir = filename:join([code:lib_dir(ssl), "examples", "certs", "etc"]), + [{active, false}, + {depth, 2}, + {reuseaddr, true}, + {mode,binary}, + {nodelay, true}, + {ciphers, [{dhe_rsa,aes_256_cbc,sha}]}, + {cacertfile, filename:join([Dir, Role, "cacerts.pem"])}, + {certfile, filename:join([Dir, Role, "cert.pem"])}, + {keyfile, filename:join([Dir, Role, "key.pem"])}]. diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index f76c55f670..20165c70f0 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2012-2013. All Rights Reserved. +%% Copyright Ericsson AB 2012-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -37,9 +38,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [{group, active}, {group, passive}, @@ -54,17 +52,23 @@ groups() -> {error_handling, [],error_handling_tests()}]. tests() -> - [server_verify_peer, - server_verify_none, + [verify_peer, + verify_none, server_require_peer_cert_ok, server_require_peer_cert_fail, + server_require_peer_cert_partial_chain, + server_require_peer_cert_allow_partial_chain, + server_require_peer_cert_do_not_allow_partial_chain, + server_require_peer_cert_partial_chain_fun_fail, verify_fun_always_run_client, verify_fun_always_run_server, cert_expired, invalid_signature_client, invalid_signature_server, extended_key_usage_verify_peer, - extended_key_usage_verify_none]. + extended_key_usage_verify_none, + critical_extension_verify_peer, + critical_extension_verify_none]. error_handling_tests()-> [client_with_cert_cipher_suites_handshake, @@ -73,23 +77,19 @@ error_handling_tests()-> unknown_server_ca_accept_verify_none, unknown_server_ca_accept_verify_peer, unknown_server_ca_accept_backwardscompatibility, - no_authority_key_identifier]. + no_authority_key_identifier, + no_authority_key_identifier_and_nonstandard_encoding]. init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), catch crypto:stop(), try crypto:start() of ok -> ssl:start(), %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), - - Config1 = ssl_test_lib:make_dsa_cert(Config0), - Config = ssl_test_lib:cert_options(Config1), - [{watchdog, Dog} | Config] + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), + Config = ssl_test_lib:make_dsa_cert(Config0), + ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} end. @@ -110,17 +110,36 @@ init_per_group(_, Config) -> end_per_group(_GroupName, Config) -> Config. +init_per_testcase(TestCase, Config) when TestCase == cert_expired; + TestCase == invalid_signature_client; + TestCase == invalid_signature_server; + TestCase == extended_key_usage_verify_none; + TestCase == extended_key_usage_verify_peer; + TestCase == critical_extension_verify_none; + TestCase == critical_extension_verify_peer; + TestCase == no_authority_key_identifier; + TestCase == no_authority_key_identifier_and_nonstandard_encoding-> + ssl:clear_pem_cache(), + init_per_testcase(common, Config); +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- -server_verify_peer() -> - [{doc,"Test server option verify_peer"}]. -server_verify_peer(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - Active = ?config(active, Config), - ReceiveFunction = ?config(receive_function, Config), +verify_peer() -> + [{doc,"Test option verify_peer"}]. +verify_peer(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -139,14 +158,14 @@ server_verify_peer(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -server_verify_none() -> - [{doc,"Test server option verify_none"}]. +verify_none() -> + [{doc,"Test option verify_none"}]. -server_verify_none(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), - Active = ?config(active, Config), - ReceiveFunction = ?config(receive_function, Config), +verify_none(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -171,10 +190,10 @@ server_verify_client_once() -> [{doc,"Test server option verify_client_once"}]. server_verify_client_once(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - Active = ?config(active, Config), - ReceiveFunction = ?config(receive_function, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -210,20 +229,23 @@ server_require_peer_cert_ok() -> server_require_peer_cert_ok(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ?config(server_verification_opts, Config)], - ClientOpts = ?config(client_verification_opts, Config), + | ssl_test_lib:ssl_options(server_verification_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {ssl_test_lib,send_recv_result, []}}, - {options, [{active, false} | ServerOpts]}]), + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} | ClientOpts]}]), + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active} | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), ssl_test_lib:close(Server), @@ -236,8 +258,8 @@ server_require_peer_cert_fail() -> server_require_peer_cert_fail(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} - | ?config(server_verification_opts, Config)], - BadClientOpts = ?config(client_opts, Config), + | ssl_test_lib:ssl_options(server_verification_opts, Config)], + BadClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, @@ -250,18 +272,183 @@ server_require_peer_cert_fail(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {options, [{active, false} | BadClientOpts]}]), + receive + {Server, {error, {tls_alert, "handshake failure"}}} -> + receive + {Client, {error, {tls_alert, "handshake failure"}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. + +%%-------------------------------------------------------------------- + +server_require_peer_cert_partial_chain() -> + [{doc, "Client sends an incompleate chain, by default not acceptable."}]. + +server_require_peer_cert_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_verification_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), + [{_,RootCA,_}, {_, _, _}] = public_key:pem_decode(ClientCAs), + + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{active, false}, + {cacerts, [RootCA]} | + proplists:delete(cacertfile, ClientOpts)]}]), + receive + {Server, {error, {tls_alert, "unknown ca"}}} -> + receive + {Client, {error, {tls_alert, "unknown ca"}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. +%%-------------------------------------------------------------------- +server_require_peer_cert_allow_partial_chain() -> + [{doc, "Server trusts intermediat CA and accepts a partial chain. (partial_chain option)"}]. + +server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_verification_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(CertChain) -> + case lists:member(IntermidiateCA, CertChain) of + true -> + {trusted_ca, IntermidiateCA}; + false -> + unknown_ca + end + end, + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active}, + {cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts)]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{active, Active} | ClientOpts]}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + + %%-------------------------------------------------------------------- +server_require_peer_cert_do_not_allow_partial_chain() -> + [{doc, "Server does not accept the chain sent by the client as ROOT CA is unkown, " + "and we do not choose to trust the intermediate CA. (partial_chain option)"}]. + +server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_verification_opts, Config)], + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + + PartialChain = fun(_CertChain) -> + unknown_ca + end, - ssl_test_lib:check_result(Server, {error, {tls_alert, "handshake failure"}}, - Client, {error, {tls_alert, "handshake failure"}}). + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts)]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + receive + {Server, {error, {tls_alert, "unknown ca"}}} -> + receive + {Client, {error, {tls_alert, "unknown ca"}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. + + %%-------------------------------------------------------------------- +server_require_peer_cert_partial_chain_fun_fail() -> + [{doc, "If parial_chain fun crashes, treat it as if it returned unkown_ca"}]. + +server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> + ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} + | ssl_test_lib:ssl_options(server_verification_opts, Config)], + ClientOpts = proplists:get_value(client_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + PartialChain = fun(_CertChain) -> + ture = false %% crash on purpose + end, + + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, [{cacerts, [IntermidiateCA]}, + {partial_chain, PartialChain} | + proplists:delete(cacertfile, ServerOpts)]}]), + + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ClientOpts}]), + + receive + {Server, {error, {tls_alert, "unknown ca"}}} -> + receive + {Client, {error, {tls_alert, "unknown ca"}}} -> + ok; + {Client, {error, closed}} -> + ok + end + end. %%-------------------------------------------------------------------- verify_fun_always_run_client() -> [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. verify_fun_always_run_client(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -277,7 +464,7 @@ verify_fun_always_run_client(Config) when is_list(Config) -> {unknown, UserState}; (_, valid, [ChainLen]) -> {valid, [ChainLen + 1]}; - (_, valid_peer, [2]) -> + (_, valid_peer, [1]) -> {fail, "verify_fun_was_always_run"}; (_, valid_peer, UserState) -> {valid, UserState} @@ -305,8 +492,8 @@ verify_fun_always_run_client(Config) when is_list(Config) -> verify_fun_always_run_server() -> [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. verify_fun_always_run_server(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), %% If user verify fun is called correctly we fail the connection. @@ -316,7 +503,7 @@ verify_fun_always_run_server(Config) when is_list(Config) -> {unknown, UserState}; (_, valid, [ChainLen]) -> {valid, [ChainLen + 1]}; - (_, valid_peer, [2]) -> + (_, valid_peer, [1]) -> {fail, "verify_fun_was_always_run"}; (_, valid_peer, UserState) -> {valid, UserState} @@ -352,39 +539,13 @@ verify_fun_always_run_server(Config) when is_list(Config) -> %%-------------------------------------------------------------------- -client_verify_none_passive() -> - [{doc,"Test client option verify_none"}]. - -client_verify_none_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result, []}}, - {options, [{active, false}, - {verify, verify_none} - | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). -%%-------------------------------------------------------------------- cert_expired() -> [{doc,"Test server with expired certificate"}]. cert_expired(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - PrivDir = ?config(priv_dir, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), @@ -429,10 +590,16 @@ cert_expired(Config) when is_list(Config) -> Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, - {options, [{verify, verify_peer} | ClientOpts]}]), - - ssl_test_lib:check_result(Server, {error, {tls_alert, "certificate expired"}}, - Client, {error, {tls_alert, "certificate expired"}}). + {options, [{verify, verify_peer} | ClientOpts]}]), + receive + {Client, {error, {tls_alert, "certificate expired"}}} -> + receive + {Server, {error, {tls_alert, "certificate expired"}}} -> + ok; + {Server, {error, closed}} -> + ok + end + end. two_digits_str(N) when N < 10 -> lists:flatten(io_lib:format("0~p", [N])); @@ -440,73 +607,15 @@ two_digits_str(N) -> lists:flatten(io_lib:format("~p", [N])). %%-------------------------------------------------------------------- - -client_verify_none_active() -> - [{doc,"Test client option verify_none"}]. - -client_verify_none_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, [{active, true} - | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active, []}}, - {options, [{active, true}, - {verify, verify_none} - | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- -client_verify_none_active_once() -> - [{doc,"Test client option verify_none"}]. - -client_verify_none_active_once(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), - - {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), - Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, - {from, self()}, - {mfa, {ssl_test_lib, send_recv_result_active, []}}, - {options, [{active, once} | ServerOpts]}]), - Port = ssl_test_lib:inet_port(Server), - - Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, - {host, Hostname}, - {from, self()}, - {mfa, {ssl_test_lib, - send_recv_result_active_once, - []}}, - {options, [{active, once}, - {verify, verify_none} - | ClientOpts]}]), - - ssl_test_lib:check_result(Server, ok, Client, ok), - ssl_test_lib:close(Server), - ssl_test_lib:close(Client). - -%%-------------------------------------------------------------------- extended_key_usage_verify_peer() -> [{doc,"Test cert that has a critical extended_key_usage extension in verify_peer mode"}]. extended_key_usage_verify_peer(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - PrivDir = ?config(priv_dir, Config), - Active = ?config(active, Config), - ReceiveFunction = ?config(receive_function, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), @@ -564,11 +673,11 @@ extended_key_usage_verify_none() -> [{doc,"Test cert that has a critical extended_key_usage extension in verify_none mode"}]. extended_key_usage_verify_none(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - PrivDir = ?config(priv_dir, Config), - Active = ?config(active, Config), - ReceiveFunction = ?config(receive_function, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), @@ -621,14 +730,129 @@ extended_key_usage_verify_none(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +critical_extension_verify_peer() -> + [{doc,"Test cert that has a critical unknown extension in verify_peer mode"}]. + +critical_extension_verify_peer(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), + + KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), + NewCertName = integer_to_list(erlang:unique_integer()) ++ ".pem", + + ServerCertFile = proplists:get_value(certfile, ServerOpts), + NewServerCertFile = filename:join([PrivDir, "server", NewCertName]), + add_critical_netscape_cert_type(ServerCertFile, NewServerCertFile, KeyFile), + NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], + + ClientCertFile = proplists:get_value(certfile, ClientOpts), + NewClientCertFile = filename:join([PrivDir, "client", NewCertName]), + add_critical_netscape_cert_type(ClientCertFile, NewClientCertFile, KeyFile), + NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_error( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_peer}, {active, Active} | NewServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_peer}, {active, Active} | NewClientOpts]}]), + + %% This certificate has a critical extension that we don't + %% understand. Therefore, verification should fail. + tcp_delivery_workaround(Server, {error, {tls_alert, "unsupported certificate"}}, + Client, {error, {tls_alert, "unsupported certificate"}}), + + ssl_test_lib:close(Server), + ok. + +%%-------------------------------------------------------------------- +critical_extension_verify_none() -> + [{doc,"Test cert that has a critical unknown extension in verify_none mode"}]. + +critical_extension_verify_none(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), + + KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), + NewCertName = integer_to_list(erlang:unique_integer()) ++ ".pem", + + ServerCertFile = proplists:get_value(certfile, ServerOpts), + NewServerCertFile = filename:join([PrivDir, "server", NewCertName]), + add_critical_netscape_cert_type(ServerCertFile, NewServerCertFile, KeyFile), + NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], + + ClientCertFile = proplists:get_value(certfile, ClientOpts), + NewClientCertFile = filename:join([PrivDir, "client", NewCertName]), + add_critical_netscape_cert_type(ClientCertFile, NewClientCertFile, KeyFile), + NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_none}, {active, Active} | NewServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), + + %% This certificate has a critical extension that we don't + %% understand. But we're using `verify_none', so verification + %% shouldn't fail. + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ok. + +add_critical_netscape_cert_type(CertFile, NewCertFile, KeyFile) -> + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + [{'Certificate', DerCert, _}] = ssl_test_lib:pem_to_der(CertFile), + OTPCert = public_key:pkix_decode_cert(DerCert, otp), + %% This is the "Netscape Cert Type" extension, telling us that the + %% certificate can be used for SSL clients and SSL servers. + NetscapeCertTypeExt = #'Extension'{ + extnID = {2,16,840,1,113730,1,1}, + critical = true, + extnValue = <<3,2,6,192>>}, + OTPTbsCert = OTPCert#'OTPCertificate'.tbsCertificate, + Extensions = OTPTbsCert#'OTPTBSCertificate'.extensions, + NewOTPTbsCert = OTPTbsCert#'OTPTBSCertificate'{ + extensions = [NetscapeCertTypeExt] ++ Extensions}, + NewDerCert = public_key:pkix_sign(NewOTPTbsCert, Key), + ssl_test_lib:der_to_pem(NewCertFile, [{'Certificate', NewDerCert, not_encrypted}]), + ok. + +%%-------------------------------------------------------------------- no_authority_key_identifier() -> [{doc, "Test cert that does not have authorityKeyIdentifier extension" " but are present in trusted certs db."}]. no_authority_key_identifier(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_opts, Config), - PrivDir = ?config(priv_dir, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), @@ -677,13 +901,75 @@ delete_authority_key_extension([Head | Rest], Acc) -> %%-------------------------------------------------------------------- +no_authority_key_identifier_and_nonstandard_encoding() -> + [{doc, "Test cert with nonstandard encoding that does not have" + " authorityKeyIdentifier extension but are present in trusted certs db."}]. + +no_authority_key_identifier_and_nonstandard_encoding(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + + KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), + [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), + Key = ssl_test_lib:public_key(public_key:pem_entry_decode(KeyEntry)), + + CertFile = proplists:get_value(certfile, ServerOpts), + NewCertFile = filename:join(PrivDir, "server/new_cert.pem"), + [{'Certificate', DerCert, _}] = ssl_test_lib:pem_to_der(CertFile), + ServerCert = public_key:pkix_decode_cert(DerCert, plain), + ServerTbsCert = ServerCert#'Certificate'.tbsCertificate, + Extensions0 = ServerTbsCert#'TBSCertificate'.extensions, + %% need to remove authorityKeyIdentifier extension to cause DB lookup by signature + Extensions = delete_authority_key_extension(Extensions0, []), + NewExtensions = replace_key_usage_extension(Extensions, []), + NewServerTbsCert = ServerTbsCert#'TBSCertificate'{extensions = NewExtensions}, + + ct:log("Extensions ~p~n, NewExtensions: ~p~n", [Extensions, NewExtensions]), + + TbsDer = public_key:pkix_encode('TBSCertificate', NewServerTbsCert, plain), + Sig = public_key:sign(TbsDer, md5, Key), + NewServerCert = ServerCert#'Certificate'{tbsCertificate = NewServerTbsCert, signature = Sig}, + NewDerCert = public_key:pkix_encode('Certificate', NewServerCert, plain), + ssl_test_lib:der_to_pem(NewCertFile, [{'Certificate', NewDerCert, not_encrypted}]), + NewServerOpts = [{certfile, NewCertFile} | proplists:delete(certfile, ServerOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, []}}, + {options, [{active, true} | NewServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, []}}, + {options, [{verify, verify_peer} | ClientOpts]}]), + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +replace_key_usage_extension([], Acc) -> + lists:reverse(Acc); +replace_key_usage_extension([#'Extension'{extnID = ?'id-ce-keyUsage'} = E | Rest], Acc) -> + %% A nonstandard DER encoding of [digitalSignature, keyEncipherment] + Val = <<3, 2, 0, 16#A0>>, + replace_key_usage_extension(Rest, [E#'Extension'{extnValue = Val} | Acc]); +replace_key_usage_extension([Head | Rest], Acc) -> + replace_key_usage_extension(Rest, [Head | Acc]). + +%%-------------------------------------------------------------------- + invalid_signature_server() -> [{doc,"Test client with invalid signature"}]. invalid_signature_server(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - PrivDir = ?config(priv_dir, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), KeyFile = filename:join(PrivDir, "server/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), @@ -718,9 +1004,9 @@ invalid_signature_client() -> [{doc,"Test server with invalid signature"}]. invalid_signature_client(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts, Config), - ServerOpts = ?config(server_verification_opts, Config), - PrivDir = ?config(priv_dir, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), KeyFile = filename:join(PrivDir, "client/key.pem"), [KeyEntry] = ssl_test_lib:pem_to_der(KeyFile), @@ -756,8 +1042,8 @@ client_with_cert_cipher_suites_handshake() -> [{doc, "Test that client with a certificate without keyEncipherment usage " " extension can connect to a server with restricted cipher suites "}]. client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> - ClientOpts = ?config(client_verification_opts_digital_signature_only, Config), - ServerOpts = ?config(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts_digital_signature_only, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -784,7 +1070,7 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> server_verify_no_cacerts() -> [{doc,"Test server must have cacerts if it wants to verify client"}]. server_verify_no_cacerts(Config) when is_list(Config) -> - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -798,8 +1084,8 @@ server_verify_no_cacerts(Config) when is_list(Config) -> unknown_server_ca_fail() -> [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. unknown_server_ca_fail(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -827,16 +1113,23 @@ unknown_server_ca_fail(Config) when is_list(Config) -> [{verify, verify_peer}, {verify_fun, FunAndState} | ClientOpts]}]), + receive + {Client, {error, {tls_alert, "unknown ca"}}} -> + receive + {Server, {error, {tls_alert, "unknown ca"}}} -> + ok; + {Server, {error, closed}} -> + ok + end + end. - ssl_test_lib:check_result(Server, {error, {tls_alert, "unknown ca"}}, - Client, {error, {tls_alert, "unknown ca"}}). %%-------------------------------------------------------------------- unknown_server_ca_accept_verify_none() -> [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -860,8 +1153,8 @@ unknown_server_ca_accept_verify_peer() -> [{doc, "Test that the client succeds if the ca is unknown in verify_peer mode" " with a verify_fun that accepts the unknown ca error"}]. unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -899,8 +1192,8 @@ unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> unknown_server_ca_accept_backwardscompatibility() -> [{doc,"Test that old style verify_funs will work"}]. unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, diff --git a/lib/ssl/test/ssl_cipher_SUITE.erl b/lib/ssl/test/ssl_cipher_SUITE.erl index 45e91786d4..b8096c5d7a 100644 --- a/lib/ssl/test/ssl_cipher_SUITE.erl +++ b/lib/ssl/test/ssl_cipher_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,16 +30,11 @@ -include("ssl_cipher.hrl"). -include("ssl_alert.hrl"). --define(TIMEOUT, 600000). - %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> - [aes_decipher_good, aes_decipher_good_tls11, aes_decipher_fail, aes_decipher_fail_tls11]. + [aes_decipher_good, aes_decipher_fail, padding_test]. groups() -> []. @@ -61,10 +57,9 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 5}), + Config. end_per_testcase(_TestCase, Config) -> Config. @@ -73,93 +68,122 @@ end_per_testcase(_TestCase, Config) -> %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- aes_decipher_good() -> - [{doc,"Decipher a known cryptotext."}]. + [{doc,"Decipher a known cryptotext using a correct key"}]. aes_decipher_good(Config) when is_list(Config) -> HashSz = 32, - CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>, - key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,148>>}, - Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8, - 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160, - 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122, - 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>, - Content = <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56, "HELLO\n">>, - Mac = <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>, - Version = {3,0}, - {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), - Version1 = {3,1}, - {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1), - ok. + CipherState = correct_cipher_state(), + decipher_check_good(HashSz, CipherState, {3,0}), + decipher_check_good(HashSz, CipherState, {3,1}), + decipher_check_good(HashSz, CipherState, {3,2}), + decipher_check_good(HashSz, CipherState, {3,3}). %%-------------------------------------------------------------------- - -aes_decipher_good_tls11() -> - [{doc,"Decipher a known TLS 1.1 cryptotext."}]. - -%% the fragment is actuall a TLS 1.1 record, with -%% Version = TLS 1.1, we get the correct NextIV in #cipher_state -aes_decipher_good_tls11(Config) when is_list(Config) -> - HashSz = 32, - CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>, - key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,148>>}, - Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8, - 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160, - 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122, - 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>, - Content = <<"HELLO\n">>, - NextIV = <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>, - Mac = <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>, - Version = {3,2}, - {Content, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), - Version1 = {3,2}, - {Content, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1), - ok. - -%%-------------------------------------------------------------------- - aes_decipher_fail() -> - [{doc,"Decipher a known cryptotext."}]. + [{doc,"Decipher a known cryptotext using a incorrect key"}]. -%% same as above, last byte of key replaced aes_decipher_fail(Config) when is_list(Config) -> HashSz = 32, - CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>, - key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,254>>}, - Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8, - 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160, - 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122, - 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>, - Version = {3,0}, - {Content, Mac, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), - 32 = byte_size(Content), - 32 = byte_size(Mac), - Version1 = {3,1}, - {Content1, Mac1, _} = ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1), - 32 = byte_size(Content1), - 32 = byte_size(Mac1), - ok. + CipherState = incorrect_cipher_state(), + decipher_check_fail(HashSz, CipherState, {3,0}), + decipher_check_fail(HashSz, CipherState, {3,1}), + decipher_check_fail(HashSz, CipherState, {3,2}), + decipher_check_fail(HashSz, CipherState, {3,3}). %%-------------------------------------------------------------------- - -aes_decipher_fail_tls11() -> - [{doc,"Decipher a known TLS 1.1 cryptotext."}]. - -%% same as above, last byte of key replaced -%% stricter padding checks in TLS 1.1 mean we get an alert instead -aes_decipher_fail_tls11(Config) when is_list(Config) -> - HashSz = 32, - CipherState = #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>, - key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,254>>}, - Fragment = <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8, - 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160, - 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122, - 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>, - Version = {3,2}, - #alert{level = ?FATAL, description = ?BAD_RECORD_MAC} = - ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version), - Version1 = {3,3}, - #alert{level = ?FATAL, description = ?BAD_RECORD_MAC} = - ssl_cipher:decipher(?AES, HashSz, CipherState, Fragment, Version1), - ok. - +padding_test(Config) when is_list(Config) -> + HashSz = 16, + CipherState = correct_cipher_state(), + pad_test(HashSz, CipherState, {3,0}), + pad_test(HashSz, CipherState, {3,1}), + pad_test(HashSz, CipherState, {3,2}), + pad_test(HashSz, CipherState, {3,3}). + +%%-------------------------------------------------------------------- +% Internal functions -------------------------------------------------------- %%-------------------------------------------------------------------- +decipher_check_good(HashSz, CipherState, Version) -> + {Content, _NextIV, Mac} = content_nextiv_mac(Version), + {Content, Mac, _} = + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, aes_fragment(Version), Version, true). + +decipher_check_fail(HashSz, CipherState, Version) -> + {Content, NextIV, Mac} = content_nextiv_mac(Version), + true = {Content, Mac, #cipher_state{iv = NextIV}} =/= + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, aes_fragment(Version), Version, true). + +pad_test(HashSz, CipherState, {3,0} = Version) -> + %% 3.0 does not have padding test + {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version), + {Content, Mac, #cipher_state{iv = NextIV}} = + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, true), + {Content, Mac, #cipher_state{iv = NextIV}} = + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,0}), {3,0}, false); +pad_test(HashSz, CipherState, {3,1} = Version) -> + %% 3.1 should have padding test, but may be disabled + {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version), + BadCont = badpad_content(Content), + {Content, Mac, #cipher_state{iv = NextIV}} = + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}) , {3,1}, false), + {BadCont, Mac, #cipher_state{iv = NextIV}} = + ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, badpad_aes_fragment({3,1}), {3,1}, true); +pad_test(HashSz, CipherState, Version) -> + %% 3.2 and 3.3 must have padding test + {Content, NextIV, Mac} = badpad_content_nextiv_mac(Version), + BadCont = badpad_content(Content), + {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, + badpad_aes_fragment(Version), Version, false), + {BadCont, Mac, #cipher_state{iv = NextIV}} = ssl_cipher:decipher(?AES_CBC, HashSz, CipherState, + badpad_aes_fragment(Version), Version, true). + +aes_fragment({3,N}) when N == 0; N == 1-> + <<197,9,6,109,242,87,80,154,85,250,110,81,119,95,65,185,53,206,216,153,246,169, + 119,177,178,238,248,174,253,220,242,81,33,0,177,251,91,44,247,53,183,198,165, + 63,20,194,159,107>>; + +aes_fragment(_) -> + <<220,193,179,139,171,33,143,245,202,47,123,251,13,232,114,8, + 190,162,74,31,186,227,119,155,94,74,119,79,169,193,240,160, + 198,181,81,19,98,162,213,228,74,224,253,168,156,59,195,122, + 108,101,107,242,20,15,169,150,163,107,101,94,93,104,241,165>>. + +badpad_aes_fragment({3,N}) when N == 0; N == 1 -> + <<186,139,125,10,118,21,26,248,120,108,193,104,87,118,145,79,225,55,228,10,105, + 30,190,37,1,88,139,243,210,99,65,41>>; +badpad_aes_fragment(_) -> + <<137,31,14,77,228,80,76,103,183,125,55,250,68,190,123,131,117,23,229,180,207, + 94,121,137,117,157,109,99,113,61,190,138,131,229,201,120,142,179,172,48,77, + 234,19,240,33,38,91,93>>. + +content_nextiv_mac({3,N}) when N == 0; N == 1 -> + {<<"HELLO\n">>, + <<72,196,247,97,62,213,222,109,210,204,217,186,172,184, 197,148>>, + <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>}; +content_nextiv_mac(_) -> + {<<"HELLO\n">>, + <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>>, + <<71,136,212,107,223,200,70,232,127,116,148,205,232,35,158,113,237,174,15,217,192,168,35,8,6,107,107,233,25,174,90,111>>}. + +badpad_content_nextiv_mac({3,N}) when N == 0; N == 1 -> + {<<"HELLO\n">>, + <<225,55,228,10,105,30,190,37,1,88,139,243,210,99,65,41>>, + <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>> + }; +badpad_content_nextiv_mac(_) -> + {<<"HELLO\n">>, + <<133,211,45,189,179,229,56,86,11,178,239,159,14,160,253,140>>, + <<183,139,16,132,10,209,67,86,168,100,61,217,145,57,36,56>> + }. + +badpad_content(Content) -> + %% BadContent will fail mac test + <<16#F0, Content/binary>>. + +correct_cipher_state() -> + #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>, + key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,148>>}. + +incorrect_cipher_state() -> + #cipher_state{iv = <<59,201,85,117,188,206,224,136,5,109,46,70,104,79,4,9>>, + key = <<72,196,247,97,62,213,222,109,210,204,217,186,172,184,197,254>>}. + diff --git a/lib/ssl/test/ssl_crl_SUITE.erl b/lib/ssl/test/ssl_crl_SUITE.erl new file mode 100644 index 0000000000..00636e5660 --- /dev/null +++ b/lib/ssl/test/ssl_crl_SUITE.erl @@ -0,0 +1,494 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(ssl_crl_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [ + {group, check_true}, + {group, check_peer}, + {group, check_best_effort} + ]. + +groups() -> + [ + {check_true, [], [{group, v2_crl}, + {group, v1_crl}, + {group, idp_crl}, + {group, crl_hash_dir}]}, + {check_peer, [], [{group, v2_crl}, + {group, v1_crl}, + {group, idp_crl}, + {group, crl_hash_dir}]}, + {check_best_effort, [], [{group, v2_crl}, + {group, v1_crl}, + {group, idp_crl}, + {group, crl_hash_dir}]}, + {v2_crl, [], basic_tests()}, + {v1_crl, [], basic_tests()}, + {idp_crl, [], basic_tests()}, + {crl_hash_dir, [], basic_tests() ++ crl_hash_dir_tests()}]. + +basic_tests() -> + [crl_verify_valid, crl_verify_revoked, crl_verify_no_crl]. + +crl_hash_dir_tests() -> + [crl_hash_dir_collision, crl_hash_dir_expired]. + +init_per_suite(Config) -> + case os:find_executable("openssl") of + false -> + {skip, "Openssl not found"}; + _ -> + OpenSSL_version = (catch os:cmd("openssl version")), + case ssl_test_lib:enough_openssl_crl_support(OpenSSL_version) of + false -> + {skip, io_lib:format("Bad openssl version: ~p",[OpenSSL_version])}; + _ -> + catch crypto:stop(), + try crypto:start() of + ok -> + {ok, Hostname0} = inet:gethostname(), + IPfamily = + case lists:member(list_to_atom(Hostname0), ct:get_config(ipv6_hosts,[])) of + true -> inet6; + false -> inet + end, + [{ipfamily,IPfamily}, {openssl_version,OpenSSL_version} | Config] + catch _:_ -> + {skip, "Crypto did not start"} + end + end + end. + +end_per_suite(_Config) -> + ssl:stop(), + application:stop(crypto). + +init_per_group(check_true, Config) -> + [{crl_check, true} | Config]; +init_per_group(check_peer, Config) -> + [{crl_check, peer} | Config]; +init_per_group(check_best_effort, Config) -> + [{crl_check, best_effort} | Config]; +init_per_group(Group, Config0) -> + case is_idp(Group) of + true -> + [{idp_crl, true} | Config0]; + false -> + DataDir = proplists:get_value(data_dir, Config0), + CertDir = filename:join(proplists:get_value(priv_dir, Config0), Group), + {CertOpts, Config} = init_certs(CertDir, Group, Config0), + {ok, _} = make_certs:all(DataDir, CertDir, CertOpts), + case Group of + crl_hash_dir -> + CrlDir = filename:join(CertDir, "crls"), + %% Copy CRLs to their hashed filenames. + %% Find the hashes with 'openssl crl -noout -hash -in crl.pem'. + populate_crl_hash_dir(CertDir, CrlDir, + [{"erlangCA", "d6134ed3"}, + {"otpCA", "d4c8d7e5"}], + replace), + CrlCacheOpts = [{crl_cache, + {ssl_crl_hash_dir, + {internal, [{dir, CrlDir}]}}}]; + _ -> + CrlCacheOpts = [] + end, + [{crl_cache_opts, CrlCacheOpts}, + {cert_dir, CertDir}, + {idp_crl, false} | Config] + end. + +end_per_group(_GroupName, Config) -> + + Config. + +init_per_testcase(Case, Config0) -> + case proplists:get_value(idp_crl, Config0) of + true -> + end_per_testcase(Case, Config0), + inets:start(), + ssl:start(), + ServerRoot = make_dir_path([proplists:get_value(priv_dir, Config0), idp_crl, tmp]), + %% start a HTTP server to serve the CRLs + {ok, Httpd} = inets:start(httpd, [{ipfamily, proplists:get_value(ipfamily, Config0)}, + {server_name, "localhost"}, {port, 0}, + {server_root, ServerRoot}, + {document_root, + filename:join(proplists:get_value(priv_dir, Config0), idp_crl)} + ]), + [{port,Port}] = httpd:info(Httpd, [port]), + Config = [{httpd_port, Port} | Config0], + DataDir = proplists:get_value(data_dir, Config), + CertDir = filename:join(proplists:get_value(priv_dir, Config0), idp_crl), + {CertOpts, Config} = init_certs(CertDir, idp_crl, Config), + {ok, _} = make_certs:all(DataDir, CertDir, CertOpts), + ct:timetrap({seconds, 6}), + [{cert_dir, CertDir} | Config]; + false -> + end_per_testcase(Case, Config0), + ssl:start(), + Config0 + end. + +end_per_testcase(_, Config) -> + case proplists:get_value(idp_crl, Config) of + true -> + ssl:stop(), + inets:stop(); + false -> + ssl:stop() + end. + +%%%================================================================ +%%% Test cases +%%%================================================================ + +crl_verify_valid() -> + [{doc,"Verify a simple valid CRL chain"}]. +crl_verify_valid(Config) when is_list(Config) -> + PrivDir = proplists:get_value(cert_dir, Config), + Check = proplists:get_value(crl_check, Config), + ServerOpts = [{keyfile, filename:join([PrivDir, "server", "key.pem"])}, + {certfile, filename:join([PrivDir, "server", "cert.pem"])}, + {cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}], + ClientOpts = case proplists:get_value(idp_crl, Config) of + true -> + [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}, + {crl_check, Check}, + {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}, + {verify, verify_peer}]; + false -> + ?config(crl_cache_opts, Config) ++ + [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}, + {crl_check, Check}, + {verify, verify_peer}] + end, + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ssl_crl_cache:insert({file, filename:join([PrivDir, "erlangCA", "crl.pem"])}), + ssl_crl_cache:insert({file, filename:join([PrivDir, "otpCA", "crl.pem"])}), + + crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts). + +crl_verify_revoked() -> + [{doc,"Verify a simple CRL chain when peer cert is reveoked"}]. +crl_verify_revoked(Config) when is_list(Config) -> + PrivDir = proplists:get_value(cert_dir, Config), + Check = proplists:get_value(crl_check, Config), + ServerOpts = [{keyfile, filename:join([PrivDir, "revoked", "key.pem"])}, + {certfile, filename:join([PrivDir, "revoked", "cert.pem"])}, + {cacertfile, filename:join([PrivDir, "revoked", "cacerts.pem"])}], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + ssl_crl_cache:insert({file, filename:join([PrivDir, "erlangCA", "crl.pem"])}), + ssl_crl_cache:insert({file, filename:join([PrivDir, "otpCA", "crl.pem"])}), + + ClientOpts = case proplists:get_value(idp_crl, Config) of + true -> + [{cacertfile, filename:join([PrivDir, "revoked", "cacerts.pem"])}, + {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}, + {crl_check, Check}, + {verify, verify_peer}]; + false -> + ?config(crl_cache_opts, Config) ++ + [{cacertfile, filename:join([PrivDir, "revoked", "cacerts.pem"])}, + {crl_check, Check}, + {verify, verify_peer}] + end, + + crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, + "certificate revoked"). + +crl_verify_no_crl() -> + [{doc,"Verify a simple CRL chain when the CRL is missing"}]. +crl_verify_no_crl(Config) when is_list(Config) -> + PrivDir = proplists:get_value(cert_dir, Config), + Check = proplists:get_value(crl_check, Config), + ServerOpts = [{keyfile, filename:join([PrivDir, "server", "key.pem"])}, + {certfile, filename:join([PrivDir, "server", "cert.pem"])}, + {cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}], + ClientOpts = case proplists:get_value(idp_crl, Config) of + true -> + [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}, + {crl_check, Check}, + {crl_cache, {ssl_crl_cache, {internal, [{http, 5000}]}}}, + {verify, verify_peer}]; + false -> + [{cacertfile, filename:join([PrivDir, "server", "cacerts.pem"])}, + {crl_check, Check}, + {verify, verify_peer}] + end, + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% In case we're running an HTTP server that serves CRLs, let's + %% rename those files, so the CRL is absent when we try to verify + %% it. + %% + %% If we're not using an HTTP server, we just need to refrain from + %% adding the CRLs to the cache manually. + rename_crl(filename:join([PrivDir, "erlangCA", "crl.pem"])), + rename_crl(filename:join([PrivDir, "otpCA", "crl.pem"])), + + %% The expected outcome when the CRL is missing depends on the + %% crl_check setting. + case Check of + true -> + %% The error "revocation status undetermined" gets turned + %% into "bad certificate". + crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, + "bad certificate"); + peer -> + crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, + "bad certificate"); + best_effort -> + %% In "best effort" mode, we consider the certificate not + %% to be revoked if we can't find the appropriate CRL. + crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts) + end. + +crl_hash_dir_collision() -> + [{doc,"Verify ssl_crl_hash_dir behaviour with hash collisions"}]. +crl_hash_dir_collision(Config) when is_list(Config) -> + PrivDir = ?config(cert_dir, Config), + Check = ?config(crl_check, Config), + + %% Create two CAs whose names hash to the same value + CA1 = "hash-collision-0000000000", + CA2 = "hash-collision-0258497583", + CertsConfig = make_certs:make_config([]), + make_certs:intermediateCA(PrivDir, CA1, "erlangCA", CertsConfig), + make_certs:intermediateCA(PrivDir, CA2, "erlangCA", CertsConfig), + + make_certs:enduser(PrivDir, CA1, "collision-client-1", CertsConfig), + make_certs:enduser(PrivDir, CA2, "collision-client-2", CertsConfig), + + [ServerOpts1, ServerOpts2] = + [ + [{keyfile, filename:join([PrivDir, EndUser, "key.pem"])}, + {certfile, filename:join([PrivDir, EndUser, "cert.pem"])}, + {cacertfile, filename:join([PrivDir, EndUser, "cacerts.pem"])}] + || EndUser <- ["collision-client-1", "collision-client-2"]], + + %% Add CRLs for our new CAs into the CRL hash directory. + %% Find the hashes with 'openssl crl -noout -hash -in crl.pem'. + CrlDir = filename:join(PrivDir, "crls"), + populate_crl_hash_dir(PrivDir, CrlDir, + [{CA1, "b68fc624"}, + {CA2, "b68fc624"}], + replace), + + ClientOpts = ?config(crl_cache_opts, Config) ++ + [{cacertfile, filename:join([PrivDir, "erlangCA", "cacerts.pem"])}, + {crl_check, Check}, + {verify, verify_peer}], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% Neither certificate revoked; both succeed. + crl_verify_valid(Hostname, ServerNode, ServerOpts1, ClientNode, ClientOpts), + crl_verify_valid(Hostname, ServerNode, ServerOpts2, ClientNode, ClientOpts), + + make_certs:revoke(PrivDir, CA1, "collision-client-1", CertsConfig), + populate_crl_hash_dir(PrivDir, CrlDir, + [{CA1, "b68fc624"}, + {CA2, "b68fc624"}], + replace), + + %% First certificate revoked; first fails, second succeeds. + crl_verify_error(Hostname, ServerNode, ServerOpts1, ClientNode, ClientOpts, + "certificate revoked"), + crl_verify_valid(Hostname, ServerNode, ServerOpts2, ClientNode, ClientOpts), + + make_certs:revoke(PrivDir, CA2, "collision-client-2", CertsConfig), + populate_crl_hash_dir(PrivDir, CrlDir, + [{CA1, "b68fc624"}, + {CA2, "b68fc624"}], + replace), + + %% Second certificate revoked; both fail. + crl_verify_error(Hostname, ServerNode, ServerOpts1, ClientNode, ClientOpts, + "certificate revoked"), + crl_verify_error(Hostname, ServerNode, ServerOpts2, ClientNode, ClientOpts, + "certificate revoked"), + + ok. + +crl_hash_dir_expired() -> + [{doc,"Verify ssl_crl_hash_dir behaviour with expired CRLs"}]. +crl_hash_dir_expired(Config) when is_list(Config) -> + PrivDir = ?config(cert_dir, Config), + Check = ?config(crl_check, Config), + + CA = "CRL-maybe-expired-CA", + %% Add "issuing distribution point", to ensure that verification + %% fails if there is no valid CRL. + CertsConfig = make_certs:make_config([{issuing_distribution_point, true}]), + make_certs:can_generate_expired_crls(CertsConfig) + orelse throw({skip, "cannot generate CRLs with expiry date in the past"}), + make_certs:intermediateCA(PrivDir, CA, "erlangCA", CertsConfig), + EndUser = "CRL-maybe-expired", + make_certs:enduser(PrivDir, CA, EndUser, CertsConfig), + + ServerOpts = [{keyfile, filename:join([PrivDir, EndUser, "key.pem"])}, + {certfile, filename:join([PrivDir, EndUser, "cert.pem"])}, + {cacertfile, filename:join([PrivDir, EndUser, "cacerts.pem"])}], + ClientOpts = ?config(crl_cache_opts, Config) ++ + [{cacertfile, filename:join([PrivDir, CA, "cacerts.pem"])}, + {crl_check, Check}, + {verify, verify_peer}], + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + %% First make a CRL that expired yesterday. + make_certs:gencrl(PrivDir, CA, CertsConfig, -24), + CrlDir = filename:join(PrivDir, "crls"), + populate_crl_hash_dir(PrivDir, CrlDir, + [{CA, "1627b4b0"}], + replace), + + %% Since the CRL has expired, it's treated as missing, and the + %% outcome depends on the crl_check setting. + case Check of + true -> + %% The error "revocation status undetermined" gets turned + %% into "bad certificate". + crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, + "bad certificate"); + peer -> + crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, + "bad certificate"); + best_effort -> + %% In "best effort" mode, we consider the certificate not + %% to be revoked if we can't find the appropriate CRL. + crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts) + end, + + %% Now make a CRL that expires tomorrow. + make_certs:gencrl(PrivDir, CA, CertsConfig, 24), + CrlDir = filename:join(PrivDir, "crls"), + populate_crl_hash_dir(PrivDir, CrlDir, + [{CA, "1627b4b0"}], + add), + + %% With a valid CRL, verification should always pass. + crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts), + + ok. + +crl_verify_valid(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts) -> + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, + send_recv_result_active, []}}, + {options, ClientOpts}]), + + ssl_test_lib:check_result(Client, ok, Server, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +crl_verify_error(Hostname, ServerNode, ServerOpts, ClientNode, ClientOpts, ExpectedAlert) -> + Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, + {from, self()}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + Client = ssl_test_lib:start_client_error([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {options, ClientOpts}]), + receive + {Server, AlertOrClose} -> + ct:pal("Server Alert or Close ~p", [AlertOrClose]) + end, + ssl_test_lib:check_result(Client, {error, {tls_alert, ExpectedAlert}}). + +%%-------------------------------------------------------------------- +%% Internal functions ------------------------------------------------ +%%-------------------------------------------------------------------- +is_idp(idp_crl) -> + true; +is_idp(_) -> + false. + +init_certs(_,v1_crl, Config) -> + {[{v2_crls, false}], Config}; +init_certs(_, idp_crl, Config) -> + Port = proplists:get_value(httpd_port, Config), + {[{crl_port,Port}, + {issuing_distribution_point, true}], Config + }; +init_certs(_,_,Config) -> + {[], Config}. + +make_dir_path(PathComponents) -> + lists:foldl(fun(F,P0) -> file:make_dir(P=filename:join(P0,F)), P end, + "", + PathComponents). + +rename_crl(Filename) -> + file:rename(Filename, Filename ++ ".notfound"). + +populate_crl_hash_dir(CertDir, CrlDir, CAsHashes, AddOrReplace) -> + ok = filelib:ensure_dir(filename:join(CrlDir, "crls")), + case AddOrReplace of + replace -> + %% Delete existing files, so we can override them. + [ok = file:delete(FileToDelete) || + {_CA, Hash} <- CAsHashes, + FileToDelete <- filelib:wildcard( + filename:join(CrlDir, Hash ++ ".r*"))]; + add -> + ok + end, + %% Create new files, incrementing suffix if needed to find unique names. + [{ok, _} = + file:copy(filename:join([CertDir, CA, "crl.pem"]), + find_free_name(CrlDir, Hash, 0)) + || {CA, Hash} <- CAsHashes], + ok. + +find_free_name(CrlDir, Hash, N) -> + Name = filename:join(CrlDir, Hash ++ ".r" ++ integer_to_list(N)), + case filelib:is_file(Name) of + true -> + find_free_name(CrlDir, Hash, N + 1); + false -> + Name + end. diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 7bfd678f4b..5ebf9bb2de 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2013. All Rights Reserved. +%% Copyright Ericsson AB 2007-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -20,6 +21,7 @@ -module(ssl_dist_SUITE). -include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). %% Note: This directive should only be used in test suites. -compile(export_all). @@ -38,12 +40,11 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> - [{ct_hooks,[ts_install_cth]}]. - all() -> - [basic, payload, plain_options, plain_verify_options]. + [basic, payload, plain_options, plain_verify_options, nodelay_option, + listen_port_options, listen_options, connect_options, use_interface, + verify_fun_fail, verify_fun_pass, crl_check_pass, crl_check_fail, + crl_check_best_effort, crl_cache_check_pass, crl_cache_check_fail]. groups() -> []. @@ -90,17 +91,15 @@ init_per_testcase(Case, Config) when is_list(Config) -> common_init(Case, Config). common_init(Case, Config) -> - Dog = ?t:timetrap(?t:seconds(?DEFAULT_TIMETRAP_SECS)), - [{watchdog, Dog},{testcase, Case}|Config]. + ct:timetrap({seconds, ?DEFAULT_TIMETRAP_SECS}), + [{testcase, Case}|Config]. end_per_testcase(Case, Config) when is_list(Config) -> Flags = proplists:get_value(old_flags, Config), - os:putenv("ERL_FLAGS", Flags), + catch os:putenv("ERL_FLAGS", Flags), common_end(Case, Config). -common_end(_, Config) -> - Dog = ?config(watchdog, Config), - ?t:timetrap_cancel(Dog), +common_end(_, _Config) -> ok. %%-------------------------------------------------------------------- @@ -198,7 +197,7 @@ payload(Config) when is_list(Config) -> ok = apply_on_ssl_node( NH2, fun () -> - Msg = crypto:rand_bytes(100000), + Msg = crypto:strong_rand_bytes(100000), SslPid ! {self(), Msg}, receive {SslPid, Msg} -> @@ -255,6 +254,422 @@ plain_verify_options(Config) when is_list(Config) -> stop_ssl_node(NH1), stop_ssl_node(NH2), success(Config). +%%-------------------------------------------------------------------- +nodelay_option() -> + [{doc,"Test specifying dist_nodelay option"}]. +nodelay_option(Config) -> + try + %% The default is 'true', so try setting it to 'false'. + application:set_env(kernel, dist_nodelay, false), + basic(Config) + after + application:unset_env(kernel, dist_nodelay) + end. + +listen_port_options() -> + [{doc, "Test specifying listening ports"}]. +listen_port_options(Config) when is_list(Config) -> + %% Start a node, and get the port number it's listening on. + NH1 = start_ssl_node(Config), + Node1 = NH1#node_handle.nodename, + Name1 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), + {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), + {Name1, Port1} = lists:keyfind(Name1, 1, NodesPorts), + + %% Now start a second node, configuring it to use the same port + %% number. + PortOpt1 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++ + " inet_dist_listen_max " ++ integer_to_list(Port1), + + try start_ssl_node([{additional_dist_opts, PortOpt1} | Config]) of + #node_handle{} -> + %% If the node was able to start, it didn't take the port + %% option into account. + exit(unexpected_success) + catch + exit:{accept_failed, timeout} -> + %% The node failed to start, as expected. + ok + end, + + %% Try again, now specifying a high max port. + PortOpt2 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++ + " inet_dist_listen_max 65535", + NH2 = start_ssl_node([{additional_dist_opts, PortOpt2} | Config]), + Node2 = NH2#node_handle.nodename, + Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)), + {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0), + {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2), + + %% The new port should be higher: + if Port2 > Port1 -> + ok; + true -> + error({port, Port2, not_higher_than, Port1}) + end, + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). +%%-------------------------------------------------------------------- +listen_options() -> + [{doc, "Test inet_dist_listen_options"}]. +listen_options(Config) when is_list(Config) -> + try_setting_priority(fun do_listen_options/2, Config). + +do_listen_options(Prio, Config) -> + PriorityString0 = "[{priority,"++integer_to_list(Prio)++"}]", + PriorityString = + case os:cmd("echo [{a,1}]") of + "[{a,1}]"++_ -> + PriorityString0; + _ -> + %% Some shells need quoting of [{}] + "'"++PriorityString0++"'" + end, + + Options = "-kernel inet_dist_listen_options " ++ PriorityString, + + NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), + NH2 = start_ssl_node([{additional_dist_opts, Options} | Config]), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + PrioritiesNode1 = + apply_on_ssl_node(NH1, fun get_socket_priorities/0), + PrioritiesNode2 = + apply_on_ssl_node(NH2, fun get_socket_priorities/0), + + Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio], + ?t:format("Elevated1: ~p~n", [Elevated1]), + Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio], + ?t:format("Elevated2: ~p~n", [Elevated2]), + [_|_] = Elevated1, + [_|_] = Elevated2, + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). +%%-------------------------------------------------------------------- +connect_options() -> + [{doc, "Test inet_dist_connect_options"}]. +connect_options(Config) when is_list(Config) -> + try_setting_priority(fun do_connect_options/2, Config). + +do_connect_options(Prio, Config) -> + PriorityString0 = "[{priority,"++integer_to_list(Prio)++"}]", + PriorityString = + case os:cmd("echo [{a,1}]") of + "[{a,1}]"++_ -> + PriorityString0; + _ -> + %% Some shells need quoting of [{}] + "'"++PriorityString0++"'" + end, + + Options = "-kernel inet_dist_connect_options " ++ PriorityString, + + NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), + NH2 = start_ssl_node([{additional_dist_opts, Options} | Config]), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + PrioritiesNode1 = + apply_on_ssl_node(NH1, fun get_socket_priorities/0), + PrioritiesNode2 = + apply_on_ssl_node(NH2, fun get_socket_priorities/0), + + Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio], + ?t:format("Elevated1: ~p~n", [Elevated1]), + Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio], + ?t:format("Elevated2: ~p~n", [Elevated2]), + %% Node 1 will have a socket with elevated priority. + [_|_] = Elevated1, + %% Node 2 will not, since it only applies to outbound connections. + [] = Elevated2, + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). +%%-------------------------------------------------------------------- +use_interface() -> + [{doc, "Test inet_dist_use_interface"}]. +use_interface(Config) when is_list(Config) -> + %% Force the node to listen only on the loopback interface. + IpString = "'{127,0,0,1}'", + Options = "-kernel inet_dist_use_interface " ++ IpString, + + %% Start a node, and get the port number it's listening on. + NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), + Node1 = NH1#node_handle.nodename, + Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), + {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), + {Name, Port} = lists:keyfind(Name, 1, NodesPorts), + + %% Now find the socket listening on that port, and check its sockname. + Sockets = apply_on_ssl_node( + NH1, + fun() -> + [inet:sockname(P) || + P <- inet_ports(), + {ok, Port} =:= (catch inet:port(P))] + end), + %% And check that it's actually listening on localhost. + [{ok,{{127,0,0,1},Port}}] = Sockets, + + stop_ssl_node(NH1), + success(Config). +%%-------------------------------------------------------------------- +verify_fun_fail() -> + [{doc,"Test specifying verify_fun with a function that always fails"}]. +verify_fun_fail(Config) when is_list(Config) -> + DistOpts = "-ssl_dist_opt " + "server_verify verify_peer server_verify_fun " + "\"{ssl_dist_SUITE,verify_fail_always,{}}\" " + "client_verify verify_peer client_verify_fun " + "\"{ssl_dist_SUITE,verify_fail_always,{}}\" ", + + NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + Node2 = NH2#node_handle.nodename, + + pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + %% Check that the function ran on the client node. + [{verify_fail_always_ran, true}] = + apply_on_ssl_node(NH1, fun () -> ets:tab2list(verify_fun_ran) end), + %% On the server node, it wouldn't run, because the server didn't + %% request a certificate from the client. + undefined = + apply_on_ssl_node(NH2, fun () -> ets:info(verify_fun_ran) end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +verify_fail_always(_Certificate, _Event, _State) -> + %% Create an ETS table, to record the fact that the verify function ran. + %% Spawn a new process, to avoid the ETS table disappearing. + Parent = self(), + spawn( + fun() -> + ets:new(verify_fun_ran, [public, named_table]), + ets:insert(verify_fun_ran, {verify_fail_always_ran, true}), + Parent ! go_ahead, + timer:sleep(infinity) + end), + receive go_ahead -> ok end, + {fail, bad_certificate}. + +%%-------------------------------------------------------------------- +verify_fun_pass() -> + [{doc,"Test specifying verify_fun with a function that always succeeds"}]. +verify_fun_pass(Config) when is_list(Config) -> + DistOpts = "-ssl_dist_opt " + "server_verify verify_peer server_verify_fun " + "\"{ssl_dist_SUITE,verify_pass_always,{}}\" " + "server_fail_if_no_peer_cert true " + "client_verify verify_peer client_verify_fun " + "\"{ssl_dist_SUITE,verify_pass_always,{}}\" ", + + NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + Node1 = NH1#node_handle.nodename, + NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + %% Check that the function ran on the client node. + [{verify_pass_always_ran, true}] = + apply_on_ssl_node(NH1, fun () -> ets:tab2list(verify_fun_ran) end), + %% Check that it ran on the server node as well. The server + %% requested and verified the client's certificate because we + %% passed fail_if_no_peer_cert. + [{verify_pass_always_ran, true}] = + apply_on_ssl_node(NH2, fun () -> ets:tab2list(verify_fun_ran) end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +verify_pass_always(_Certificate, _Event, State) -> + %% Create an ETS table, to record the fact that the verify function ran. + %% Spawn a new process, to avoid the ETS table disappearing. + Parent = self(), + spawn( + fun() -> + ets:new(verify_fun_ran, [public, named_table]), + ets:insert(verify_fun_ran, {verify_pass_always_ran, true}), + Parent ! go_ahead, + timer:sleep(infinity) + end), + receive go_ahead -> ok end, + {valid, State}. +%%-------------------------------------------------------------------- +crl_check_pass() -> + [{doc,"Test crl_check with non-revoked certificate"}]. +crl_check_pass(Config) when is_list(Config) -> + DistOpts = "-ssl_dist_opt client_crl_check true", + NewConfig = + [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + + NH1 = start_ssl_node(NewConfig), + Node1 = NH1#node_handle.nodename, + NH2 = start_ssl_node(NewConfig), + Node2 = NH2#node_handle.nodename, + + PrivDir = ?config(priv_dir, Config), + cache_crls_on_ssl_nodes(PrivDir, ["erlangCA", "otpCA"], [NH1, NH2]), + + %% The server's certificate is not revoked, so connection succeeds. + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +%%-------------------------------------------------------------------- +crl_check_fail() -> + [{doc,"Test crl_check with revoked certificate"}]. +crl_check_fail(Config) when is_list(Config) -> + DistOpts = "-ssl_dist_opt client_crl_check true", + NewConfig = + [{many_verify_opts, true}, + %% The server uses a revoked certificate. + {server_cert_dir, "revoked"}, + {additional_dist_opts, DistOpts}] ++ Config, + + NH1 = start_ssl_node(NewConfig), + %%Node1 = NH1#node_handle.nodename, + NH2 = start_ssl_node(NewConfig), + Node2 = NH2#node_handle.nodename, + + PrivDir = ?config(priv_dir, Config), + cache_crls_on_ssl_nodes(PrivDir, ["erlangCA", "otpCA"], [NH1, NH2]), + + %% The server's certificate is revoked, so connection fails. + pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +%%-------------------------------------------------------------------- +crl_check_best_effort() -> + [{doc,"Test specifying crl_check as best_effort"}]. +crl_check_best_effort(Config) when is_list(Config) -> + DistOpts = "-ssl_dist_opt " + "server_verify verify_peer server_crl_check best_effort", + NewConfig = + [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + + %% We don't have the correct CRL at hand, but since crl_check is + %% best_effort, we accept it anyway. + NH1 = start_ssl_node(NewConfig), + Node1 = NH1#node_handle.nodename, + NH2 = start_ssl_node(NewConfig), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +%%-------------------------------------------------------------------- +crl_cache_check_pass() -> + [{doc,"Test specifying crl_check with custom crl_cache module"}]. +crl_cache_check_pass(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + NodeDir = filename:join([PrivDir, "Certs"]), + DistOpts = "-ssl_dist_opt " + "client_crl_check true " + "client_crl_cache " + "\"{ssl_dist_SUITE,{\\\"" ++ NodeDir ++ "\\\",[]}}\"", + NewConfig = + [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + + NH1 = start_ssl_node(NewConfig), + Node1 = NH1#node_handle.nodename, + NH2 = start_ssl_node(NewConfig), + Node2 = NH2#node_handle.nodename, + + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +%%-------------------------------------------------------------------- +crl_cache_check_fail() -> + [{doc,"Test custom crl_cache module with revoked certificate"}]. +crl_cache_check_fail(Config) when is_list(Config) -> + PrivDir = ?config(priv_dir, Config), + NodeDir = filename:join([PrivDir, "Certs"]), + DistOpts = "-ssl_dist_opt " + "client_crl_check true " + "client_crl_cache " + "\"{ssl_dist_SUITE,{\\\"" ++ NodeDir ++ "\\\",[]}}\"", + NewConfig = + [{many_verify_opts, true}, + %% The server uses a revoked certificate. + {server_cert_dir, "revoked"}, + {additional_dist_opts, DistOpts}] ++ Config, + + NH1 = start_ssl_node(NewConfig), + NH2 = start_ssl_node(NewConfig), + Node2 = NH2#node_handle.nodename, + + pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), + + [] = apply_on_ssl_node(NH1, fun () -> nodes() end), + [] = apply_on_ssl_node(NH2, fun () -> nodes() end), + + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). + +%% ssl_crl_cache_api callbacks +lookup(_DistributionPoint, _DbHandle) -> + not_available. + +select({rdnSequence, NameParts}, {NodeDir, _}) -> + %% Extract the CN from the issuer name... + [CN] = [CN || + [#'AttributeTypeAndValue'{ + type = ?'id-at-commonName', + value = <<_, _, CN/binary>>}] <- NameParts], + %% ...and use that as the directory name to find the CRL. + error_logger:info_report([{found_cn, CN}]), + CRLFile = filename:join([NodeDir, CN, "crl.pem"]), + {ok, PemBin} = file:read_file(CRLFile), + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + CRLs. + +fresh_crl(_DistributionPoint, CRL) -> + CRL. %%-------------------------------------------------------------------- %%% Internal functions ----------------------------------------------- @@ -269,6 +684,32 @@ tstsrvr_format(Fmt, ArgList) -> send_to_tstcntrl(Message) -> send_to_tstsrvr({message, Message}). +try_setting_priority(TestFun, Config) -> + Prio = 1, + case gen_udp:open(0, [{priority,Prio}]) of + {ok,Socket} -> + case inet:getopts(Socket, [priority]) of + {ok,[{priority,Prio}]} -> + ok = gen_udp:close(Socket), + TestFun(Prio, Config); + _ -> + ok = gen_udp:close(Socket), + {skip, + "Can not set priority "++integer_to_list(Prio)++ + " on socket"} + end; + {error,_} -> + {skip, "Can not set priority on socket"} + end. + +get_socket_priorities() -> + [Priority || + {ok,[{priority,Priority}]} <- + [inet:getopts(Port, [priority]) || Port <- inet_ports()]]. + +inet_ports() -> + [Port || Port <- erlang:ports(), + element(2, erlang:port_info(Port, name)) =:= "tcp_inet"]. %% %% test_server side api @@ -315,7 +756,7 @@ start_ssl_node(Config) -> start_ssl_node(Config, XArgs) -> Name = mk_node_name(Config), - SSL = ?config(ssl_opts, Config), + SSL = proplists:get_value(ssl_opts, Config), SSLDistOpts = setup_dist_opts(Config), start_ssl_node_raw(Name, SSL ++ " " ++ SSLDistOpts ++ XArgs). @@ -324,7 +765,7 @@ start_ssl_node_raw(Name, Args) -> [binary, {packet, 4}, {active, false}]), {ok, ListenPort} = inet:port(LSock), CmdLine = mk_node_cmdline(ListenPort, Name, Args), - ?t:format("Attempting to start ssl node ~s: ~s~n", [Name, CmdLine]), + ?t:format("Attempting to start ssl node ~ts: ~ts~n", [Name, CmdLine]), case open_port({spawn, CmdLine}, []) of Port when is_port(Port) -> unlink(Port), @@ -341,6 +782,19 @@ start_ssl_node_raw(Name, Args) -> exit({failed_to_start_node, Name, Error}) end. +cache_crls_on_ssl_nodes(PrivDir, CANames, NHs) -> + [begin + File = filename:join([PrivDir, "Certs", CAName, "crl.pem"]), + {ok, PemBin} = file:read_file(File), + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + ok = apply_on_ssl_node(NH, ssl_manager, insert_crls, + ["no_distribution_point", CRLs, dist]) + end + || NH <- NHs, CAName <- CANames], + ok. + %% %% command line creation %% @@ -351,17 +805,13 @@ host_name() -> Host. mk_node_name(Config) -> - {A, B, C} = erlang:now(), - Case = ?config(testcase, Config), + N = erlang:unique_integer([positive]), + Case = proplists:get_value(testcase, Config), atom_to_list(?MODULE) ++ "_" ++ atom_to_list(Case) ++ "_" - ++ integer_to_list(A) - ++ "-" - ++ integer_to_list(B) - ++ "-" - ++ integer_to_list(C). + ++ integer_to_list(N). mk_node_cmdline(ListenPort, Name, Args) -> Static = "-detached -noinput", @@ -380,11 +830,13 @@ mk_node_cmdline(ListenPort, Name, Args) -> ++ NameSw ++ " " ++ Name ++ " " ++ "-pa " ++ Pa ++ " " ++ "-run application start crypto -run application start public_key " + ++ "-eval 'net_kernel:verbose(1)' " ++ "-run " ++ atom_to_list(?MODULE) ++ " cnct2tstsrvr " ++ host_name() ++ " " ++ integer_to_list(ListenPort) ++ " " ++ Args ++ " " ++ "-env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ Name ++ " " + ++ "-kernel error_logger \"{file,\\\"" ++ Pwd ++ "/error_log." ++ Name ++ "\\\"}\" " ++ "-setcookie " ++ atom_to_list(erlang:get_cookie()). %% @@ -590,12 +1042,10 @@ rand_bin(N) -> rand_bin(0, Acc) -> Acc; rand_bin(N, Acc) -> - rand_bin(N-1, [random:uniform(256)-1|Acc]). + rand_bin(N-1, [rand:uniform(256)-1|Acc]). make_randfile(Dir) -> {ok, IoDev} = file:open(filename:join([Dir, "RAND"]), [write]), - {A, B, C} = erlang:now(), - random:seed(A, B, C), ok = file:write(IoDev, rand_bin(1024)), file:close(IoDev). @@ -611,13 +1061,13 @@ do_append_files([F|Fs], RF) -> do_append_files(Fs, RF). setup_certs(Config) -> - PrivDir = ?config(priv_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), NodeDir = filename:join([PrivDir, "Certs"]), RGenDir = filename:join([NodeDir, "rand_gen"]), ok = file:make_dir(NodeDir), ok = file:make_dir(RGenDir), make_randfile(RGenDir), - make_certs:all(RGenDir, NodeDir), + {ok, _} = make_certs:all(RGenDir, NodeDir), SDir = filename:join([NodeDir, "server"]), SC = filename:join([SDir, "cert.pem"]), SK = filename:join([SDir, "key.pem"]), @@ -630,12 +1080,12 @@ setup_certs(Config) -> append_files([CK, CC], CKC). setup_dist_opts(Config) -> - PrivDir = ?config(priv_dir, Config), - DataDir = ?config(data_dir, Config), + PrivDir = proplists:get_value(priv_dir, Config), + DataDir = proplists:get_value(data_dir, Config), Dhfile = filename:join([DataDir, "dHParam.pem"]), NodeDir = filename:join([PrivDir, "Certs"]), - SDir = filename:join([NodeDir, "server"]), - CDir = filename:join([NodeDir, "client"]), + SDir = filename:join([NodeDir, proplists:get_value(server_cert_dir, Config, "server")]), + CDir = filename:join([NodeDir, proplists:get_value(client_cert_dir, Config, "client")]), SC = filename:join([SDir, "cert.pem"]), SK = filename:join([SDir, "key.pem"]), SKC = filename:join([SDir, "keycert.pem"]), @@ -693,7 +1143,7 @@ add_ssl_opts_config(Config) -> %% just point out ssl ebin with -pa. %% try - Dir = ?config(priv_dir, Config), + Dir = proplists:get_value(priv_dir, Config), LibDir = code:lib_dir(), Apps = application:which_applications(), {value, {stdlib, _, STDL_VSN}} = lists:keysearch(stdlib, 1, Apps), diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl index a40f07fd07..a671e3e307 100644 --- a/lib/ssl/test/ssl_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_handshake_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -26,49 +27,152 @@ -include_lib("common_test/include/ct.hrl"). -include("ssl_internal.hrl"). -include("tls_handshake.hrl"). +-include_lib("public_key/include/public_key.hrl"). %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. +all() -> [decode_hello_handshake, + decode_single_hello_extension_correctly, + decode_supported_elliptic_curves_hello_extension_correctly, + decode_unknown_hello_extension_correctly, + encode_single_hello_sni_extension_correctly, + decode_single_hello_sni_extension_correctly, + decode_empty_server_sni_correctly, + select_proper_tls_1_2_rsa_default_hashsign, + ignore_hassign_extension_pre_tls_1_2]. + +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + Config. +end_per_suite(Config) -> + Config. + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_,Config) -> + Config. + +init_per_testcase(ignore_hassign_extension_pre_tls_1_2, Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + case is_supported(sha512) of + true -> + ssl:start(), + %% make rsa certs using oppenssl + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), + Config = ssl_test_lib:cert_options(Config0), + ct:timetrap({seconds, 5}), + Config; + false -> + {skip, "Crypto did not support sha512"} + end + catch _:_ -> + {skip, "Crypto did not start"} + end; +init_per_testcase(_, Config0) -> + Config0. -all() -> [ - decode_hello_handshake, - decode_single_hello_extension_correctly, - decode_unknown_hello_extension_correctly]. +end_per_testcase(ignore_hassign_extension_pre_tls_1_2, _) -> + crypto:stop(); +end_per_testcase(_TestCase, Config) -> + Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- decode_hello_handshake(_Config) -> - HelloPacket = <<16#02, 16#00, 16#00, - 16#44, 16#03, 16#03, 16#4e, 16#7f, 16#c1, 16#03, 16#35, - 16#c2, 16#07, 16#b9, 16#4a, 16#58, 16#af, 16#34, 16#07, - 16#a6, 16#7e, 16#ef, 16#52, 16#cb, 16#e0, 16#ea, 16#b7, - 16#aa, 16#47, 16#c8, 16#c2, 16#2c, 16#66, 16#fa, 16#f8, - 16#09, 16#42, 16#cf, 16#00, 16#c0, 16#30, 16#00, 16#00, - 16#1c, - 16#00, 16#0b, 16#00, 16#04, 16#03, 16#00, 16#01, 16#02, % ec_point_formats - 16#ff, 16#01, 16#00, 16#01, 16#00, %% renegotiate - 16#00, 16#23, - 16#00, 16#00, 16#33, 16#74, 16#00, 16#07, 16#06, 16#73, - 16#70, 16#64, 16#79, 16#2f, 16#32>>, - - Version = {3, 0}, - {Records, _Buffer} = tls_handshake:get_tls_handshake(Version, HelloPacket, <<>>), - - {Hello, _Data} = hd(Records), - #renegotiation_info{renegotiated_connection = <<0>>} = Hello#server_hello.renegotiation_info. + HelloPacket = <<16#02, 16#00, 16#00, + 16#44, 16#03, 16#03, 16#4e, 16#7f, 16#c1, 16#03, 16#35, + 16#c2, 16#07, 16#b9, 16#4a, 16#58, 16#af, 16#34, 16#07, + 16#a6, 16#7e, 16#ef, 16#52, 16#cb, 16#e0, 16#ea, 16#b7, + 16#aa, 16#47, 16#c8, 16#c2, 16#2c, 16#66, 16#fa, 16#f8, + 16#09, 16#42, 16#cf, 16#00, 16#c0, 16#30, 16#00, 16#00, + 16#1c, + 16#00, 16#0b, 16#00, 16#04, 16#03, 16#00, 16#01, 16#02, % ec_point_formats + 16#ff, 16#01, 16#00, 16#01, 16#00, %% renegotiate + 16#00, 16#23, + 16#00, 16#00, 16#33, 16#74, 16#00, 16#07, 16#06, 16#73, + 16#70, 16#64, 16#79, 16#2f, 16#32>>, + Version = {3, 0}, + {Records, _Buffer} = tls_handshake:get_tls_handshake(Version, HelloPacket, <<>>, + #ssl_options{v2_hello_compatible = false}), + + {Hello, _Data} = hd(Records), + #renegotiation_info{renegotiated_connection = <<0>>} + = (Hello#server_hello.extensions)#hello_extensions.renegotiation_info. + decode_single_hello_extension_correctly(_Config) -> - Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, - Extensions = tls_handshake:dec_hello_extensions(Renegotiation, []), - [{renegotiation_info,#renegotiation_info{renegotiated_connection = <<0>>}}] = Extensions. - + Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, + Extensions = ssl_handshake:decode_hello_extensions(Renegotiation), + #renegotiation_info{renegotiated_connection = <<0>>} + = Extensions#hello_extensions.renegotiation_info. + +decode_supported_elliptic_curves_hello_extension_correctly(_Config) -> + % List of supported and unsupported curves (RFC4492:S5.1.1) + ClientEllipticCurves = [0, tls_v1:oid_to_enum(?sect233k1), 37, tls_v1:oid_to_enum(?sect193r2), 16#badc], + % Construct extension binary - modified version of ssl_handshake:encode_hello_extensions([#elliptic_curves{}], _) + EllipticCurveList = << <<X:16>> || X <- ClientEllipticCurves>>, + ListLen = byte_size(EllipticCurveList), + Len = ListLen + 2, + Extension = <<?UINT16(?ELLIPTIC_CURVES_EXT), ?UINT16(Len), ?UINT16(ListLen), EllipticCurveList/binary>>, + % after decoding we should see only valid curves + #hello_extensions{elliptic_curves = DecodedCurves} = ssl_handshake:decode_hello_extensions(Extension), + #elliptic_curves{elliptic_curve_list = [?sect233k1, ?sect193r2]} = DecodedCurves. decode_unknown_hello_extension_correctly(_Config) -> - FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>, - Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, - Extensions = tls_handshake:dec_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>, []), - [{renegotiation_info,#renegotiation_info{renegotiated_connection = <<0>>}}] = Extensions. - + FourByteUnknown = <<16#CA,16#FE, ?UINT16(4), 3, 0, 1, 2>>, + Renegotiation = <<?UINT16(?RENEGOTIATION_EXT), ?UINT16(1), 0>>, + Extensions = ssl_handshake:decode_hello_extensions(<<FourByteUnknown/binary, Renegotiation/binary>>), + #renegotiation_info{renegotiated_connection = <<0>>} + = Extensions#hello_extensions.renegotiation_info. + +encode_single_hello_sni_extension_correctly(_Config) -> + Exts = #hello_extensions{sni = #sni{hostname = "test.com"}}, + SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08, + $t, $e, $s, $t, $., $c, $o, $m>>, + ExtSize = byte_size(SNI), + HelloExt = <<ExtSize:16/unsigned-big-integer, SNI/binary>>, + Encoded = ssl_handshake:encode_hello_extensions(Exts), + HelloExt = Encoded. + +decode_single_hello_sni_extension_correctly(_Config) -> + Exts = #hello_extensions{sni = #sni{hostname = "test.com"}}, + SNI = <<16#00, 16#00, 16#00, 16#0d, 16#00, 16#0b, 16#00, 16#00, 16#08, + $t, $e, $s, $t, $., $c, $o, $m>>, + Decoded = ssl_handshake:decode_hello_extensions(SNI), + Exts = Decoded. + +decode_empty_server_sni_correctly(_Config) -> + Exts = #hello_extensions{sni = ""}, + SNI = <<?UINT16(?SNI_EXT),?UINT16(0)>>, + Decoded = ssl_handshake:decode_hello_extensions(SNI), + Exts = Decoded. + + +select_proper_tls_1_2_rsa_default_hashsign(_Config) -> + % RFC 5246 section 7.4.1.4.1 tells to use {sha1,rsa} as default signature_algorithm for RSA key exchanges + {sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,3}), + % Older versions use MD5/SHA1 combination + {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,2}), + {md5sha, rsa} = ssl_handshake:select_hashsign_algs(undefined, ?rsaEncryption, {3,0}). + + +ignore_hassign_extension_pre_tls_1_2(Config) -> + Opts = proplists:get_value(server_opts, Config), + CertFile = proplists:get_value(certfile, Opts), + [{_, Cert, _}] = ssl_test_lib:pem_to_der(CertFile), + HashSigns = #hash_sign_algos{hash_sign_algos = [{sha512, rsa}, {sha, dsa}]}, + {sha512, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,3}), {3,3}), + %%% Ignore + {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,2}), {3,2}), + {md5sha, rsa} = ssl_handshake:select_hashsign(HashSigns, Cert, ecdhe_rsa, tls_v1:default_signature_algs({3,0}), {3,0}). + +is_supported(Hash) -> + Algos = crypto:supports(), + Hashs = proplists:get_value(hashs, Algos), + lists:member(Hash, Hashs). diff --git a/lib/ssl/test/ssl_npn_handshake_SUITE.erl b/lib/ssl/test/ssl_npn_handshake_SUITE.erl index 30c0a67a36..c55fa73cfb 100644 --- a/lib/ssl/test/ssl_npn_handshake_SUITE.erl +++ b/lib/ssl/test/ssl_npn_handshake_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,8 +30,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [{group, 'tlsv1.2'}, {group, 'tlsv1.1'}, @@ -70,10 +69,8 @@ init_per_suite(Config) -> try crypto:start() of ok -> ssl:start(), - Result = - (catch make_certs:all(?config(data_dir, Config), - ?config(priv_dir, Config))), - ct:log("Make certs ~p~n", [Result]), + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), + proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -89,8 +86,7 @@ init_per_group(GroupName, Config) -> true -> case ssl_test_lib:sufficient_crypto_support(GroupName) of true -> - ssl_test_lib:init_tls_version(GroupName), - Config; + ssl_test_lib:init_tls_version(GroupName, Config); false -> {skip, "Missing crypto support"} end; @@ -102,6 +98,15 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), + ct:timetrap({seconds, 10}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- @@ -172,7 +177,7 @@ no_client_negotiate_but_server_supports_npn(Config) when is_list(Config) -> run_npn_handshake(Config, [], [{next_protocols_advertised, [<<"spdy/1">>, <<"http/1.1">>, <<"http/1.0">>]}], - {error, next_protocol_not_negotiated}). + {error, protocol_not_negotiated}). %-------------------------------------------------------------------------------- @@ -180,16 +185,16 @@ client_negotiate_server_does_not_support(Config) when is_list(Config) -> run_npn_handshake(Config, [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], - {error, next_protocol_not_negotiated}). + {error, protocol_not_negotiated}). %-------------------------------------------------------------------------------- renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> Data = "hello world", - ClientOpts0 = ?config(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0, - ServerOpts0 = ?config(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, ExpectedProtocol = {ok, <<"http/1.0">>}, @@ -211,7 +216,7 @@ renegotiate_from_client_after_npn_handshake(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- npn_not_supported_client(Config) when is_list(Config) -> - ClientOpts0 = ?config(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), PrefProtocols = {client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}, ClientOpts = [PrefProtocols] ++ ClientOpts0, @@ -226,7 +231,7 @@ npn_not_supported_client(Config) when is_list(Config) -> %-------------------------------------------------------------------------------- npn_not_supported_server(Config) when is_list(Config)-> - ServerOpts0 = ?config(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), AdvProtocols = {next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}, ServerOpts = [AdvProtocols] ++ ServerOpts0, @@ -234,10 +239,10 @@ npn_not_supported_server(Config) when is_list(Config)-> %-------------------------------------------------------------------------------- npn_handshake_session_reused(Config) when is_list(Config)-> - ClientOpts0 = ?config(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), ClientOpts = [{client_preferred_next_protocols, {client, [<<"http/1.0">>], <<"http/1.1">>}}] ++ ClientOpts0, - ServerOpts0 = ?config(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), ServerOpts =[{next_protocols_advertised, [<<"spdy/2">>, <<"http/1.1">>, <<"http/1.0">>]}] ++ ServerOpts0, @@ -288,9 +293,9 @@ npn_handshake_session_reused(Config) when is_list(Config)-> run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> Data = "hello world", - ClientOpts0 = ?config(client_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), ClientOpts = ClientExtraOpts ++ ClientOpts0, - ServerOpts0 = ?config(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), ServerOpts = ServerExtraOpts ++ ServerOpts0, {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -311,8 +316,8 @@ run_npn_handshake(Config, ClientExtraOpts, ServerExtraOpts, ExpectedProtocol) -> assert_npn(Socket, Protocol) -> ct:log("Negotiated Protocol ~p, Expecting: ~p ~n", - [ssl:negotiated_next_protocol(Socket), Protocol]), - Protocol = ssl:negotiated_next_protocol(Socket). + [ssl:negotiated_protocol(Socket), Protocol]), + Protocol = ssl:negotiated_protocol(Socket). assert_npn_and_renegotiate_and_send_data(Socket, Protocol, Data) -> assert_npn(Socket, Protocol), @@ -332,7 +337,7 @@ ssl_receive_and_assert_npn(Socket, Protocol, Data) -> ssl_send(Socket, Data) -> ct:log("Connection info: ~p~n", - [ssl:connection_info(Socket)]), + [ssl:connection_information(Socket)]), ssl:send(Socket, Data). ssl_receive(Socket, Data) -> @@ -340,7 +345,7 @@ ssl_receive(Socket, Data) -> ssl_receive(Socket, Data, Buffer) -> ct:log("Connection info: ~p~n", - [ssl:connection_info(Socket)]), + [ssl:connection_information(Socket)]), receive {ssl, Socket, MoreData} -> ct:log("Received ~p~n",[MoreData]), @@ -360,4 +365,4 @@ ssl_receive(Socket, Data, Buffer) -> connection_info_result(Socket) -> - ssl:connection_info(Socket). + ssl:connection_information(Socket). diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index ef5a02abef..00eb9fee4f 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -32,9 +33,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [encode_and_decode_npn_client_hello_test, encode_and_decode_npn_server_hello_test, @@ -43,89 +41,100 @@ all() -> create_server_hello_with_advertised_protocols_test, create_server_hello_with_no_advertised_protocols_test]. +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- -encode_and_decode_client_hello_test(_Config) -> +encode_and_decode_client_hello_test(Config) -> HandShakeData = create_client_handshake(undefined), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = - tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), - NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation, + tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), + NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation, NextProtocolNegotiation = undefined. %%-------------------------------------------------------------------- -encode_and_decode_npn_client_hello_test(_Config) -> +encode_and_decode_npn_client_hello_test(Config) -> HandShakeData = create_client_handshake(#next_protocol_negotiation{extension_data = <<>>}), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = - tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), - NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation, + tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), + NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation, NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<>>}. %%-------------------------------------------------------------------- -encode_and_decode_server_hello_test(_Config) -> +encode_and_decode_server_hello_test(Config) -> HandShakeData = create_server_handshake(undefined), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = - tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), - NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation, + tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), + NextProtocolNegotiation = (DecodedHandshakeMessage#server_hello.extensions)#hello_extensions.next_protocol_negotiation, NextProtocolNegotiation = undefined. %%-------------------------------------------------------------------- -encode_and_decode_npn_server_hello_test(_Config) -> +encode_and_decode_npn_server_hello_test(Config) -> HandShakeData = create_server_handshake(#next_protocol_negotiation{extension_data = <<6, "spdy/2">>}), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), {[{DecodedHandshakeMessage, _Raw}], _} = - tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>), - NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation, + tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>, #ssl_options{}), + NextProtocolNegotiation = (DecodedHandshakeMessage#server_hello.extensions)#hello_extensions.next_protocol_negotiation, ct:log("~p ~n", [NextProtocolNegotiation]), NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<6, "spdy/2">>}. %%-------------------------------------------------------------------- create_server_hello_with_no_advertised_protocols_test(_Config) -> - Hello = tls_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), false, undefined, undefined, undefined), - undefined = Hello#server_hello.next_protocol_negotiation. + Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), #hello_extensions{}), + undefined = (Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation. %%-------------------------------------------------------------------- create_server_hello_with_advertised_protocols_test(_Config) -> - Hello = tls_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), - false, [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>], undefined, undefined), - #next_protocol_negotiation{extension_data = <<6, "spdy/1", 8, "http/1.0", 8, "http/1.1">>} = - Hello#server_hello.next_protocol_negotiation. + Hello = ssl_handshake:server_hello(<<>>, {3, 0}, create_connection_states(), + #hello_extensions{next_protocol_negotiation = [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>]}), + [<<"spdy/1">>, <<"http/1.0">>, <<"http/1.1">>] = + (Hello#server_hello.extensions)#hello_extensions.next_protocol_negotiation. %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- create_client_handshake(Npn) -> + Vsn = {1, 2}, tls_handshake:encode_handshake(#client_hello{ - client_version = {1, 2}, - random = <<1:256>>, - session_id = <<>>, - cipher_suites = [?TLS_DHE_DSS_WITH_DES_CBC_SHA], - compression_methods = "", - next_protocol_negotiation = Npn, - renegotiation_info = #renegotiation_info{} - }, vsn). + client_version = Vsn, + random = <<1:256>>, + session_id = <<>>, + cipher_suites = [?TLS_DHE_DSS_WITH_DES_CBC_SHA], + compression_methods = "", + extensions = #hello_extensions{ + next_protocol_negotiation = Npn, + renegotiation_info = #renegotiation_info{}} + }, Vsn). create_server_handshake(Npn) -> + Vsn = {1, 2}, tls_handshake:encode_handshake(#server_hello{ - server_version = {1, 2}, - random = <<1:256>>, - session_id = <<>>, - cipher_suite = ?TLS_DHE_DSS_WITH_DES_CBC_SHA, - compression_method = 1, - next_protocol_negotiation = Npn, - renegotiation_info = #renegotiation_info{} - }, vsn). + server_version = Vsn, + random = <<1:256>>, + session_id = <<>>, + cipher_suite = ?TLS_DHE_DSS_WITH_DES_CBC_SHA, + compression_method = 1, + extensions = #hello_extensions{ + next_protocol_negotiation = Npn, + renegotiation_info = #renegotiation_info{}} + }, Vsn). create_connection_states() -> #connection_states{ - pending_read = #connection_state{ - security_parameters = #security_parameters{ - server_random = <<1:256>>, - compression_algorithm = 1, - cipher_suite = ?TLS_DHE_DSS_WITH_DES_CBC_SHA - } - }, - - current_read = #connection_state { - secure_renegotiation = false - } - }. + pending_read = #connection_state{ + security_parameters = #security_parameters{ + server_random = <<1:256>>, + compression_algorithm = 1, + cipher_suite = ?TLS_DHE_DSS_WITH_DES_CBC_SHA + } + }, + current_read = #connection_state { + secure_renegotiation = false + } + }. diff --git a/lib/ssl/test/ssl_packet_SUITE.erl b/lib/ssl/test/ssl_packet_SUITE.erl index 36f7af784d..e49d432c21 100644 --- a/lib/ssl/test/ssl_packet_SUITE.erl +++ b/lib/ssl/test/ssl_packet_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -37,16 +38,16 @@ -define(uint24(X), << ?UINT24(X) >> ). -define(uint32(X), << ?UINT32(X) >> ). -define(uint64(X), << ?UINT64(X) >> ). --define(TIMEOUT, 120000). -define(MANY, 1000). -define(SOME, 50). +-define(BASE_TIMEOUT_SECONDS, 15). +-define(SOME_SCALE, 20). +-define(MANY_SCALE, 20). + %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- - -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, @@ -140,10 +141,8 @@ init_per_suite(Config) -> try crypto:start() of ok -> ssl:start(), - Result = - (catch make_certs:all(?config(data_dir, Config), - ?config(priv_dir, Config))), - ct:log("Make certs ~p~n", [Result]), + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), + proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -158,8 +157,7 @@ init_per_group(GroupName, Config) -> true -> case ssl_test_lib:sufficient_crypto_support(GroupName) of true -> - ssl_test_lib:init_tls_version(GroupName), - Config; + ssl_test_lib:init_tls_version(GroupName, Config); false -> {skip, "Missing crypto support"} end; @@ -172,10 +170,9 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS}), + Config. end_per_testcase(_TestCase, Config) -> @@ -189,6 +186,7 @@ packet_raw_passive_many_small() -> [{doc,"Test packet option {packet, raw} in passive mode."}]. packet_raw_passive_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, raw}", packet(Config, Data, send, passive_raw, ?MANY, raw, false). @@ -198,6 +196,7 @@ packet_raw_passive_some_big() -> [{doc,"Test packet option {packet, raw} in passive mode."}]. packet_raw_passive_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, passive_raw, ?SOME, raw, false). %%-------------------------------------------------------------------- @@ -205,6 +204,7 @@ packet_0_passive_many_small() -> [{doc,"Test packet option {packet, 0} in passive mode."}]. packet_0_passive_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 0}, equivalent to packet raw.", packet(Config, Data, send, passive_raw, ?MANY, 0, false). @@ -213,6 +213,7 @@ packet_0_passive_some_big() -> [{doc,"Test packet option {packet, 0} in passive mode."}]. packet_0_passive_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, passive_raw, ?SOME, 0, false). @@ -221,6 +222,7 @@ packet_1_passive_many_small() -> [{doc,"Test packet option {packet, 1} in passive mode."}]. packet_1_passive_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 1}", packet(Config, Data, send, passive_recv_packet, ?MANY, 1, false). @@ -229,6 +231,7 @@ packet_1_passive_some_big() -> [{doc,"Test packet option {packet, 1} in passive mode."}]. packet_1_passive_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(255, "1")), packet(Config, Data, send, passive_recv_packet, ?SOME, 1, false). @@ -237,6 +240,7 @@ packet_2_passive_many_small() -> [{doc,"Test packet option {packet, 2} in passive mode"}]. packet_2_passive_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 2}", packet(Config, Data, send, passive_recv_packet, ?MANY, 2, false). @@ -245,6 +249,7 @@ packet_2_passive_some_big() -> [{doc,"Test packet option {packet, 2} in passive mode"}]. packet_2_passive_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, passive_recv_packet, ?SOME, 2, false). @@ -253,6 +258,7 @@ packet_4_passive_many_small() -> [{doc,"Test packet option {packet, 4} in passive mode"}]. packet_4_passive_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 4}", packet(Config, Data, send, passive_recv_packet, ?MANY, 4, false). @@ -261,6 +267,7 @@ packet_4_passive_some_big() -> [{doc,"Test packet option {packet, 4} in passive mode"}]. packet_4_passive_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, passive_recv_packet, ?SOME, 4, false). @@ -277,6 +284,7 @@ packet_raw_active_once_some_big() -> [{doc,"Test packet option {packet, raw} in active once mode."}]. packet_raw_active_once_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send_raw, active_once_raw, ?SOME, raw, once). @@ -285,6 +293,7 @@ packet_0_active_once_many_small() -> [{doc,"Test packet option {packet, 0} in active once mode."}]. packet_0_active_once_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 0}", packet(Config, Data, send_raw, active_once_raw, ?MANY, 0, once). @@ -293,6 +302,7 @@ packet_0_active_once_some_big() -> [{doc,"Test packet option {packet, 0} in active once mode."}]. packet_0_active_once_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send_raw, active_once_raw, ?SOME, 0, once). @@ -309,6 +319,7 @@ packet_1_active_once_some_big() -> [{doc,"Test packet option {packet, 1} in active once mode."}]. packet_1_active_once_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(255, "1")), packet(Config, Data, send, active_once_packet, ?SOME, 1, once). @@ -318,6 +329,7 @@ packet_2_active_once_many_small() -> [{doc,"Test packet option {packet, 2} in active once mode"}]. packet_2_active_once_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 2}", packet(Config, Data, send, active_once_packet, ?MANY, 2, once). @@ -326,6 +338,7 @@ packet_2_active_once_some_big() -> [{doc,"Test packet option {packet, 2} in active once mode"}]. packet_2_active_once_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, active_once_raw, ?SOME, 2, once). @@ -335,6 +348,7 @@ packet_4_active_once_many_small() -> packet_4_active_once_many_small(Config) when is_list(Config) -> Data = "Packet option is {packet, 4}", + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), packet(Config, Data, send, active_once_packet, ?MANY, 4, once). %%-------------------------------------------------------------------- @@ -342,6 +356,7 @@ packet_4_active_once_some_big() -> [{doc,"Test packet option {packet, 4} in active once mode"}]. packet_4_active_once_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, active_once_packet, ?SOME, 4, once). @@ -350,6 +365,7 @@ packet_raw_active_many_small() -> [{doc,"Test packet option {packet, raw} in active mode."}]. packet_raw_active_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, raw}", packet(Config, Data, send_raw, active_raw, ?MANY, raw, true). @@ -358,6 +374,7 @@ packet_raw_active_some_big() -> [{doc,"Test packet option {packet, raw} in active mode."}]. packet_raw_active_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send_raw, active_raw, ?SOME, raw, true). @@ -366,6 +383,7 @@ packet_0_active_many_small() -> [{doc,"Test packet option {packet, 0} in active mode."}]. packet_0_active_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 0}", packet(Config, Data, send_raw, active_raw, ?MANY, 0, true). @@ -382,6 +400,7 @@ packet_1_active_many_small() -> [{doc,"Test packet option {packet, 1} in active mode."}]. packet_1_active_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 1}", packet(Config, Data, send, active_packet, ?MANY, 1, true). @@ -390,6 +409,7 @@ packet_1_active_some_big() -> [{doc,"Test packet option {packet, 1} in active mode."}]. packet_1_active_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(255, "1")), packet(Config, Data, send, active_packet, ?SOME, 1, true). @@ -398,6 +418,7 @@ packet_2_active_many_small() -> [{doc,"Test packet option {packet, 2} in active mode"}]. packet_2_active_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 2}", packet(Config, Data, send, active_packet, ?MANY, 2, true). @@ -414,6 +435,7 @@ packet_4_active_many_small() -> [{doc,"Test packet option {packet, 4} in active mode"}]. packet_4_active_many_small(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?MANY_SCALE}), Data = "Packet option is {packet, 4}", packet(Config, Data, send, active_packet, ?MANY, 4, true). @@ -422,6 +444,7 @@ packet_4_active_some_big() -> [{doc,"Test packet option {packet, 4} in active mode"}]. packet_4_active_some_big(Config) when is_list(Config) -> + ct:timetrap({seconds, ?BASE_TIMEOUT_SECONDS * ?SOME_SCALE}), Data = lists:append(lists:duplicate(100, "1234567890")), packet(Config, Data, send, active_packet, ?SOME, 4, true). @@ -430,8 +453,8 @@ packet_send_to_large() -> [{doc,"Test setting the packet option {packet, 2} on the send side"}]. packet_send_to_large(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = lists:append(lists:duplicate(30, "1234567890")), @@ -458,8 +481,8 @@ packet_wait_active() -> [{doc,"Test waiting when complete packages have not arrived"}]. packet_wait_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(lists:duplicate(100, "1234567890")), @@ -491,8 +514,8 @@ packet_wait_passive() -> [{doc,"Test waiting when complete packages have not arrived"}]. packet_wait_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(lists:duplicate(100, "1234567890")), @@ -521,8 +544,8 @@ packet_baddata_active() -> [{doc,"Test that if a bad packet arrives error msg is sent and socket is closed"}]. packet_baddata_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(lists:duplicate(100, "1234567890")), @@ -554,8 +577,8 @@ packet_baddata_passive() -> [{doc,"Test that if a bad packet arrives error msg is sent and socket is closed"}]. packet_baddata_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(lists:duplicate(100, "1234567890")), @@ -589,8 +612,8 @@ packet_size_active() -> packet_size arrives error msg is sent and socket is closed"}]. packet_size_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(lists:duplicate(100, "1234567890")), @@ -623,8 +646,8 @@ packet_size_passive() -> than packet_size arrives error msg is sent and socket is closed"}]. packet_size_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(lists:duplicate(100, "1234567890")), @@ -655,8 +678,8 @@ packet_size_passive(Config) when is_list(Config) -> packet_cdr_decode() -> [{doc,"Test setting the packet option {packet, cdr}, {mode, binary}"}]. packet_cdr_decode(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), %% A valid cdr packet @@ -688,8 +711,8 @@ packet_cdr_decode(Config) when is_list(Config) -> packet_cdr_decode_list() -> [{doc,"Test setting the packet option {packet, cdr} {mode, list}"}]. packet_cdr_decode_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), %% A valid cdr packet @@ -723,8 +746,8 @@ packet_http_decode() -> "(Body will be binary http strings are lists)"}]. packet_http_decode(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Request = "GET / HTTP/1.1\r\n" @@ -805,8 +828,8 @@ packet_http_decode_list() -> [{doc, "Test setting the packet option {packet, http}, {mode, list}" "(Body will be list too)"}]. packet_http_decode_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Request = "GET / HTTP/1.1\r\n" @@ -862,8 +885,8 @@ client_http_decode_list(Socket, HttpRequest) -> packet_http_bin_decode_multi() -> [{doc,"Test setting the packet option {packet, http_bin} with multiple requests"}]. packet_http_bin_decode_multi(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Request = <<"GET / HTTP/1.1\r\n" @@ -952,8 +975,8 @@ packet_http_error_passive() -> " with a incorrect http header."}]. packet_http_error_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Request = "GET / HTTP/1.1\r\n" @@ -1012,8 +1035,8 @@ packet_httph_active() -> [{doc,"Test setting the packet option {packet, httph}"}]. packet_httph_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Trailer = "Content-Encoding: gzip\r\n" @@ -1067,8 +1090,8 @@ client_http_decode_trailer_active(Socket) -> packet_httph_bin_active() -> [{doc,"Test setting the packet option {packet, httph_bin}"}]. packet_httph_bin_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Trailer = "Content-Encoding: gzip\r\n" @@ -1117,8 +1140,8 @@ packet_httph_active_once() -> [{doc,"Test setting the packet option {packet, httph}"}]. packet_httph_active_once(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Trailer = "Content-Encoding: gzip\r\n" @@ -1170,8 +1193,8 @@ packet_httph_bin_active_once() -> [{doc,"Test setting the packet option {packet, httph_bin}"}]. packet_httph_bin_active_once(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Trailer = "Content-Encoding: gzip\r\n" @@ -1224,8 +1247,8 @@ packet_httph_passive() -> [{doc,"Test setting the packet option {packet, httph}"}]. packet_httph_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Trailer = "Content-Encoding: gzip\r\n" @@ -1264,8 +1287,8 @@ packet_httph_bin_passive() -> [{doc,"Test setting the packet option {packet, httph_bin}"}]. packet_httph_bin_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Trailer = "Content-Encoding: gzip\r\n" @@ -1304,8 +1327,8 @@ packet_line_decode() -> [{doc,"Test setting the packet option {packet, line}, {mode, binary}"}]. packet_line_decode(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(lists:flatten(io_lib:format("Line ends here.~n" @@ -1340,8 +1363,8 @@ packet_line_decode_list() -> [{doc,"Test setting the packet option {packet, line}, {mode, list}"}]. packet_line_decode_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = lists:flatten(io_lib:format("Line ends here.~n" @@ -1378,8 +1401,8 @@ packet_asn1_decode() -> [{doc,"Test setting the packet option {packet, asn1}"}]. packet_asn1_decode(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), File = proplists:get_value(certfile, ServerOpts), @@ -1413,8 +1436,8 @@ packet_asn1_decode_list() -> [{doc,"Test setting the packet option {packet, asn1}"}]. packet_asn1_decode_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), File = proplists:get_value(certfile, ServerOpts), @@ -1450,8 +1473,8 @@ packet_tpkt_decode() -> [{doc,"Test setting the packet option {packet, tpkt}"}]. packet_tpkt_decode(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = list_to_binary(add_tpkt_header("TPKT data")), @@ -1482,8 +1505,8 @@ packet_tpkt_decode_list() -> [{doc,"Test setting the packet option {packet, tpkt}"}]. packet_tpkt_decode_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = binary_to_list(list_to_binary(add_tpkt_header("TPKT data"))), @@ -1515,8 +1538,8 @@ packet_tpkt_decode_list(Config) when is_list(Config) -> %% [{doc,"Test setting the packet option {packet, fcgi}"}]. %% packet_fcgi_decode(Config) when is_list(Config) -> -%% ClientOpts = ?config(client_opts, Config), -%% ServerOpts = ?config(server_opts, Config), +%% ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), +%% ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), %% {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), %% Data = ... @@ -1548,8 +1571,8 @@ packet_tpkt_decode_list(Config) when is_list(Config) -> packet_sunrm_decode() -> [{doc,"Test setting the packet option {packet, sunrm}"}]. packet_sunrm_decode(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<11:32, "Hello world">>, @@ -1580,8 +1603,8 @@ packet_sunrm_decode_list() -> [{doc,"Test setting the packet option {packet, sunrm}"}]. packet_sunrm_decode_list(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = binary_to_list(list_to_binary([<<11:32>>, "Hello world"])), @@ -1612,8 +1635,8 @@ header_decode_one_byte_active() -> [{doc,"Test setting the packet option {header, 1}"}]. header_decode_one_byte_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<11:8, "Hello world">>, @@ -1631,8 +1654,8 @@ header_decode_one_byte_active(Config) when is_list(Config) -> {from, self()}, {mfa, {?MODULE, client_header_decode_active, [Data, [11 | <<"Hello world">> ]]}}, - {options, [{active, true}, {header, 1}, - binary | ClientOpts]}]), + {options, [{active, true}, binary, {header, 1} + | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), @@ -1645,8 +1668,8 @@ header_decode_two_bytes_active() -> [{doc,"Test setting the packet option {header, 2}"}]. header_decode_two_bytes_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<11:8, "Hello world">>, @@ -1679,8 +1702,8 @@ header_decode_two_bytes_two_sent_active() -> [{doc,"Test setting the packet option {header, 2} and sending two byte"}]. header_decode_two_bytes_two_sent_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<"He">>, @@ -1688,7 +1711,7 @@ header_decode_two_bytes_two_sent_active(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, server_header_decode_active, - [Data, [$H, $e]]}}, + [Data, [$H, $e | <<>>]]}}, {options, [{active, true}, binary, {header,2}|ServerOpts]}]), @@ -1697,7 +1720,7 @@ header_decode_two_bytes_two_sent_active(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {?MODULE, client_header_decode_active, - [Data, [$H, $e]]}}, + [Data, [$H, $e | <<>>]]}}, {options, [{active, true}, {header, 2}, binary | ClientOpts]}]), @@ -1713,8 +1736,8 @@ header_decode_two_bytes_one_sent_active() -> [{doc,"Test setting the packet option {header, 2} and sending one byte"}]. header_decode_two_bytes_one_sent_active(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<"H">>, @@ -1746,8 +1769,8 @@ header_decode_one_byte_passive() -> [{doc,"Test setting the packet option {header, 1}"}]. header_decode_one_byte_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<11:8, "Hello world">>, @@ -1765,8 +1788,8 @@ header_decode_one_byte_passive(Config) when is_list(Config) -> {from, self()}, {mfa, {?MODULE, client_header_decode_passive, [Data, [11 | <<"Hello world">> ]]}}, - {options, [{active, false}, {header, 1}, - binary | ClientOpts]}]), + {options, [{active, false}, binary, {header, 1} + | ClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), @@ -1779,8 +1802,8 @@ header_decode_two_bytes_passive() -> [{doc,"Test setting the packet option {header, 2}"}]. header_decode_two_bytes_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<11:8, "Hello world">>, @@ -1813,8 +1836,8 @@ header_decode_two_bytes_two_sent_passive() -> [{doc,"Test setting the packet option {header, 2} and sending two byte"}]. header_decode_two_bytes_two_sent_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<"He">>, @@ -1822,7 +1845,7 @@ header_decode_two_bytes_two_sent_passive(Config) when is_list(Config) -> Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, {from, self()}, {mfa, {?MODULE, server_header_decode_passive, - [Data, [$H, $e]]}}, + [Data, [$H, $e | <<>>]]}}, {options, [{active, false}, binary, {header,2}|ServerOpts]}]), @@ -1831,7 +1854,7 @@ header_decode_two_bytes_two_sent_passive(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {?MODULE, client_header_decode_passive, - [Data, [$H, $e]]}}, + [Data, [$H, $e | <<>>]]}}, {options, [{active, false}, {header, 2}, binary | ClientOpts]}]), @@ -1847,8 +1870,8 @@ header_decode_two_bytes_one_sent_passive() -> [{doc,"Test setting the packet option {header, 2} and sending one byte"}]. header_decode_two_bytes_one_sent_passive(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = <<"H">>, @@ -1878,8 +1901,8 @@ header_decode_two_bytes_one_sent_passive(Config) when is_list(Config) -> %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- packet(Config, Data, Send, Recv, Quantity, Packet, Active) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ClientNode}, {port, 0}, @@ -2124,10 +2147,14 @@ client_header_decode_passive(Socket, Packet, Result) -> %% option and the bitsynax makes it obsolete! check_header_result([Byte1 | _], [Byte1]) -> ok; +check_header_result([Byte1 | _], [Byte1| <<>>]) -> + ok; check_header_result([Byte1, Byte2 | _], [Byte1, Byte2]) -> ok; -check_header_result(_,Got) -> - exit({?LINE, Got}). +check_header_result([Byte1, Byte2 | _], [Byte1, Byte2 | <<>>]) -> + ok; +check_header_result(Expected,Got) -> + exit({?LINE, {Expected, Got}}). server_line_packet_decode(Socket, Packet) when is_binary(Packet) -> [L1, L2] = string:tokens(binary_to_list(Packet), "\n"), diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl index f95eae51b7..cb0571d0a7 100644 --- a/lib/ssl/test/ssl_payload_SUITE.erl +++ b/lib/ssl/test/ssl_payload_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -29,8 +30,6 @@ %%-------------------------------------------------------------------- %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, 'tlsv1.2'}, @@ -72,7 +71,7 @@ init_per_suite(Config) -> try crypto:start() of ok -> ssl:start(), - make_certs:all(?config(data_dir, Config), ?config(priv_dir, Config)), + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), proplists:get_value(priv_dir, Config)), ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -87,8 +86,7 @@ init_per_group(GroupName, Config) -> true -> case ssl_test_lib:sufficient_crypto_support(GroupName) of true -> - ssl_test_lib:init_tls_version(GroupName), - Config; + ssl_test_lib:init_tls_version(GroupName, Config); false -> {skip, "Missing crypto support"} end; @@ -100,10 +98,27 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_huge; + TestCase == server_echos_active_once_huge; + TestCase == server_echos_active_huge; + TestCase == client_echos_passive_huge; + TestCase == client_echos_active_once_huge; + TestCase == client_echos_active_huge -> + ct:timetrap({seconds, 90}), + Config; + +init_per_testcase(TestCase, Config) when TestCase == server_echos_passive_big; + TestCase == server_echos_active_once_big; + TestCase == server_echos_active_big; + TestCase == client_echos_passive_big; + TestCase == client_echos_active_once_big; + TestCase == client_echos_active_big -> + ct:timetrap({seconds, 60}), + Config; + +init_per_testcase(_TestCase, Config) -> + ct:timetrap({seconds, 15}), + Config. end_per_testcase(_TestCase, Config) -> Config. @@ -116,8 +131,8 @@ server_echos_passive_small() -> "sends them back, and closes."}]. server_echos_passive_small(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -132,8 +147,8 @@ server_echos_active_once_small() -> " them, sends them back, and closes."}]. server_echos_active_once_small(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -148,8 +163,8 @@ server_echos_active_small() -> "sends them back, and closes."}]. server_echos_active_small(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -163,8 +178,8 @@ client_echos_passive_small() -> "sends them back, and closes."}]. client_echos_passive_small(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -178,8 +193,8 @@ client_echos_active_once_small() -> "them, sends them back, and closes."]. client_echos_active_once_small(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -193,8 +208,8 @@ client_echos_active_small() -> "sends them back, and closes."}]. client_echos_active_small(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -209,8 +224,8 @@ server_echos_passive_big() -> "sends them back, and closes."}]. server_echos_passive_big(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -225,8 +240,8 @@ server_echos_active_once_big() -> "them, sends them back, and closes."}]. server_echos_active_once_big(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -241,8 +256,8 @@ server_echos_active_big() -> " them, sends them back, and closes."}]. server_echos_active_big(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -256,8 +271,8 @@ client_echos_passive_big() -> "sends them back, and closes."}]. client_echos_passive_big(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -271,8 +286,8 @@ client_echos_active_once_big() -> " them, sends them back, and closes."}]. client_echos_active_once_big(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -286,8 +301,8 @@ client_echos_active_big() -> "sends them back, and closes."}]. client_echos_active_big(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -301,8 +316,8 @@ server_echos_passive_huge() -> " them, sends them back, and closes."}]. server_echos_passive_huge(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -316,8 +331,8 @@ server_echos_active_once_huge() -> "them, sends them back, and closes."}]. server_echos_active_once_huge(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -331,8 +346,8 @@ server_echos_active_huge() -> "sends them back, and closes."}]. server_echos_active_huge(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -346,8 +361,8 @@ client_echos_passive_huge() -> "them, sends them back, and closes."}]. client_echos_passive_huge(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -360,8 +375,8 @@ client_echos_active_once_huge() -> "them, sends them back, and closes."}]. client_echos_active_once_huge(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", @@ -374,8 +389,8 @@ client_echos_active_huge() -> "sends them back, and closes."}]. client_echos_active_huge(Config) when is_list(Config) -> - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Str = "1234567890", diff --git a/lib/ssl/test/ssl_pem_cache_SUITE.erl b/lib/ssl/test/ssl_pem_cache_SUITE.erl new file mode 100644 index 0000000000..13b0ce8ed9 --- /dev/null +++ b/lib/ssl/test/ssl_pem_cache_SUITE.erl @@ -0,0 +1,126 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +%% + +-module(ssl_pem_cache_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("kernel/include/file.hrl"). + +-define(CLEANUP_INTERVAL, 5000). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- +all() -> + [pem_cleanup]. + +groups() -> + []. + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl:start(), + %% make rsa certs using oppenssl + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), + Config1 = ssl_test_lib:make_dsa_cert(Config0), + ssl_test_lib:cert_options(Config1) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_Config) -> + application:stop(crypto). + +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(pem_cleanup = Case, Config) -> + end_per_testcase(Case, Config) , + application:load(ssl), + application:set_env(ssl, ssl_pem_cache_clean, ?CLEANUP_INTERVAL), + ssl:start(), + ct:timetrap({minutes, 1}), + Config. + +end_per_testcase(_TestCase, Config) -> + ssl:stop(), + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +pem_cleanup() -> + [{doc, "Test pem cache invalidate mechanism"}]. +pem_cleanup(Config)when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = proplists:get_value(client_opts, Config), + ServerOpts = proplists:get_value(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, ClientOpts}]), + + Size = ssl_pkix_db:db_size(get_pem_cache()), + Certfile = proplists:get_value(certfile, ServerOpts), + {ok, FileInfo} = file:read_file_info(Certfile), + Time = later(), + ok = file:write_file_info(Certfile, FileInfo#file_info{mtime = Time}), + ct:sleep(2 * ?CLEANUP_INTERVAL), + Size1 = ssl_pkix_db:db_size(get_pem_cache()), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + false = Size == Size1. + +get_pem_cache() -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + case element(6, State) of + [_CertDb, _FileRefDb, PemCache| _] -> + PemCache; + _ -> + undefined + end. + +later()-> + DateTime = calendar:now_to_local_time(os:timestamp()), + Gregorian = calendar:datetime_to_gregorian_seconds(DateTime), + calendar:gregorian_seconds_to_datetime(Gregorian + (2 * ?CLEANUP_INTERVAL)). + diff --git a/lib/ssl/test/ssl_session_cache_SUITE.erl b/lib/ssl/test/ssl_session_cache_SUITE.erl index c31f6c2d7d..b352844ba0 100644 --- a/lib/ssl/test/ssl_session_cache_SUITE.erl +++ b/lib/ssl/test/ssl_session_cache_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2010-2013. All Rights Reserved. +%% Copyright Ericsson AB 2010-2015. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/.2 +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -30,6 +31,7 @@ -define(SLEEP, 500). -define(TIMEOUT, 60000). -define(LONG_TIMEOUT, 600000). +-define(MAX_TABLE_SIZE, 5). -behaviour(ssl_session_cache_api). @@ -41,31 +43,27 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [session_cleanup, session_cache_process_list, - session_cache_process_mnesia]. + session_cache_process_mnesia, + client_unique_session, + max_table_size + ]. groups() -> []. init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), catch crypto:stop(), try crypto:start() of ok -> ssl:start(), - %% make rsa certs using oppenssl - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), - - Config1 = ssl_test_lib:make_dsa_cert(Config0), - Config = ssl_test_lib:cert_options(Config1), - [{watchdog, Dog} | Config] + %% make rsa certs using + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), + Config = ssl_test_lib:make_dsa_cert(Config0), + ssl_test_lib:cert_options(Config) catch _:_ -> {skip, "Crypto did not start"} end. @@ -87,30 +85,41 @@ init_per_testcase(session_cache_process_mnesia, Config) -> mnesia:start(), init_customized_session_cache(mnesia, Config); -init_per_testcase(session_cleanup, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), +init_per_testcase(session_cleanup, Config) -> ssl:stop(), application:load(ssl), application:set_env(ssl, session_lifetime, 5), application:set_env(ssl, session_delay_cleanup_time, ?DELAY), ssl:start(), - [{watchdog, Dog} | Config]; + ct:timetrap({seconds, 20}), + Config; -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(client_unique_session, Config) -> + ct:timetrap({seconds, 40}), + Config; -init_customized_session_cache(Type, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), +init_per_testcase(max_table_size, Config) -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, session_cache_server_max, ?MAX_TABLE_SIZE), + application:set_env(ssl, session_cache_client_max, ?MAX_TABLE_SIZE), + application:set_env(ssl, session_delay_cleanup_time, ?DELAY), + ssl:start(), + ct:timetrap({seconds, 40}), + Config. + +init_customized_session_cache(Type, Config) -> ssl:stop(), application:load(ssl), application:set_env(ssl, session_cb, ?MODULE), - application:set_env(ssl, session_cb_init_args, [Type]), + application:set_env(ssl, session_cb_init_args, [{type, Type}]), ssl:start(), - [{watchdog, Dog} | Config]. + catch (end_per_testcase(list_to_atom("session_cache_process" ++ atom_to_list(Type)), + Config)), + ets:new(ssl_test, [named_table, public, set]), + ets:insert(ssl_test, {type, Type}), + ct:timetrap({seconds, 20}), + Config. end_per_testcase(session_cache_process_list, Config) -> application:unset_env(ssl, session_cb), @@ -126,19 +135,58 @@ end_per_testcase(session_cleanup, Config) -> application:unset_env(ssl, session_delay_cleanup_time), application:unset_env(ssl, session_lifetime), end_per_testcase(default_action, Config); -end_per_testcase(_TestCase, Config) -> +end_per_testcase(max_table_size, Config) -> + application:unset_env(ssl, session_cach_server_max), + application:unset_env(ssl, session_cach_client_max), + end_per_testcase(default_action, Config); +end_per_testcase(Case, Config) when Case == session_cache_process_list; + Case == session_cache_process_mnesia -> + ets:delete(ssl_test), + Config; +end_per_testcase(_, Config) -> Config. %%-------------------------------------------------------------------- %% Test Cases -------------------------------------------------------- %%-------------------------------------------------------------------- +client_unique_session() -> + [{doc, "Test session table does not grow when client " + "sets up many connections"}]. +client_unique_session(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = proplists:get_value(client_opts, Config), + ServerOpts = proplists:get_value(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + LastClient = clients_start(Server, + ClientNode, Hostname, Port, ClientOpts, client_unique_session, 20), + receive + {LastClient, {ok, _}} -> + ok + end, + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + + 1 = ssl_session_cache:size(ClientCache), + + ssl_test_lib:close(Server, 500), + ssl_test_lib:close(LastClient). + session_cleanup() -> [{doc, "Test that sessions are cleand up eventually, so that the session table " "does not grow and grow ..."}]. -session_cleanup(Config)when is_list(Config) -> +session_cleanup(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = @@ -149,9 +197,9 @@ session_cleanup(Config)when is_list(Config) -> Port = ssl_test_lib:inet_port(Server), Client = ssl_test_lib:start_client([{node, ClientNode}, - {port, Port}, {host, Hostname}, + {port, Port}, {host, Hostname}, {mfa, {ssl_test_lib, no_result, []}}, - {from, self()}, {options, ClientOpts}]), + {from, self()}, {options, ClientOpts}]), SessionInfo = receive {Server, Info} -> @@ -164,12 +212,13 @@ session_cleanup(Config)when is_list(Config) -> {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), [_, _,_, _, Prop] = StatusInfo, State = ssl_test_lib:state(Prop), - Cache = element(2, State), - SessionTimer = element(6, State), + ClientCache = element(2, State), + ServerCache = element(3, State), + SessionTimer = element(7, State), Id = proplists:get_value(session_id, SessionInfo), - CSession = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}), - SSession = ssl_session_cache:lookup(Cache, {Port, Id}), + CSession = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}), + SSession = ssl_session_cache:lookup(ServerCache, {Port, Id}), true = CSession =/= undefined, true = SSession =/= undefined, @@ -185,42 +234,14 @@ session_cleanup(Config)when is_list(Config) -> ct:sleep(?SLEEP), %% Make sure clean has had time to run - undefined = ssl_session_cache:lookup(Cache, {{Hostname, Port}, Id}), - undefined = ssl_session_cache:lookup(Cache, {Port, Id}), + undefined = ssl_session_cache:lookup(ClientCache, {{Hostname, Port}, Id}), + undefined = ssl_session_cache:lookup(ServerCache, {Port, Id}), process_flag(trap_exit, false), ssl_test_lib:close(Server), ssl_test_lib:close(Client). -check_timer(Timer) -> - case erlang:read_timer(Timer) of - false -> - {status, _, _, _} = sys:get_status(whereis(ssl_manager)), - timer:sleep(?SLEEP), - {status, _, _, _} = sys:get_status(whereis(ssl_manager)), - ok; - Int -> - ct:sleep(Int), - check_timer(Timer) - end. -get_delay_timers() -> - {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), - [_, _,_, _, Prop] = StatusInfo, - State = ssl_test_lib:state(Prop), - case element(7, State) of - {undefined, undefined} -> - ct:sleep(?SLEEP), - get_delay_timers(); - {undefined, _} -> - ct:sleep(?SLEEP), - get_delay_timers(); - {_, undefined} -> - ct:sleep(?SLEEP), - get_delay_timers(); - DelayTimers -> - DelayTimers - end. %%-------------------------------------------------------------------- session_cache_process_list() -> [{doc,"Test reuse of sessions (short handshake)"}]. @@ -233,19 +254,55 @@ session_cache_process_mnesia(Config) when is_list(Config) -> session_cache_process(mnesia,Config). %%-------------------------------------------------------------------- + +max_table_size() -> + [{doc,"Test max limit on session table"}]. +max_table_size(Config) when is_list(Config) -> + process_flag(trap_exit, true), + ClientOpts = proplists:get_value(client_verification_opts, Config), + ServerOpts = proplists:get_value(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, no_result, []}}, + {tcp_options, [{active, false}]}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + LastClient = clients_start(Server, + ClientNode, Hostname, Port, ClientOpts, max_table_size, 20), + receive + {LastClient, {ok, _}} -> + ok + end, + ct:sleep(1000), + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + ClientCache = element(2, State), + ServerCache = element(3, State), + N = ssl_session_cache:size(ServerCache), + M = ssl_session_cache:size(ClientCache), + ct:pal("~p",[{N, M}]), + ssl_test_lib:close(Server, 500), + ssl_test_lib:close(LastClient), + true = N =< ?MAX_TABLE_SIZE, + true = M =< ?MAX_TABLE_SIZE. + +%%-------------------------------------------------------------------- %%% Session cache API callbacks %%-------------------------------------------------------------------- -init([Type]) -> - ets:new(ssl_test, [named_table, public, set]), - ets:insert(ssl_test, {type, Type}), - case Type of +init(Opts) -> + case proplists:get_value(type, Opts) of list -> spawn(fun() -> session_loop([]) end); mnesia -> mnesia:start(), - {atomic,ok} = mnesia:create_table(sess_cache, []), - sess_cache + Name = atom_to_list(proplists:get_value(role, Opts)), + TabName = list_to_atom(Name ++ "sess_cache"), + {atomic,ok} = mnesia:create_table(TabName, []), + TabName end. session_cb() -> @@ -258,7 +315,7 @@ terminate(Cache) -> Cache ! terminate; mnesia -> catch {atomic,ok} = - mnesia:delete_table(sess_cache) + mnesia:delete_table(Cache) end. lookup(Cache, Key) -> @@ -268,10 +325,10 @@ lookup(Cache, Key) -> receive {Cache, Res} -> Res end; mnesia -> case mnesia:transaction(fun() -> - mnesia:read(sess_cache, + mnesia:read(Cache, Key, read) end) of - {atomic, [{sess_cache, Key, Value}]} -> + {atomic, [{Cache, Key, Value}]} -> Value; _ -> undefined @@ -285,8 +342,8 @@ update(Cache, Key, Value) -> mnesia -> {atomic, ok} = mnesia:transaction(fun() -> - mnesia:write(sess_cache, - {sess_cache, Key, Value}, write) + mnesia:write(Cache, + {Cache, Key, Value}, write) end) end. @@ -297,7 +354,7 @@ delete(Cache, Key) -> mnesia -> {atomic, ok} = mnesia:transaction(fun() -> - mnesia:delete(sess_cache, Key) + mnesia:delete(Cache, Key) end) end. @@ -308,7 +365,7 @@ foldl(Fun, Acc, Cache) -> receive {Cache, Res} -> Res end; mnesia -> Foldl = fun() -> - mnesia:foldl(Fun, Acc, sess_cache) + mnesia:foldl(Fun, Acc, Cache) end, {atomic, Res} = mnesia:transaction(Foldl), Res @@ -325,8 +382,8 @@ select_session(Cache, PartialKey) -> mnesia -> Sel = fun() -> mnesia:select(Cache, - [{{sess_cache,{PartialKey,'$1'}, '$2'}, - [],['$$']}]) + [{{Cache,{PartialKey,'_'}, '$1'}, + [],['$1']}]) end, {atomic, Res} = mnesia:transaction(Sel), Res @@ -354,8 +411,8 @@ session_loop(Sess) -> Pid ! {self(), Res}, session_loop(Sess); {Pid,select_session,PKey} -> - Sel = fun({{PKey0, Id},Session}, Acc) when PKey == PKey0 -> - [[Id, Session]|Acc]; + Sel = fun({{PKey0, _Id},Session}, Acc) when PKey == PKey0 -> + [Session | Acc]; (_,Acc) -> Acc end, @@ -370,3 +427,75 @@ session_loop(Sess) -> session_cache_process(_Type,Config) when is_list(Config) -> ssl_basic_SUITE:reuse_session(Config). + + +clients_start(_Server, ClientNode, Hostname, Port, ClientOpts, Test, 0) -> + %% Make sure session is registered + ct:sleep(?SLEEP * 2), + ssl_test_lib:start_client([{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {?MODULE, connection_info_result, []}}, + {from, self()}, {options, test_copts(Test, 0, ClientOpts)}]); +clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N) -> + spawn_link(ssl_test_lib, start_client, + [[{node, ClientNode}, + {port, Port}, {host, Hostname}, + {mfa, {ssl_test_lib, no_result, []}}, + {from, self()}, {options, test_copts(Test, N, ClientOpts)}]]), + Server ! listen, + wait_for_server(), + clients_start(Server, ClientNode, Hostname, Port, ClientOpts, Test, N-1). + +connection_info_result(Socket) -> + ssl:connection_information(Socket, [protocol, cipher_suite]). + +check_timer(Timer) -> + case erlang:read_timer(Timer) of + false -> + {status, _, _, _} = sys:get_status(whereis(ssl_manager)), + timer:sleep(?SLEEP), + {status, _, _, _} = sys:get_status(whereis(ssl_manager)), + ok; + Int -> + ct:sleep(Int), + check_timer(Timer) + end. + +get_delay_timers() -> + {status, _, _, StatusInfo} = sys:get_status(whereis(ssl_manager)), + [_, _,_, _, Prop] = StatusInfo, + State = ssl_test_lib:state(Prop), + case element(8, State) of + {undefined, undefined} -> + ct:sleep(?SLEEP), + get_delay_timers(); + {undefined, _} -> + ct:sleep(?SLEEP), + get_delay_timers(); + {_, undefined} -> + ct:sleep(?SLEEP), + get_delay_timers(); + DelayTimers -> + DelayTimers + end. + +wait_for_server() -> + ct:sleep(100). + + +test_copts(_, 0, ClientOpts) -> + ClientOpts; +test_copts(max_table_size, N, ClientOpts) -> + Version = tls_record:highest_protocol_version([]), + CipherSuites = %%lists:map(fun(X) -> ssl_cipher:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), +[ Y|| Y = {Alg,_, _, _} <- lists:map(fun(X) -> ssl_cipher:suite_definition(X) end, ssl_cipher:filter_suites(ssl_cipher:suites(Version))), Alg =/= ecdhe_ecdsa, Alg =/= ecdh_ecdsa, Alg =/= ecdh_rsa, Alg =/= ecdhe_rsa, Alg =/= dhe_dss, Alg =/= dss], + case length(CipherSuites) of + M when M >= N -> + Cipher = lists:nth(N, CipherSuites), + ct:pal("~p",[Cipher]), + [{ciphers, [Cipher]} | ClientOpts]; + _ -> + ClientOpts + end; +test_copts(_, _, ClientOpts) -> + ClientOpts. diff --git a/lib/ssl/test/ssl_sni_SUITE.erl b/lib/ssl/test/ssl_sni_SUITE.erl new file mode 100644 index 0000000000..34ef2e6af9 --- /dev/null +++ b/lib/ssl/test/ssl_sni_SUITE.erl @@ -0,0 +1,190 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2015-2016. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +%% + +-module(ssl_sni_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("public_key/include/public_key.hrl"). + +%%-------------------------------------------------------------------- +%% Common Test interface functions ----------------------------------- +%%-------------------------------------------------------------------- + +all() -> [no_sni_header, + sni_match, + sni_no_match, + no_sni_header_fun, + sni_match_fun, + sni_no_match_fun]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + ssl:start(), + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), + ssl_test_lib:cert_options(Config0) + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(_) -> + ssl:stop(), + application:stop(crypto). + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:log("Ciphers: ~p~n ", [ ssl:cipher_suites()]), + ct:timetrap({seconds, 5}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% Test Cases -------------------------------------------------------- +%%-------------------------------------------------------------------- +no_sni_header(Config) -> + run_handshake(Config, undefined, undefined, "server"). + +no_sni_header_fun(Config) -> + run_sni_fun_handshake(Config, undefined, undefined, "server"). + +sni_match(Config) -> + run_handshake(Config, "a.server", "a.server", "a.server"). + +sni_match_fun(Config) -> + run_sni_fun_handshake(Config, "a.server", "a.server", "a.server"). + +sni_no_match(Config) -> + run_handshake(Config, "c.server", undefined, "server"). + +sni_no_match_fun(Config) -> + run_sni_fun_handshake(Config, "c.server", undefined, "server"). + + +%%-------------------------------------------------------------------- +%% Internal Functions ------------------------------------------------ +%%-------------------------------------------------------------------- +ssl_recv(SSLSocket, Expect) -> + ssl_recv(SSLSocket, "", Expect). + +ssl_recv(SSLSocket, CurrentData, ExpectedData) -> + receive + {ssl, SSLSocket, Data} -> + NeweData = CurrentData ++ Data, + case NeweData of + ExpectedData -> + ok; + _ -> + ssl_recv(SSLSocket, NeweData, ExpectedData) + end; + Other -> + ct:fail({unexpected_message, Other}) + after 4000 -> + ct:fail({timeout, CurrentData, ExpectedData}) + end. + +send_and_hostname(SSLSocket) -> + ssl:send(SSLSocket, "OK"), + case ssl:connection_information(SSLSocket, [sni_hostname]) of + {ok, [{sni_hostname, Hostname}]} -> + Hostname; + {ok, []} -> + undefined + end. + +rdnPart([[#'AttributeTypeAndValue'{type=Type, value=Value} | _] | _], Type) -> + Value; +rdnPart([_ | Tail], Type) -> + rdnPart(Tail, Type); +rdnPart([], _) -> + unknown. + +rdn_to_string({utf8String, Binary}) -> + erlang:binary_to_list(Binary); +rdn_to_string({printableString, String}) -> + String. + +recv_and_certificate(SSLSocket) -> + ssl_recv(SSLSocket, "OK"), + {ok, PeerCert} = ssl:peercert(SSLSocket), + #'OTPCertificate'{tbsCertificate = #'OTPTBSCertificate'{subject = {rdnSequence, Subject}}} + = public_key:pkix_decode_cert(PeerCert, otp), + ct:log("Subject of certificate received from server: ~p", [Subject]), + rdn_to_string(rdnPart(Subject, ?'id-at-commonName')). + +run_sni_fun_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, " + "ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), + SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, + ServerOptions = proplists:get_value(server_opts, Config) ++ [{sni_fun, SNIFun}], + ClientOptions = + case SNIHostname of + undefined -> + proplists:get_value(client_opts, Config); + _ -> + [{server_name_indication, SNIHostname}] ++ proplists:get_value(client_opts, Config) + end, + ct:log("Options: ~p", [[ServerOptions, ClientOptions]]), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {?MODULE, recv_and_certificate, []}}, + {options, ClientOptions}]), + ssl_test_lib:check_result(Server, ExpectedSNIHostname, Client, ExpectedCN), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +run_handshake(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, " + "ExpectedSNIHostname: ~p, ExpectedCN: ~p", + [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_opts, Config), + ClientOptions = + case SNIHostname of + undefined -> + proplists:get_value(client_opts, Config); + _ -> + [{server_name_indication, SNIHostname}] ++ proplists:get_value(client_opts, Config) + end, + ct:log("Options: ~p", [[ServerOptions, ClientOptions]]), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, {from, self()}, + {mfa, {?MODULE, recv_and_certificate, []}}, + {options, ClientOptions}]), + ssl_test_lib:check_result(Server, ExpectedSNIHostname, Client, ExpectedCN), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 34c52b10b3..27c670cdc2 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -27,6 +28,7 @@ -compile(export_all). -record(sslsocket, { fd = nil, pid = nil}). +-define(SLEEP, 1000). %% For now always run locally run_where(_) -> @@ -59,14 +61,23 @@ run_server(Opts) -> Options = proplists:get_value(options, Opts), Pid = proplists:get_value(from, Opts), Transport = proplists:get_value(transport, Opts, ssl), - ct:log("ssl:listen(~p, ~p)~n", [Port, Options]), + ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]), {ok, ListenSocket} = rpc:call(Node, Transport, listen, [Port, Options]), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), run_server(ListenSocket, Opts). run_server(ListenSocket, Opts) -> - do_run_server(ListenSocket, connect(ListenSocket, Opts), Opts). + Accepters = proplists:get_value(accepters, Opts, 1), + run_server(ListenSocket, Opts, Accepters). + +run_server(ListenSocket, Opts, 1) -> + do_run_server(ListenSocket, connect(ListenSocket, Opts), Opts); +run_server(ListenSocket, Opts, N) -> + Pid = proplists:get_value(from, Opts), + Server = spawn(?MODULE, run_server, [ListenSocket, Opts, 1]), + Pid ! {accepter, N, Server}, + run_server(ListenSocket, Opts, N-1). do_run_server(_, {error, timeout} = Result, Opts) -> Pid = proplists:get_value(from, Opts), @@ -77,13 +88,13 @@ do_run_server(ListenSocket, AcceptSocket, Opts) -> Pid = proplists:get_value(from, Opts), Transport = proplists:get_value(transport, Opts, ssl), {Module, Function, Args} = proplists:get_value(mfa, Opts), - ct:log("Server: apply(~p,~p,~p)~n", - [Module, Function, [AcceptSocket | Args]]), + ct:log("~p:~p~nServer: apply(~p,~p,~p)~n", + [?MODULE,?LINE, Module, Function, [AcceptSocket | Args]]), case rpc:call(Node, Module, Function, [AcceptSocket | Args]) of no_result_msg -> ok; Msg -> - ct:log("Server Msg: ~p ~n", [Msg]), + ct:log("~p:~p~nServer Msg: ~p ~n", [?MODULE,?LINE, Msg]), Pid ! {self(), Msg} end, receive @@ -92,10 +103,10 @@ do_run_server(ListenSocket, AcceptSocket, Opts) -> {listen, MFA} -> run_server(ListenSocket, [MFA | proplists:delete(mfa, Opts)]); close -> - ct:log("Server closing ~p ~n", [self()]), + ct:log("~p:~p~nServer closing ~p ~n", [?MODULE,?LINE, self()]), Result = rpc:call(Node, Transport, close, [AcceptSocket], 500), Result1 = rpc:call(Node, Transport, close, [ListenSocket], 500), - ct:log("Result ~p : ~p ~n", [Result, Result1]); + ct:log("~p:~p~nResult ~p : ~p ~n", [?MODULE,?LINE, Result, Result1]); {ssl_closed, _} -> ok end. @@ -105,7 +116,8 @@ connect(#sslsocket{} = ListenSocket, Opts) -> Node = proplists:get_value(node, Opts), ReconnectTimes = proplists:get_value(reconnect_times, Opts, 0), Timeout = proplists:get_value(timeout, Opts, infinity), - AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout), + SslOpts = proplists:get_value(ssl_extra_opts, Opts, []), + AcceptSocket = connect(ListenSocket, Node, 1 + ReconnectTimes, dummy, Timeout, SslOpts), case ReconnectTimes of 0 -> AcceptSocket; @@ -115,27 +127,35 @@ connect(#sslsocket{} = ListenSocket, Opts) -> end; connect(ListenSocket, Opts) -> Node = proplists:get_value(node, Opts), - ct:log("gen_tcp:accept(~p)~n", [ListenSocket]), + ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]), AcceptSocket. -connect(_, _, 0, AcceptSocket, _) -> +connect(_, _, 0, AcceptSocket, _, _) -> AcceptSocket; -connect(ListenSocket, Node, N, _, Timeout) -> + +connect(ListenSocket, Node, N, _, Timeout, []) -> ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, [ListenSocket]), - ct:log("ssl:ssl_accept(~p, ~p)~n", [AcceptSocket, Timeout]), + ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", [?MODULE,?LINE, AcceptSocket, Timeout]), case rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Timeout]) of ok -> - connect(ListenSocket, Node, N-1, AcceptSocket, Timeout); + connect(ListenSocket, Node, N-1, AcceptSocket, Timeout, []); Result -> + ct:log("~p:~p~nssl:ssl_accept@~p ret ~p",[?MODULE,?LINE, Node,Result]), Result - end. + end; +connect(ListenSocket, Node, _, _, Timeout, Opts) -> + ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), + {ok, AcceptSocket} = rpc:call(Node, ssl, transport_accept, + [ListenSocket]), + ct:log("ssl:ssl_accept(~p,~p, ~p)~n", [AcceptSocket, Opts, Timeout]), + rpc:call(Node, ssl, ssl_accept, [AcceptSocket, Opts, Timeout]), + AcceptSocket. - remove_close_msg(0) -> ok; remove_close_msg(ReconnectTimes) -> @@ -145,15 +165,21 @@ remove_close_msg(ReconnectTimes) -> end. start_client(Args) -> - Result = spawn_link(?MODULE, run_client, [lists:delete(return_socket, Args)]), + Result = spawn_link(?MODULE, run_client_init, [lists:delete(return_socket, Args)]), receive - { connected, Socket } -> - case lists:member(return_socket, Args) of - true -> { Result, Socket }; - false -> Result - end + {connected, Socket} -> + case lists:member(return_socket, Args) of + true -> {Result, Socket}; + false -> Result + end; + {connect_failed, Reason} -> + {connect_failed, Reason} end. +run_client_init(Opts) -> + put(retries, 0), + run_client(Opts). + run_client(Opts) -> Node = proplists:get_value(node, Opts), Host = proplists:get_value(host, Opts), @@ -161,72 +187,99 @@ run_client(Opts) -> Pid = proplists:get_value(from, Opts), Transport = proplists:get_value(transport, Opts, ssl), Options = proplists:get_value(options, Opts), - ct:log("ssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]), + ct:log("~p:~p~n~p:connect(~p, ~p)@~p~n", [?MODULE,?LINE, Transport, Host, Port, Node]), + ct:log("SSLOpts: ~p", [Options]), case rpc:call(Node, Transport, connect, [Host, Port, Options]) of {ok, Socket} -> - Pid ! { connected, Socket }, - ct:log("Client: connected~n", []), + Pid ! {connected, Socket}, + ct:log("~p:~p~nClient: connected~n", [?MODULE,?LINE]), %% In special cases we want to know the client port, it will %% be indicated by sending {port, 0} in options list! send_selected_port(Pid, proplists:get_value(port, Options), Socket), {Module, Function, Args} = proplists:get_value(mfa, Opts), - ct:log("Client: apply(~p,~p,~p)~n", - [Module, Function, [Socket | Args]]), + ct:log("~p:~p~nClient: apply(~p,~p,~p)~n", + [?MODULE,?LINE, Module, Function, [Socket | Args]]), case rpc:call(Node, Module, Function, [Socket | Args]) of no_result_msg -> ok; Msg -> - ct:log("Client Msg: ~p ~n", [Msg]), + ct:log("~p:~p~nClient Msg: ~p ~n", [?MODULE,?LINE, Msg]), Pid ! {self(), Msg} end, receive close -> - ct:log("Client closing~n", []), + ct:log("~p:~p~nClient closing~n", [?MODULE,?LINE]), rpc:call(Node, Transport, close, [Socket]); {ssl_closed, Socket} -> ok; {gen_tcp, closed} -> ok end; + {error, econnrefused = Reason} -> + case get(retries) of + N when N < 5 -> + ct:log("~p:~p~neconnrefused retries=~p sleep ~p",[?MODULE,?LINE, N,?SLEEP]), + put(retries, N+1), + ct:sleep(?SLEEP), + run_client(Opts); + _ -> + ct:log("~p:~p~nClient faild several times: connection failed: ~p ~n", [?MODULE,?LINE, Reason]), + Pid ! {self(), {error, Reason}} + end; + {error, econnreset = Reason} -> + case get(retries) of + N when N < 5 -> + ct:log("~p:~p~neconnreset retries=~p sleep ~p",[?MODULE,?LINE, N,?SLEEP]), + put(retries, N+1), + ct:sleep(?SLEEP), + run_client(Opts); + _ -> + ct:log("~p:~p~nClient faild several times: connection failed: ~p ~n", [?MODULE,?LINE, Reason]), + Pid ! {self(), {error, Reason}} + end; {error, Reason} -> - ct:log("Client: connection failed: ~p ~n", [Reason]), - Pid ! {self(), {error, Reason}} + ct:log("~p:~p~nClient: connection failed: ~p ~n", [?MODULE,?LINE, Reason]), + Pid ! {connect_failed, Reason}; + {badrpc,BadRPC} -> + ct:log("~p:~p~nBad rpc: ~p",[?MODULE,?LINE, BadRPC]), + Pid ! {connect_failed, {badrpc,BadRPC}} end. close(Pid) -> - ct:log("Close ~p ~n", [Pid]), + ct:log("~p:~p~nClose ~p ~n", [?MODULE,?LINE, Pid]), + Monitor = erlang:monitor(process, Pid), + Pid ! close, + receive + {'DOWN', Monitor, process, Pid, Reason} -> + erlang:demonitor(Monitor), + ct:log("~p:~p~nPid: ~p down due to:~p ~n", [?MODULE,?LINE, Pid, Reason]) + + end. + +close(Pid, Timeout) -> + ct:log("~p:~p~n Close ~p ~n", [?MODULE,?LINE, Pid]), Monitor = erlang:monitor(process, Pid), Pid ! close, receive {'DOWN', Monitor, process, Pid, Reason} -> erlang:demonitor(Monitor), - ct:log("Pid: ~p down due to:~p ~n", [Pid, Reason]) + ct:log("~p:~p~nPid: ~p down due to:~p ~n", [?MODULE,?LINE, Pid, Reason]) + after + Timeout -> + exit(Pid, kill) end. check_result(Server, ServerMsg, Client, ClientMsg) -> receive - {Server, ServerMsg} -> - receive - {Client, ClientMsg} -> - ok; - Unexpected -> - Reason = {{expected, {Client, ClientMsg}}, - {got, Unexpected}}, - ct:fail(Reason) - end; - {Client, ClientMsg} -> - receive - {Server, ServerMsg} -> - ok; - Unexpected -> - Reason = {{expected, {Server, ClientMsg}}, - {got, Unexpected}}, - ct:fail(Reason) - end; + {Server, ServerMsg} -> + check_result(Client, ClientMsg); + + {Client, ClientMsg} -> + check_result(Server, ServerMsg); + {Port, {data,Debug}} when is_port(Port) -> - io:format("openssl ~s~n",[Debug]), + ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Server, ServerMsg, Client, ClientMsg); - Unexpected -> Reason = {{expected, {Client, ClientMsg}}, {expected, {Server, ServerMsg}}, {got, Unexpected}}, @@ -238,8 +291,11 @@ check_result(Pid, Msg) -> {Pid, Msg} -> ok; {Port, {data,Debug}} when is_port(Port) -> - io:format("openssl ~s~n",[Debug]), + ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), check_result(Pid,Msg); + %% {Port, {exit_status, Status}} when is_port(Port) -> + %% ct:log("~p:~p Exit status: ~p~n",[?MODULE,?LINE, Status]), + %% check_result(Pid, Msg); Unexpected -> Reason = {{expected, {Pid, Msg}}, {got, Unexpected}}, @@ -263,19 +319,28 @@ wait_for_result(Server, ServerMsg, Client, ClientMsg) -> %% Unexpected end; {Port, {data,Debug}} when is_port(Port) -> - io:format("openssl ~s~n",[Debug]), + ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), wait_for_result(Server, ServerMsg, Client, ClientMsg) %% Unexpected -> %% Unexpected end. - +check_ok([]) -> + ok; +check_ok(Pids) -> + receive + {Pid, ok} -> + check_ok(lists:delete(Pid, Pids)); + Other -> + ct:fail({expected, {"pid()", ok}, got, Other}) + end. + wait_for_result(Pid, Msg) -> receive {Pid, Msg} -> ok; {Port, {data,Debug}} when is_port(Port) -> - io:format("openssl ~s~n",[Debug]), + ct:log("~p:~p~nopenssl ~s~n",[?MODULE,?LINE, Debug]), wait_for_result(Pid,Msg) %% Unexpected -> %% Unexpected @@ -284,43 +349,48 @@ wait_for_result(Pid, Msg) -> user_lookup(psk, _Identity, UserState) -> {ok, UserState}; user_lookup(srp, Username, _UserState) -> - Salt = ssl:random_bytes(16), + Salt = ssl_cipher:random_bytes(16), UserPassHash = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, <<"secret">>])]), {ok, {srp_1024, Salt, UserPassHash}}. cert_options(Config) -> - ClientCaCertFile = filename:join([?config(priv_dir, Config), + ClientCaCertFile = filename:join([proplists:get_value(priv_dir, Config), "client", "cacerts.pem"]), - ClientCertFile = filename:join([?config(priv_dir, Config), + ClientCertFile = filename:join([proplists:get_value(priv_dir, Config), "client", "cert.pem"]), - ClientCertFileDigitalSignatureOnly = filename:join([?config(priv_dir, Config), + ClientCertFileDigitalSignatureOnly = filename:join([proplists:get_value(priv_dir, Config), "client", "digital_signature_only_cert.pem"]), - ServerCaCertFile = filename:join([?config(priv_dir, Config), + ServerCaCertFile = filename:join([proplists:get_value(priv_dir, Config), "server", "cacerts.pem"]), - ServerCertFile = filename:join([?config(priv_dir, Config), + ServerCertFile = filename:join([proplists:get_value(priv_dir, Config), "server", "cert.pem"]), - ServerKeyFile = filename:join([?config(priv_dir, Config), + ServerKeyFile = filename:join([proplists:get_value(priv_dir, Config), "server", "key.pem"]), - ClientKeyFile = filename:join([?config(priv_dir, Config), + ClientKeyFile = filename:join([proplists:get_value(priv_dir, Config), "client", "key.pem"]), - ServerKeyCertFile = filename:join([?config(priv_dir, Config), + ServerKeyCertFile = filename:join([proplists:get_value(priv_dir, Config), "server", "keycert.pem"]), - ClientKeyCertFile = filename:join([?config(priv_dir, Config), + ClientKeyCertFile = filename:join([proplists:get_value(priv_dir, Config), "client", "keycert.pem"]), - BadCaCertFile = filename:join([?config(priv_dir, Config), + BadCaCertFile = filename:join([proplists:get_value(priv_dir, Config), "badcacert.pem"]), - BadCertFile = filename:join([?config(priv_dir, Config), + BadCertFile = filename:join([proplists:get_value(priv_dir, Config), "badcert.pem"]), - BadKeyFile = filename:join([?config(priv_dir, Config), + BadKeyFile = filename:join([proplists:get_value(priv_dir, Config), "badkey.pem"]), PskSharedSecret = <<1,2,3,4,5,6,7,8,9,10,11,12,13,14,15>>, - [{client_opts, [{ssl_imp, new},{reuseaddr, true}]}, - {client_verification_opts, [{cacertfile, ClientCaCertFile}, + + SNIServerACertFile = filename:join([proplists:get_value(priv_dir, Config), "a.server", "cert.pem"]), + SNIServerAKeyFile = filename:join([proplists:get_value(priv_dir, Config), "a.server", "key.pem"]), + SNIServerBCertFile = filename:join([proplists:get_value(priv_dir, Config), "b.server", "cert.pem"]), + SNIServerBKeyFile = filename:join([proplists:get_value(priv_dir, Config), "b.server", "key.pem"]), + [{client_opts, []}, + {client_verification_opts, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, {ssl_imp, new}]}, - {client_verification_opts_digital_signature_only, [{cacertfile, ClientCaCertFile}, + {client_verification_opts_digital_signature_only, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFileDigitalSignatureOnly}, {keyfile, ClientKeyFile}, {ssl_imp, new}]}, @@ -356,7 +426,7 @@ cert_options(Config) -> {user_lookup_fun, {fun user_lookup/3, undefined}}, {ciphers, srp_anon_suites()}]}, {server_verification_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ServerCaCertFile}, + {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {client_kc_opts, [{certfile, ClientKeyCertFile}, {ssl_imp, new}]}, {server_kc_opts, [{ssl_imp, new},{reuseaddr, true}, @@ -375,7 +445,17 @@ cert_options(Config) -> {server_bad_cert, [{ssl_imp, new},{cacertfile, ServerCaCertFile}, {certfile, BadCertFile}, {keyfile, ServerKeyFile}]}, {server_bad_key, [{ssl_imp, new},{cacertfile, ServerCaCertFile}, - {certfile, ServerCertFile}, {keyfile, BadKeyFile}]} + {certfile, ServerCertFile}, {keyfile, BadKeyFile}]}, + {sni_server_opts, [{sni_hosts, [ + {"a.server", [ + {certfile, SNIServerACertFile}, + {keyfile, SNIServerAKeyFile} + ]}, + {"b.server", [ + {certfile, SNIServerBCertFile}, + {keyfile, SNIServerBKeyFile} + ]} + ]}]} | Config]. @@ -414,7 +494,7 @@ make_ecdsa_cert(Config) -> {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_ecdsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ClientCaCertFile}, + {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, {verify, verify_peer}]}, {client_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true}, @@ -439,7 +519,7 @@ make_ecdh_rsa_cert(Config) -> {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_ecdh_rsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ClientCaCertFile}, + {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, {verify, verify_peer}]}, {client_ecdh_rsa_opts, [{ssl_imp, new},{reuseaddr, true}, @@ -472,11 +552,11 @@ make_cert_files(RoleStr, Config, Alg1, Alg2, Prefix) -> Alg2Str = atom_to_list(Alg2), CaInfo = {CaCert, _} = erl_make_certs:make_cert([{key, Alg1}]), {Cert, CertKey} = erl_make_certs:make_cert([{key, Alg2}, {issuer, CaInfo}]), - CaCertFile = filename:join([?config(priv_dir, Config), + CaCertFile = filename:join([proplists:get_value(priv_dir, Config), RoleStr, Prefix ++ Alg1Str ++ "_cacerts.pem"]), - CertFile = filename:join([?config(priv_dir, Config), + CertFile = filename:join([proplists:get_value(priv_dir, Config), RoleStr, Prefix ++ Alg2Str ++ "_cert.pem"]), - KeyFile = filename:join([?config(priv_dir, Config), + KeyFile = filename:join([proplists:get_value(priv_dir, Config), RoleStr, Prefix ++ Alg2Str ++ "_key.pem"]), der_to_pem(CaCertFile, [{'Certificate', CaCert, not_encrypted}]), @@ -500,33 +580,33 @@ run_upgrade_server(Opts) -> SslOptions = proplists:get_value(ssl_options, Opts), Pid = proplists:get_value(from, Opts), - ct:log("gen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]), + ct:log("~p:~p~ngen_tcp:listen(~p, ~p)~n", [?MODULE,?LINE, Port, TcpOptions]), {ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), - ct:log("gen_tcp:accept(~p)~n", [ListenSocket]), + ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]), try {ok, SslAcceptSocket} = case TimeOut of infinity -> - ct:log("ssl:ssl_accept(~p, ~p)~n", - [AcceptSocket, SslOptions]), + ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", + [?MODULE,?LINE, AcceptSocket, SslOptions]), rpc:call(Node, ssl, ssl_accept, [AcceptSocket, SslOptions]); _ -> - ct:log("ssl:ssl_accept(~p, ~p, ~p)~n", - [AcceptSocket, SslOptions, TimeOut]), + ct:log("~p:~p~nssl:ssl_accept(~p, ~p, ~p)~n", + [?MODULE,?LINE, AcceptSocket, SslOptions, TimeOut]), rpc:call(Node, ssl, ssl_accept, [AcceptSocket, SslOptions, TimeOut]) end, {Module, Function, Args} = proplists:get_value(mfa, Opts), Msg = rpc:call(Node, Module, Function, [SslAcceptSocket | Args]), - ct:log("Upgrade Server Msg: ~p ~n", [Msg]), + ct:log("~p:~p~nUpgrade Server Msg: ~p ~n", [?MODULE,?LINE, Msg]), Pid ! {self(), Msg}, receive close -> - ct:log("Upgrade Server closing~n", []), + ct:log("~p:~p~nUpgrade Server closing~n", [?MODULE,?LINE]), rpc:call(Node, ssl, close, [SslAcceptSocket]) end catch error:{badmatch, Error} -> @@ -544,24 +624,24 @@ run_upgrade_client(Opts) -> TcpOptions = proplists:get_value(tcp_options, Opts), SslOptions = proplists:get_value(ssl_options, Opts), - ct:log("gen_tcp:connect(~p, ~p, ~p)~n", - [Host, Port, TcpOptions]), + ct:log("~p:~p~ngen_tcp:connect(~p, ~p, ~p)~n", + [?MODULE,?LINE, Host, Port, TcpOptions]), {ok, Socket} = rpc:call(Node, gen_tcp, connect, [Host, Port, TcpOptions]), send_selected_port(Pid, Port, Socket), - ct:log("ssl:connect(~p, ~p)~n", [Socket, SslOptions]), + ct:log("~p:~p~nssl:connect(~p, ~p)~n", [?MODULE,?LINE, Socket, SslOptions]), {ok, SslSocket} = rpc:call(Node, ssl, connect, [Socket, SslOptions]), {Module, Function, Args} = proplists:get_value(mfa, Opts), - ct:log("apply(~p, ~p, ~p)~n", - [Module, Function, [SslSocket | Args]]), + ct:log("~p:~p~napply(~p, ~p, ~p)~n", + [?MODULE,?LINE, Module, Function, [SslSocket | Args]]), Msg = rpc:call(Node, Module, Function, [SslSocket | Args]), - ct:log("Upgrade Client Msg: ~p ~n", [Msg]), + ct:log("~p:~p~nUpgrade Client Msg: ~p ~n", [?MODULE,?LINE, Msg]), Pid ! {self(), Msg}, receive close -> - ct:log("Upgrade Client closing~n", []), + ct:log("~p:~p~nUpgrade Client closing~n", [?MODULE,?LINE]), rpc:call(Node, ssl, close, [SslSocket]) end. @@ -580,21 +660,21 @@ run_upgrade_server_error(Opts) -> SslOptions = proplists:get_value(ssl_options, Opts), Pid = proplists:get_value(from, Opts), - ct:log("gen_tcp:listen(~p, ~p)~n", [Port, TcpOptions]), + ct:log("~p:~p~ngen_tcp:listen(~p, ~p)~n", [?MODULE,?LINE, Port, TcpOptions]), {ok, ListenSocket} = rpc:call(Node, gen_tcp, listen, [Port, TcpOptions]), Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), - ct:log("gen_tcp:accept(~p)~n", [ListenSocket]), + ct:log("~p:~p~ngen_tcp:accept(~p)~n", [?MODULE,?LINE, ListenSocket]), {ok, AcceptSocket} = rpc:call(Node, gen_tcp, accept, [ListenSocket]), Error = case TimeOut of infinity -> - ct:log("ssl:ssl_accept(~p, ~p)~n", - [AcceptSocket, SslOptions]), + ct:log("~p:~p~nssl:ssl_accept(~p, ~p)~n", + [?MODULE,?LINE, AcceptSocket, SslOptions]), rpc:call(Node, ssl, ssl_accept, [AcceptSocket, SslOptions]); _ -> - ct:log("ssl:ssl_accept(~p, ~p, ~p)~n", - [AcceptSocket, SslOptions, TimeOut]), + ct:log("~p:~p~nssl:ssl_accept(~p, ~p, ~p)~n", + [?MODULE,?LINE, AcceptSocket, SslOptions, TimeOut]), rpc:call(Node, ssl, ssl_accept, [AcceptSocket, SslOptions, TimeOut]) end, @@ -613,26 +693,26 @@ run_server_error(Opts) -> Options = proplists:get_value(options, Opts), Pid = proplists:get_value(from, Opts), Transport = proplists:get_value(transport, Opts, ssl), - ct:log("ssl:listen(~p, ~p)~n", [Port, Options]), + ct:log("~p:~p~nssl:listen(~p, ~p)~n", [?MODULE,?LINE, Port, Options]), case rpc:call(Node, Transport, listen, [Port, Options]) of {ok, #sslsocket{} = ListenSocket} -> %% To make sure error_client will %% get {error, closed} and not {error, connection_refused} Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), - ct:log("ssl:transport_accept(~p)~n", [ListenSocket]), + ct:log("~p:~p~nssl:transport_accept(~p)~n", [?MODULE,?LINE, ListenSocket]), case rpc:call(Node, Transport, transport_accept, [ListenSocket]) of {error, _} = Error -> Pid ! {self(), Error}; {ok, AcceptSocket} -> - ct:log("ssl:ssl_accept(~p)~n", [AcceptSocket]), + ct:log("~p:~p~nssl:ssl_accept(~p)~n", [?MODULE,?LINE, AcceptSocket]), Error = rpc:call(Node, ssl, ssl_accept, [AcceptSocket]), Pid ! {self(), Error} end; {ok, ListenSocket} -> Pid ! {listen, up}, send_selected_port(Pid, Port, ListenSocket), - ct:log("~p:accept(~p)~n", [Transport, ListenSocket]), + ct:log("~p:~p~n~p:accept(~p)~n", [?MODULE,?LINE, Transport, ListenSocket]), case rpc:call(Node, Transport, accept, [ListenSocket]) of {error, _} = Error -> Pid ! {self(), Error} @@ -654,10 +734,21 @@ run_client_error(Opts) -> Pid = proplists:get_value(from, Opts), Transport = proplists:get_value(transport, Opts, ssl), Options = proplists:get_value(options, Opts), - ct:log("ssl:connect(~p, ~p, ~p)~n", [Host, Port, Options]), + ct:log("~p:~p~nssl:connect(~p, ~p, ~p)~n", [?MODULE,?LINE, Host, Port, Options]), Error = rpc:call(Node, Transport, connect, [Host, Port, Options]), Pid ! {self(), Error}. +accepters(N) -> + accepters([], N). + +accepters(Acc, 0) -> + Acc; +accepters(Acc, N) -> + receive + {accepter, _, Server} -> + accepters([Server| Acc], N-1) + end. + inet_port(Pid) when is_pid(Pid)-> receive {Pid, {port, Port}} -> @@ -713,7 +804,12 @@ send_selected_port(_,_,_) -> rsa_suites(CounterPart) -> ECC = is_sane_ecc(CounterPart), - lists:filter(fun({rsa, _, _}) -> + FIPS = is_fips(CounterPart), + lists:filter(fun({rsa, des_cbc, sha}) when FIPS == true -> + false; + ({dhe_rsa, des_cbc, sha}) when FIPS == true -> + false; + ({rsa, _, _}) -> true; ({dhe_rsa, _, _}) -> true; @@ -722,7 +818,17 @@ rsa_suites(CounterPart) -> (_) -> false end, - ssl:cipher_suites()). + common_ciphers(CounterPart)). + +common_ciphers(crypto) -> + ssl:cipher_suites(); +common_ciphers(openssl) -> + OpenSslSuites = + string:tokens(string:strip(os:cmd("openssl ciphers"), right, $\n), ":"), + [ssl_cipher:erl_suite_definition(S) + || S <- ssl_cipher:suites(tls_record:highest_protocol_version([])), + lists:member(ssl_cipher:openssl_suite_name(S), OpenSslSuites) + ]. rsa_non_signed_suites() -> lists:filter(fun({rsa, _, _}) -> @@ -764,48 +870,34 @@ openssl_rsa_suites(CounterPart) -> false -> "DSS | ECDHE | ECDH" end, - lists:filter(fun(Str) -> - case re:run(Str, Names,[]) of - nomatch -> - false; - _ -> - true - end - end, Ciphers). + lists:filter(fun(Str) -> string_regex_filter(Str, Names) + end, Ciphers). openssl_dsa_suites() -> Ciphers = ssl:cipher_suites(openssl), - lists:filter(fun(Str) -> - case re:run(Str,"DSS",[]) of - nomatch -> - false; - _ -> - true - end + lists:filter(fun(Str) -> string_regex_filter(Str, "DSS") end, Ciphers). openssl_ecdsa_suites() -> Ciphers = ssl:cipher_suites(openssl), - lists:filter(fun(Str) -> - case re:run(Str,"ECDHE-ECDSA",[]) of - nomatch -> - false; - _ -> - true - end + lists:filter(fun(Str) -> string_regex_filter(Str, "ECDHE-ECDSA") end, Ciphers). openssl_ecdh_rsa_suites() -> Ciphers = ssl:cipher_suites(openssl), - lists:filter(fun(Str) -> - case re:run(Str,"ECDH-RSA",[]) of - nomatch -> - false; - _ -> - true - end + lists:filter(fun(Str) -> string_regex_filter(Str, "ECDH-RSA") end, Ciphers). +string_regex_filter(Str, Search) when is_list(Str) -> + case re:run(Str, Search, []) of + nomatch -> + false; + _ -> + true + end; +string_regex_filter(_Str, _Search) -> + false. + anonymous_suites() -> Suites = [{dh_anon, rc4_128, md5}, @@ -813,6 +905,8 @@ anonymous_suites() -> {dh_anon, '3des_ede_cbc', sha}, {dh_anon, aes_128_cbc, sha}, {dh_anon, aes_256_cbc, sha}, + {dh_anon, aes_128_gcm, null, sha256}, + {dh_anon, aes_256_gcm, null, sha384}, {ecdh_anon,rc4_128,sha}, {ecdh_anon,'3des_ede_cbc',sha}, {ecdh_anon,aes_128_cbc,sha}, @@ -825,25 +919,39 @@ psk_suites() -> {psk, '3des_ede_cbc', sha}, {psk, aes_128_cbc, sha}, {psk, aes_256_cbc, sha}, + {psk, aes_128_cbc, sha256}, + {psk, aes_256_cbc, sha384}, {dhe_psk, rc4_128, sha}, {dhe_psk, '3des_ede_cbc', sha}, {dhe_psk, aes_128_cbc, sha}, {dhe_psk, aes_256_cbc, sha}, + {dhe_psk, aes_128_cbc, sha256}, + {dhe_psk, aes_256_cbc, sha384}, {rsa_psk, rc4_128, sha}, {rsa_psk, '3des_ede_cbc', sha}, {rsa_psk, aes_128_cbc, sha}, - {rsa_psk, aes_256_cbc, sha}], + {rsa_psk, aes_256_cbc, sha}, + {rsa_psk, aes_128_cbc, sha256}, + {rsa_psk, aes_256_cbc, sha384}, + {psk, aes_128_gcm, null, sha256}, + {psk, aes_256_gcm, null, sha384}, + {dhe_psk, aes_128_gcm, null, sha256}, + {dhe_psk, aes_256_gcm, null, sha384}, + {rsa_psk, aes_128_gcm, null, sha256}, + {rsa_psk, aes_256_gcm, null, sha384}], ssl_cipher:filter_suites(Suites). psk_anon_suites() -> - [{psk, rc4_128, sha}, - {psk, '3des_ede_cbc', sha}, - {psk, aes_128_cbc, sha}, - {psk, aes_256_cbc, sha}, - {dhe_psk, rc4_128, sha}, - {dhe_psk, '3des_ede_cbc', sha}, - {dhe_psk, aes_128_cbc, sha}, - {dhe_psk, aes_256_cbc, sha}]. + Suites = + [{psk, rc4_128, sha}, + {psk, '3des_ede_cbc', sha}, + {psk, aes_128_cbc, sha}, + {psk, aes_256_cbc, sha}, + {dhe_psk, rc4_128, sha}, + {dhe_psk, '3des_ede_cbc', sha}, + {dhe_psk, aes_128_cbc, sha}, + {dhe_psk, aes_256_cbc, sha}], + ssl_cipher:filter_suites(Suites). srp_suites() -> Suites = @@ -856,9 +964,11 @@ srp_suites() -> ssl_cipher:filter_suites(Suites). srp_anon_suites() -> - [{srp_anon, '3des_ede_cbc', sha}, - {srp_anon, aes_128_cbc, sha}, - {srp_anon, aes_256_cbc, sha}]. + Suites = + [{srp_anon, '3des_ede_cbc', sha}, + {srp_anon, aes_128_cbc, sha}, + {srp_anon, aes_256_cbc, sha}], + ssl_cipher:filter_suites(Suites). srp_dss_suites() -> Suites = @@ -867,6 +977,14 @@ srp_dss_suites() -> {srp_dss, aes_256_cbc, sha}], ssl_cipher:filter_suites(Suites). +rc4_suites(Version) -> + Suites = ssl_cipher:rc4_suites(Version), + ssl_cipher:filter_suites(Suites). + +des_suites(Version) -> + Suites = ssl_cipher:des_suites(Version), + ssl_cipher:filter_suites(Suites). + pem_to_der(File) -> {ok, PemBin} = file:read_file(File), public_key:pem_decode(PemBin). @@ -876,8 +994,9 @@ der_to_pem(File, Entries) -> file:write_file(File, PemBin). cipher_result(Socket, Result) -> - Result = ssl:connection_info(Socket), - ct:log("Successfull connect: ~p~n", [Result]), + {ok, Info} = ssl:connection_information(Socket), + Result = {ok, {proplists:get_value(protocol, Info), proplists:get_value(cipher_suite, Info)}}, + ct:log("~p:~p~nSuccessfull connect: ~p~n", [?MODULE,?LINE, Result]), %% Importante to send two packets here %% to properly test "cipher state" handling ssl:send(Socket, "Hello\n"), @@ -922,13 +1041,20 @@ receive_rizzo_duong_beast() -> end end. -state([{data,[{"State", State}]} | _]) -> - State; -state([{data,[{"StateData", State}]} | _]) -> + +state([{data,[{"State", {_StateName, StateData}}]} | _]) -> %% gen_statem + StateData; +state([{data,[{"State", State}]} | _]) -> %% gen_server State; +state([{data,[{"StateData", State}]} | _]) -> %% gen_fsm + State; state([_ | Rest]) -> state(Rest). +is_tls_version('dtlsv1.2') -> + true; +is_tls_version('dtlsv1') -> + true; is_tls_version('tlsv1.2') -> true; is_tls_version('tlsv1.1') -> @@ -940,16 +1066,29 @@ is_tls_version('sslv3') -> is_tls_version(_) -> false. -init_tls_version(Version) -> +init_tls_version(Version, Config) + when Version == 'dtlsv1.2'; Version == 'dtlsv1' -> + ssl:stop(), + application:load(ssl), + application:set_env(ssl, dtls_protocol_version, Version), + ssl:start(), + [{protocol, dtls}, {protocol_opts, [{protocol, dtls}]}|Config]; + +init_tls_version(Version, Config) -> ssl:stop(), application:load(ssl), application:set_env(ssl, protocol_version, Version), - ssl:start(). + ssl:start(), + [{protocol, tls}|Config]. -sufficient_crypto_support('tlsv1.2') -> +sufficient_crypto_support(Version) + when Version == 'tlsv1.2'; Version == 'dtlsv1.2' -> CryptoSupport = crypto:supports(), proplists:get_bool(sha256, proplists:get_value(hashs, CryptoSupport)); -sufficient_crypto_support(ciphers_ec) -> +sufficient_crypto_support(Group) when Group == ciphers_ec; %% From ssl_basic_SUITE + Group == erlang_server; %% From ssl_ECC_SUITE + Group == erlang_client; %% From ssl_ECC_SUITE + Group == erlang -> %% From ssl_ECC_SUITE CryptoSupport = crypto:supports(), proplists:get_bool(ecdh, proplists:get_value(public_keys, CryptoSupport)); sufficient_crypto_support(_) -> @@ -993,6 +1132,9 @@ is_sane_ecc(openssl) -> "OpenSSL 1.0.0" ++ _ -> % Known bug in openssl %% manifests as SSL_CHECK_SERVERHELLO_TLSEXT:tls invalid ecpointformat list false; + "OpenSSL 1.0.1l" ++ _ -> + %% Breaks signature verification + false; "OpenSSL 0.9.8" ++ _ -> % Does not support ECC false; "OpenSSL 0.9.7" ++ _ -> % Does not support ECC @@ -1013,6 +1155,25 @@ is_sane_ecc(crypto) -> is_sane_ecc(_) -> true. +is_fips(openssl) -> + VersionStr = os:cmd("openssl version"), + case re:split(VersionStr, "fips") of + [_] -> + false; + _ -> + true + end; +is_fips(crypto) -> + [{_,_, Bin}] = crypto:info_lib(), + case re:split(Bin, <<"fips">>) of + [_] -> + false; + _ -> + true + end; +is_fips(_) -> + false. + cipher_restriction(Config0) -> case is_sane_ecc(openssl) of false -> @@ -1026,3 +1187,161 @@ cipher_restriction(Config0) -> true -> Config0 end. + +check_sane_openssl_version(Version) -> + case supports_ssl_tls_version(Version) of + true -> + case {Version, os:cmd("openssl version")} of + {_, "OpenSSL 1.0.2" ++ _} -> + true; + {_, "OpenSSL 1.0.1" ++ _} -> + true; + {'tlsv1.2', "OpenSSL 1.0" ++ _} -> + false; + {'tlsv1.1', "OpenSSL 1.0" ++ _} -> + false; + {'tlsv1.2', "OpenSSL 0" ++ _} -> + false; + {'tlsv1.1', "OpenSSL 0" ++ _} -> + false; + {_, _} -> + true + end; + false -> + false + end. +enough_openssl_crl_support("OpenSSL 0." ++ _) -> false; +enough_openssl_crl_support(_) -> true. + +wait_for_openssl_server(Port) -> + wait_for_openssl_server(Port, 10). +wait_for_openssl_server(_, 0) -> + exit(failed_to_connect_to_openssl); +wait_for_openssl_server(Port, N) -> + case gen_tcp:connect("localhost", Port, []) of + {ok, S} -> + gen_tcp:close(S); + _ -> + ct:sleep(?SLEEP), + wait_for_openssl_server(Port, N-1) + end. + +version_flag(tlsv1) -> + "-tls1"; +version_flag('tlsv1.1') -> + "-tls1_1"; +version_flag('tlsv1.2') -> + "-tls1_2"; +version_flag(sslv3) -> + "-ssl3"; +version_flag(sslv2) -> + "-ssl2". + +filter_suites(Ciphers0) -> + Version = tls_record:highest_protocol_version([]), + Supported0 = ssl_cipher:suites(Version) + ++ ssl_cipher:anonymous_suites(Version) + ++ ssl_cipher:psk_suites(Version) + ++ ssl_cipher:srp_suites() + ++ ssl_cipher:rc4_suites(Version), + Supported1 = ssl_cipher:filter_suites(Supported0), + Supported2 = [ssl_cipher:erl_suite_definition(S) || S <- Supported1], + [Cipher || Cipher <- Ciphers0, lists:member(Cipher, Supported2)]. + +-define(OPENSSL_QUIT, "Q\n"). +close_port(Port) -> + catch port_command(Port, ?OPENSSL_QUIT), + close_loop(Port, 500, false). + +close_loop(Port, Time, SentClose) -> + receive + {Port, {data,Debug}} when is_port(Port) -> + ct:log("openssl ~s~n",[Debug]), + close_loop(Port, Time, SentClose); + {ssl,_,Msg} -> + ct:log("ssl Msg ~s~n",[Msg]), + close_loop(Port, Time, SentClose); + {Port, closed} -> + ct:log("Port Closed~n",[]), + ok; + {'EXIT', Port, Reason} -> + ct:log("Port Closed ~p~n",[Reason]), + ok; + Msg -> + ct:log("Port Msg ~p~n",[Msg]), + close_loop(Port, Time, SentClose) + after Time -> + case SentClose of + false -> + ct:log("Closing port ~n",[]), + catch erlang:port_close(Port), + close_loop(Port, Time, true); + true -> + ct:log("Timeout~n",[]) + end + end. + +portable_open_port(Exe, Args) -> + AbsPath = os:find_executable(Exe), + ct:pal("open_port({spawn_executable, ~p}, [{args, ~p}, stderr_to_stdout]).", [AbsPath, Args]), + open_port({spawn_executable, AbsPath}, + [{args, Args}, stderr_to_stdout]). + +supports_ssl_tls_version(Version) -> + VersionFlag = version_flag(Version), + Exe = "openssl", + Args = ["s_client", VersionFlag], + Port = ssl_test_lib:portable_open_port(Exe, Args), + do_supports_ssl_tls_version(Port). + +do_supports_ssl_tls_version(Port) -> + receive + {Port, {data, "unknown option" ++ _}} -> + false; + {Port, {data, Data}} -> + case lists:member("error", string:tokens(Data, ":")) of + true -> + false; + false -> + do_supports_ssl_tls_version(Port) + end + after 500 -> + true + end. + +ssl_options(Option, Config) -> + ProtocolOpts = proplists:get_value(protocol_opts, Config, []), + Opts = proplists:get_value(Option, Config, []), + Opts ++ ProtocolOpts. + +protocol_version(Config) -> + protocol_version(Config, atom). + +protocol_version(Config, tuple) -> + case proplists:get_value(protocol, Config) of + dtls -> + dtls_record:protocol_version(dtls_record:highest_protocol_version([])); + _ -> + tls_record:highest_protocol_version(tls_record:supported_protocol_versions()) + end; + +protocol_version(Config, atom) -> + case proplists:get_value(protocol, Config) of + dtls -> + dtls_record:protocol_version(protocol_version(Config, tuple)); + _ -> + tls_record:protocol_version(protocol_version(Config, tuple)) + end. + +protocol_options(Config, Options) -> + Protocol = proplists:get_value(protocol, Config, tls), + {Protocol, Opts} = lists:keyfind(Protocol, 1, Options), + Opts. + +ct_log_supported_protocol_versions(Config) -> + case proplists:get_value(protocol, Config) of + dtls -> + ct:log("DTLS version ~p~n ", [dtls_record:supported_protocol_versions()]); + _ -> + ct:log("TLS/SSL version ~p~n ", [tls_record:supported_protocol_versions()]) + end. diff --git a/lib/ssl/test/ssl_to_openssl_SUITE.erl b/lib/ssl/test/ssl_to_openssl_SUITE.erl index 019ed58b1b..b3109b5de9 100644 --- a/lib/ssl/test/ssl_to_openssl_SUITE.erl +++ b/lib/ssl/test/ssl_to_openssl_SUITE.erl @@ -1,18 +1,19 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2008-2013. All Rights Reserved. +%% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% -%% The contents of this file are subject to the Erlang Public License, -%% Version 1.1, (the "License"); you may not use this file except in -%% compliance with the License. You should have received a copy of the -%% Erlang Public License along with this software. If not, it can be -%% retrieved online at http://www.erlang.org/. +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at %% -%% Software distributed under the License is distributed on an "AS IS" -%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See -%% the License for the specific language governing rights and limitations -%% under the License. +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. %% %% %CopyrightEnd% %% @@ -25,8 +26,6 @@ -include_lib("common_test/include/ct.hrl"). --define(TIMEOUT, 120000). --define(LONG_TIMEOUT, 600000). -define(SLEEP, 1000). -define(OPENSSL_RENEGOTIATE, "R\n"). -define(OPENSSL_QUIT, "Q\n"). @@ -37,8 +36,6 @@ %% Common Test interface functions ----------------------------------- %%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - all() -> [ {group, basic}, @@ -50,9 +47,9 @@ all() -> groups() -> [{basic, [], basic_tests()}, - {'tlsv1.2', [], all_versions_tests() ++ npn_tests()}, - {'tlsv1.1', [], all_versions_tests() ++ npn_tests()}, - {'tlsv1', [], all_versions_tests()++ npn_tests()}, + {'tlsv1.2', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1.1', [], all_versions_tests() ++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, + {'tlsv1', [], all_versions_tests()++ alpn_tests() ++ npn_tests() ++ sni_server_tests()}, {'sslv3', [], all_versions_tests()}]. basic_tests() -> @@ -79,6 +76,18 @@ all_versions_tests() -> expired_session, ssl2_erlang_server_openssl_client]. +alpn_tests() -> + [erlang_client_alpn_openssl_server_alpn, + erlang_server_alpn_openssl_client_alpn, + erlang_client_alpn_openssl_server, + erlang_client_openssl_server_alpn, + erlang_server_alpn_openssl_client, + erlang_server_openssl_client_alpn, + erlang_client_alpn_openssl_server_alpn_renegotiate, + erlang_server_alpn_openssl_client_alpn_renegotiate, + erlang_client_alpn_npn_openssl_server_alpn_npn, + erlang_server_alpn_npn_openssl_client_alpn_npn]. + npn_tests() -> [erlang_client_openssl_server_npn, erlang_server_openssl_client_npn, @@ -89,24 +98,29 @@ npn_tests() -> erlang_client_openssl_server_npn_only_client, erlang_client_openssl_server_npn_only_server]. +sni_server_tests() -> + [erlang_server_openssl_client_sni_match, + erlang_server_openssl_client_sni_match_fun, + erlang_server_openssl_client_sni_no_match, + erlang_server_openssl_client_sni_no_match_fun, + erlang_server_openssl_client_sni_no_header, + erlang_server_openssl_client_sni_no_header_fun]. + init_per_suite(Config0) -> - Dog = ct:timetrap(?LONG_TIMEOUT *2), case os:find_executable("openssl") of false -> {skip, "Openssl not found"}; _ -> + ct:pal("Version: ~p", [os:cmd("openssl version")]), catch crypto:stop(), try crypto:start() of ok -> ssl:start(), - Result = - (catch make_certs:all(?config(data_dir, Config0), - ?config(priv_dir, Config0))), - ct:log("Make certs ~p~n", [Result]), + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config0), + proplists:get_value(priv_dir, Config0)), Config1 = ssl_test_lib:make_dsa_cert(Config0), - Config2 = ssl_test_lib:cert_options(Config1), - Config = [{watchdog, Dog} | Config2], + Config = ssl_test_lib:cert_options(Config1), ssl_test_lib:cipher_restriction(Config) catch _:_ -> {skip, "Crypto did not start"} @@ -117,13 +131,19 @@ end_per_suite(_Config) -> ssl:stop(), application:stop(crypto). +init_per_group(basic, Config) -> + case ssl_test_lib:supports_ssl_tls_version(sslv2) of + true -> + [{v2_hello_compatible, true} | Config]; + false -> + [{v2_hello_compatible, false} | Config] + end; init_per_group(GroupName, Config) -> case ssl_test_lib:is_tls_version(GroupName) of true -> - case check_sane_openssl_version(GroupName) of + case ssl_test_lib:check_sane_openssl_version(GroupName) of true -> - ssl_test_lib:init_tls_version(GroupName), - Config; + ssl_test_lib:init_tls_version(GroupName, Config); false -> {skip, openssl_does_not_support_version} end; @@ -135,41 +155,98 @@ init_per_group(GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. -init_per_testcase(expired_session, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?EXPIRE * 1000 * 5), +init_per_testcase(expired_session, Config) -> + ct:timetrap(?EXPIRE * 1000 * 5), ssl:stop(), application:load(ssl), application:set_env(ssl, session_lifetime, ?EXPIRE), ssl:start(), - [{watchdog, Dog} | Config]; + Config; + +init_per_testcase(TestCase, Config) when TestCase == ciphers_rsa_signed_certs; + TestCase == ciphers_dsa_signed_certs -> + ct:timetrap({seconds, 45}), + special_init(TestCase, Config); -init_per_testcase(TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = ct:timetrap(?TIMEOUT), - special_init(TestCase, [{watchdog, Dog} | Config]). +init_per_testcase(TestCase, Config) -> + ct:timetrap({seconds, 20}), + special_init(TestCase, Config). special_init(TestCase, Config) when TestCase == erlang_client_openssl_server_renegotiate; TestCase == erlang_client_openssl_server_nowrap_seqnum; TestCase == erlang_server_openssl_client_nowrap_seqnum -> - check_sane_openssl_renegotaite(Config); + {ok, Version} = application:get_env(ssl, protocol_version), + check_sane_openssl_renegotaite(Config, Version); special_init(ssl2_erlang_server_openssl_client, Config) -> - check_sane_openssl_sslv2(Config); + case ssl_test_lib:supports_ssl_tls_version(sslv2) of + true -> + Config; + false -> + {skip, "sslv2 not supported by openssl"} + end; + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_openssl_server_alpn; + TestCase == erlang_server_alpn_openssl_client_alpn; + TestCase == erlang_client_alpn_openssl_server; + TestCase == erlang_client_openssl_server_alpn; + TestCase == erlang_server_alpn_openssl_client; + TestCase == erlang_server_openssl_client_alpn -> + check_openssl_alpn_support(Config); + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_openssl_server_alpn_renegotiate; + TestCase == erlang_server_alpn_openssl_client_alpn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + case check_sane_openssl_renegotaite(Config, Version) of + {skip, _} = Skip -> + Skip; + _ -> + check_openssl_alpn_support(Config) + end; + +special_init(TestCase, Config) + when TestCase == erlang_client_alpn_npn_openssl_server_alpn_npn; + TestCase == erlang_server_alpn_npn_openssl_client_alpn_npn -> + case check_openssl_alpn_support(Config) of + {skip, _} = Skip -> + Skip; + _ -> + check_openssl_npn_support(Config) + end; special_init(TestCase, Config) when TestCase == erlang_client_openssl_server_npn; - TestCase == erlang_server_openssl_client_npn; - TestCase == erlang_server_openssl_client_npn_renegotiate; - TestCase == erlang_client_openssl_server_npn_renegotiate; + TestCase == erlang_server_openssl_client_npn; TestCase == erlang_server_openssl_client_npn_only_server; TestCase == erlang_server_openssl_client_npn_only_client; TestCase == erlang_client_openssl_server_npn_only_client; TestCase == erlang_client_openssl_server_npn_only_server -> check_openssl_npn_support(Config); +special_init(TestCase, Config) + when TestCase == erlang_server_openssl_client_npn_renegotiate; + TestCase == erlang_client_openssl_server_npn_renegotiate -> + {ok, Version} = application:get_env(ssl, protocol_version), + case check_sane_openssl_renegotaite(Config, Version) of + {skip, _} = Skip -> + Skip; + _ -> + check_openssl_npn_support(Config) + end; + +special_init(TestCase, Config) + when TestCase == erlang_server_openssl_client_sni_match; + TestCase == erlang_server_openssl_client_sni_no_match; + TestCase == erlang_server_openssl_client_sni_no_header; + TestCase == erlang_server_openssl_client_sni_match_fun; + TestCase == erlang_server_openssl_client_sni_no_match_fun; + TestCase == erlang_server_openssl_client_sni_no_header_fun -> + check_openssl_sni_support(Config); + special_init(_, Config) -> Config. @@ -186,8 +263,8 @@ basic_erlang_client_openssl_server() -> [{doc,"Test erlang client with openssl server"}]. basic_erlang_client_openssl_server(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -197,14 +274,13 @@ basic_erlang_client_openssl_server(Config) when is_list(Config) -> CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile, - - ct:log("openssl cmd: ~p~n", [Cmd]), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + "-cert", CertFile, "-key", KeyFile], - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -217,7 +293,7 @@ basic_erlang_client_openssl_server(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false). @@ -226,40 +302,41 @@ basic_erlang_server_openssl_client() -> [{doc,"Test erlang server with openssl client"}]. basic_erlang_server_openssl_client(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + V2Compat = proplists:get_value(v2_hello_compatible, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Data = "From openssl to erlang", + ct:pal("v2_hello_compatible: ~p", [V2Compat]), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, - {options, ServerOpts}]), - Port = ssl_test_lib:inet_port(Server), - - Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ - " -host localhost", + {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, + {options,[{v2_hello_compatible, V2Compat} | ServerOpts]}]), - ct:log("openssl cmd: ~p~n", [Cmd]), + Port = ssl_test_lib:inet_port(Server), - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + Exe = "openssl", + Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port) | workaround_openssl_s_clinent()], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, Data), ssl_test_lib:check_result(Server, ok), %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), - process_flag(trap_exit, false), - ok. + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + %%-------------------------------------------------------------------- erlang_client_openssl_server() -> [{doc,"Test erlang client with openssl server"}]. erlang_client_openssl_server(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -268,15 +345,15 @@ erlang_client_openssl_server(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile, - - ct:log("openssl cmd: ~p~n", [Cmd]), - - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -289,7 +366,7 @@ erlang_client_openssl_server(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false). @@ -298,7 +375,7 @@ erlang_server_openssl_client() -> [{doc,"Test erlang server with openssl client"}]. erlang_server_openssl_client(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -309,31 +386,29 @@ erlang_server_openssl_client(Config) when is_list(Config) -> {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), - Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -host localhost", + Exe = "openssl", + Args = ["s_client", "-connect", "localhost: " ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version)], - ct:log("openssl cmd: ~p~n", [Cmd]), + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), true = port_command(OpenSslPort, Data), ssl_test_lib:check_result(Server, ok), %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), process_flag(trap_exit, false). -%%-------------------------------------------------------------------- - erlang_client_openssl_server_dsa_cert() -> [{doc,"Test erlang server with openssl client"}]. erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = ?config(client_dsa_opts, Config), - ServerOpts = ?config(server_dsa_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_dsa_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -343,17 +418,16 @@ erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> CaCertFile = proplists:get_value(cacertfile, ServerOpts), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile - ++ " -key " ++ KeyFile ++ " -Verify 2 -msg", - - ct:log("openssl cmd: ~p~n", [Cmd]), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-CAfile", CaCertFile, + "-key", KeyFile, "-Verify", "2", "-msg"], - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -367,7 +441,7 @@ erlang_client_openssl_server_dsa_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -376,8 +450,8 @@ erlang_server_openssl_client_dsa_cert() -> [{doc,"Test erlang server with openssl client"}]. erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = ?config(client_dsa_opts, Config), - ServerOpts = ?config(server_dsa_verify_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_dsa_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_dsa_verify_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -391,21 +465,22 @@ erlang_server_openssl_client_dsa_cert(Config) when is_list(Config) -> {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -host localhost " ++ " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile - ++ " -key " ++ KeyFile ++ " -msg", - - ct:log("openssl cmd: ~p~n", [Cmd]), - - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client", "-connect", "localhost: " ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, + "-CAfile", CaCertFile, + "-key", KeyFile, "-msg"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, Data), ssl_test_lib:check_result(Server, ok), %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), process_flag(trap_exit, false). %%-------------------------------------------------------------------- @@ -415,7 +490,7 @@ erlang_server_openssl_client_reuse_session() -> "same session id, to test reusing of sessions."}]. erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -427,13 +502,14 @@ erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> {reconnect_times, 5}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -host localhost -reconnect", - - ct:log("openssl cmd: ~p~n", [Cmd]), + Version = ssl_test_lib:protocol_version(Config), - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + Exe = "openssl", + Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-reconnect"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, Data), @@ -441,7 +517,7 @@ erlang_server_openssl_client_reuse_session(Config) when is_list(Config) -> %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), process_flag(trap_exit, false), ok. @@ -451,8 +527,8 @@ erlang_client_openssl_server_renegotiate() -> [{doc,"Test erlang client when openssl server issuses a renegotiate"}]. erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -462,16 +538,16 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile, "-msg"], - ct:log("openssl cmd: ~p~n", [Cmd]), - - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -487,7 +563,7 @@ erlang_client_openssl_server_renegotiate(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false), ok. @@ -501,8 +577,8 @@ erlang_client_openssl_server_nowrap_seqnum() -> " to lower treashold substantially."}]. erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -512,15 +588,15 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile, "-msg"], - ct:log("openssl cmd: ~p~n", [Cmd]), + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -533,7 +609,7 @@ erlang_client_openssl_server_nowrap_seqnum(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false). %%-------------------------------------------------------------------- @@ -544,7 +620,7 @@ erlang_server_openssl_client_nowrap_seqnum() -> " to lower treashold substantially."}]. erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -558,13 +634,13 @@ erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> trigger_renegotiate, [[Data, N+2]]}}, {options, [{renegotiate_at, N}, {reuse_sessions, false} | ServerOpts]}]), Port = ssl_test_lib:inet_port(Server), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -host localhost -msg", - - ct:log("openssl cmd: ~p~n", [Cmd]), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client","-connect", "localhost: " ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-msg"], - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, Data), @@ -572,7 +648,7 @@ erlang_server_openssl_client_nowrap_seqnum(Config) when is_list(Config) -> %% Clean close down! Server needs to be closed first !! ssl_test_lib:close(Server), - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), process_flag(trap_exit, false). %%-------------------------------------------------------------------- @@ -583,8 +659,8 @@ erlang_client_openssl_server_no_server_ca_cert() -> "implicitly tested eleswhere."}]. erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), - ClientOpts = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -593,15 +669,15 @@ erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ " -msg", + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile, "-msg"], - ct:log("openssl cmd: ~p~n", [Cmd]), - - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - - wait_for_openssl_server(), + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -615,7 +691,7 @@ erlang_client_openssl_server_no_server_ca_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false). @@ -624,8 +700,8 @@ erlang_client_openssl_server_client_cert() -> [{doc,"Test erlang client with openssl server when client sends cert"}]. erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_verification_opts, Config), - ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -635,16 +711,16 @@ erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> CertFile = proplists:get_value(certfile, ServerOpts), CaCertFile = proplists:get_value(cacertfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile - ++ " -key " ++ KeyFile ++ " -Verify 2", + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-CAfile", CaCertFile, + "-key", KeyFile, "-Verify", "2"], - ct:log("openssl cmd: ~p~n", [Cmd]), + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -657,7 +733,7 @@ erlang_client_openssl_server_client_cert(Config) when is_list(Config) -> ssl_test_lib:check_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false). @@ -667,8 +743,8 @@ erlang_server_openssl_client_client_cert() -> [{doc,"Test erlang server with openssl client when client sends cert"}]. erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_verification_opts, Config), - ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -686,20 +762,19 @@ erlang_server_openssl_client_client_cert(Config) when is_list(Config) -> CaCertFile = proplists:get_value(cacertfile, ClientOpts), CertFile = proplists:get_value(certfile, ClientOpts), KeyFile = proplists:get_value(keyfile, ClientOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_client -cert " ++ CertFile ++ " -CAfile " ++ CaCertFile - ++ " -key " ++ KeyFile ++ " -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -host localhost", - - ct:log("openssl cmd: ~p~n", [Cmd]), - - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - true = port_command(OpenSslPort, Data), - + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client", "-cert", CertFile, + "-CAfile", CaCertFile, + "-key", KeyFile,"-connect", "localhost:" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version)], + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + true = port_command(OpenSslPort, Data), ssl_test_lib:check_result(Server, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), ssl_test_lib:close(Server), process_flag(trap_exit, false). @@ -709,9 +784,9 @@ erlang_server_erlang_client_client_cert() -> [{doc,"Test erlang server with erlang client when client sends cert"}]. erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_verification_opts, Config), - ClientOpts = ?config(client_verification_opts, Config), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + ServerOpts = proplists:get_value(server_verification_opts, Config), + ClientOpts = proplists:get_value(client_verification_opts, Config), + Version = ssl_test_lib:protocol_version(Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Data = "From erlang to erlang", @@ -746,9 +821,7 @@ erlang_server_erlang_client_client_cert(Config) when is_list(Config) -> ciphers_rsa_signed_certs() -> [{doc,"Test cipher suites that uses rsa certs"}]. ciphers_rsa_signed_certs(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), - + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:rsa_suites(openssl), run_suites(Ciphers, Version, Config, rsa). %%-------------------------------------------------------------------- @@ -756,9 +829,7 @@ ciphers_rsa_signed_certs(Config) when is_list(Config) -> ciphers_dsa_signed_certs() -> [{doc,"Test cipher suites that uses dsa certs"}]. ciphers_dsa_signed_certs(Config) when is_list(Config) -> - Version = - tls_record:protocol_version(tls_record:highest_protocol_version([])), - + Version = ssl_test_lib:protocol_version(Config), Ciphers = ssl_test_lib:dsa_suites(), run_suites(Ciphers, Version, Config, dsa). @@ -767,23 +838,21 @@ erlang_client_bad_openssl_server() -> [{doc,"Test what happens if openssl server sends garbage to erlang ssl client"}]. erlang_client_bad_openssl_server(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_verification_opts, Config), - ClientOpts = ?config(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", - - ct:log("openssl cmd: ~p~n", [Cmd]), - - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client0 = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -812,7 +881,7 @@ erlang_client_bad_openssl_server(Config) when is_list(Config) -> [{versions, [Version]} | ClientOpts]}]), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client1), process_flag(trap_exit, false), ok. @@ -824,22 +893,21 @@ expired_session() -> "better code coverage of the ssl_manager module"}]. expired_session(Config) when is_list(Config) -> process_flag(trap_exit, true), - ClientOpts = ?config(client_opts, Config), - ServerOpts = ?config(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), + "-cert", CertFile,"-key", KeyFile], - ct:log("openssl cmd: ~p~n", [Cmd]), - - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), Client0 = ssl_test_lib:start_client([{node, ClientNode}, @@ -869,7 +937,7 @@ expired_session(Config) when is_list(Config) -> {from, self()}, {options, ClientOpts}]), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client2), process_flag(trap_exit, false). @@ -879,7 +947,7 @@ ssl2_erlang_server_openssl_client() -> ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -890,22 +958,154 @@ ssl2_erlang_server_openssl_client(Config) when is_list(Config) -> {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Cmd = "openssl s_client -port " ++ integer_to_list(Port) ++ - " -host localhost -ssl2 -msg", - - ct:log("openssl cmd: ~p~n", [Cmd]), + Exe = "openssl", + Args = ["s_client", "-connect", "localhost:" ++ integer_to_list(Port), + "-ssl2", "-msg"], - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, Data), + + ct:log("Ports ~p~n", [[erlang:port_info(P) || P <- erlang:ports()]]), receive - {'EXIT', OpenSslPort, _} -> + {'EXIT', OpenSslPort, _} = Exit -> + ct:log("Received: ~p ~n", [Exit]), ok - end, + receive + {'EXIT', _, _} = UnkownExit -> + Msg = lists:flatten(io_lib:format("Received: ~p ~n", [UnkownExit])), + ct:log(Msg), + ct:comment(Msg), + ok + after 0 -> + ok + end, ssl_test_lib:check_result(Server, {error, {tls_alert, "protocol version"}}), process_flag(trap_exit, false). %%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_client_alpn_openssl_server(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_with_opts(Config, + [{alpn_advertised_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_client_openssl_server_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_server_alpn_openssl_client(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_with_opts(Config, + [{alpn_preferred_protocols, [<<"spdy/2">>]}], + [], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------------- + +erlang_server_openssl_client_alpn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_with_opts(Config, + [], + ["-alpn", "spdy/2"], + Data, fun(Server, OpensslPort) -> + true = port_command(OpensslPort, Data), + ssl_test_lib:check_result(Server, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_client_alpn_openssl_server_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_server_alpn_openssl_client_alpn_renegotiate(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, ?OPENSSL_RENEGOTIATE), + ct:sleep(?SLEEP), + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_client_alpn_npn_openssl_server_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- + +erlang_server_alpn_npn_openssl_client_alpn_npn(Config) when is_list(Config) -> + Data = "From openssl to erlang", + start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, fun(Client, OpensslPort) -> + true = port_command(OpensslPort, Data), + + ssl_test_lib:check_result(Client, ok) + end), + ok. + +%%-------------------------------------------------------------------- erlang_client_openssl_server_npn() -> [{doc,"Test erlang client with openssl server doing npn negotiation"}]. @@ -961,7 +1161,7 @@ erlang_server_openssl_client_npn_renegotiate(Config) when is_list(Config) -> erlang_client_openssl_server_npn_only_server(Config) when is_list(Config) -> Data = "From openssl to erlang", start_erlang_client_and_openssl_server_with_opts(Config, [], - "-nextprotoneg spdy/2", Data, fun(Server, OpensslPort) -> + ["-nextprotoneg", "spdy/2"], Data, fun(Server, OpensslPort) -> true = port_command(OpensslPort, Data), ssl_test_lib:check_result(Server, ok) end), @@ -973,7 +1173,7 @@ erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> Data = "From openssl to erlang", start_erlang_client_and_openssl_server_with_opts(Config, [{client_preferred_next_protocols, - {client, [<<"spdy/2">>], <<"http/1.1">>}}], "", + {client, [<<"spdy/2">>], <<"http/1.1">>}}], [], Data, fun(Server, OpensslPort) -> true = port_command(OpensslPort, Data), ssl_test_lib:check_result(Server, ok) @@ -983,7 +1183,7 @@ erlang_client_openssl_server_npn_only_client(Config) when is_list(Config) -> %%-------------------------------------------------------------------------- erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [{next_protocols_advertised, [<<"spdy/2">>]}], "", + start_erlang_server_and_openssl_client_with_opts(Config, [{next_protocols_advertised, [<<"spdy/2">>]}], [], Data, fun(Server, OpensslPort) -> true = port_command(OpensslPort, Data), ssl_test_lib:check_result(Server, ok) @@ -992,12 +1192,31 @@ erlang_server_openssl_client_npn_only_server(Config) when is_list(Config) -> erlang_server_openssl_client_npn_only_client(Config) when is_list(Config) -> Data = "From openssl to erlang", - start_erlang_server_and_openssl_client_with_opts(Config, [], "-nextprotoneg spdy/2", + start_erlang_server_and_openssl_client_with_opts(Config, [], ["-nextprotoneg", "spdy/2"], Data, fun(Server, OpensslPort) -> true = port_command(OpensslPort, Data), ssl_test_lib:check_result(Server, ok) end), ok. +%-------------------------------------------------------------------------- +erlang_server_openssl_client_sni_no_header(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, undefined, undefined, "server"). + +erlang_server_openssl_client_sni_no_header_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, undefined, undefined, "server"). + +erlang_server_openssl_client_sni_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "a.server", "a.server", "a.server"). + +erlang_server_openssl_client_sni_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "a.server", "a.server", "a.server"). + +erlang_server_openssl_client_sni_no_match(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test(Config, "c.server", undefined, "server"). + +erlang_server_openssl_client_sni_no_match_fun(Config) when is_list(Config) -> + erlang_server_openssl_client_sni_test_sni_fun(Config, "c.server", undefined, "server"). + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ @@ -1006,11 +1225,11 @@ run_suites(Ciphers, Version, Config, Type) -> {ClientOpts, ServerOpts} = case Type of rsa -> - {?config(client_opts, Config), - ?config(server_opts, Config)}; + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_opts, Config)}; dsa -> - {?config(client_opts, Config), - ?config(server_dsa_opts, Config)} + {ssl_test_lib:ssl_options(client_opts, Config), + ssl_test_lib:ssl_options(server_dsa_opts, Config)} end, Result = lists:map(fun(Cipher) -> @@ -1024,6 +1243,99 @@ run_suites(Ciphers, Version, Config, Type) -> ct:fail(cipher_suite_failed_see_test_case_log) end. +client_read_check([], _Data) -> + ok; +client_read_check([Hd | T], Data) -> + case binary:match(Data, list_to_binary(Hd)) of + nomatch -> + nomatch; + _ -> + client_read_check(T, Data) + end. +client_check_result(Port, DataExpected, DataReceived) -> + receive + {Port, {data, TheData}} -> + Data = list_to_binary(TheData), + NewData = <<DataReceived/binary, Data/binary>>, + ct:log("New Data: ~p", [NewData]), + case client_read_check(DataExpected, NewData) of + ok -> + ok; + _ -> + client_check_result(Port, DataExpected, NewData) + end + after 3000 -> + ct:fail({"Time out on openSSL Client", {expected, DataExpected}, + {got, DataReceived}}) + end. +client_check_result(Port, DataExpected) -> + client_check_result(Port, DataExpected, <<"">>). + +send_and_hostname(SSLSocket) -> + ssl:send(SSLSocket, "OK"), + case ssl:connection_information(SSLSocket, [sni_hostname]) of + {ok, []} -> + undefined; + {ok, [{sni_hostname, Hostname}]} -> + Hostname + end. + +erlang_server_openssl_client_sni_test(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + ServerOptions = proplists:get_value(sni_server_opts, Config) ++ proplists:get_value(server_opts, Config), + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname,Port); + _ -> + openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname, Port, SNIHostname) + end, + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + %% Client check needs to be done befor server check, + %% or server check might consume client messages + ExpectedClientOutput = ["OK", "/CN=" ++ ExpectedCN ++ "/"], + client_check_result(ClientPort, ExpectedClientOutput), + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server), + ok. + + +erlang_server_openssl_client_sni_test_sni_fun(Config, SNIHostname, ExpectedSNIHostname, ExpectedCN) -> + ct:log("Start running handshake for sni_fun, Config: ~p, SNIHostname: ~p, ExpectedSNIHostname: ~p, ExpectedCN: ~p", [Config, SNIHostname, ExpectedSNIHostname, ExpectedCN]), + [{sni_hosts, ServerSNIConf}] = proplists:get_value(sni_server_opts, Config), + SNIFun = fun(Domain) -> proplists:get_value(Domain, ServerSNIConf, undefined) end, + ServerOptions = proplists:get_value(server_opts, Config) ++ [{sni_fun, SNIFun}], + {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, {mfa, {?MODULE, send_and_hostname, []}}, + {options, ServerOptions}]), + Port = ssl_test_lib:inet_port(Server), + Exe = "openssl", + ClientArgs = case SNIHostname of + undefined -> + openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname,Port); + _ -> + openssl_client_args(ssl_test_lib:supports_ssl_tls_version(sslv2), Hostname, Port, SNIHostname) + end, + + ClientPort = ssl_test_lib:portable_open_port(Exe, ClientArgs), + + %% Client check needs to be done befor server check, + %% or server check might consume client messages + ExpectedClientOutput = ["OK", "/CN=" ++ ExpectedCN ++ "/"], + client_check_result(ClientPort, ExpectedClientOutput), + ssl_test_lib:check_result(Server, ExpectedSNIHostname), + ssl_test_lib:close_port(ClientPort), + ssl_test_lib:close(Server). + + cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> process_flag(trap_exit, true), ct:log("Testing CipherSuite ~p~n", [CipherSuite]), @@ -1033,14 +1345,13 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Cmd = "openssl s_server -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile ++ "", + Exe = "openssl", + Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], - ct:log("openssl cmd: ~p~n", [Cmd]), + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), - - wait_for_openssl_server(), + ssl_test_lib:wait_for_openssl_server(Port), ConnectionInfo = {ok, {Version, CipherSuite}}, @@ -1069,7 +1380,7 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> Result = ssl_test_lib:wait_for_result(Client, ok), %% Clean close down! Server needs to be closed first !! - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), ssl_test_lib:close(Client), Return = case Result of @@ -1083,8 +1394,8 @@ cipher(CipherSuite, Version, Config, ClientOpts, ServerOpts) -> start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, OpensslServerOpts, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), - ClientOpts0 = ?config(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), ClientOpts = ErlangClientOpts ++ ClientOpts0, {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1094,38 +1405,110 @@ start_erlang_client_and_openssl_server_with_opts(Config, ErlangClientOpts, Opens Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = case OpensslServerOpts of + [] -> + ["s_server", "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile,"-key", KeyFile]; + [Opt, Value] -> + ["s_server", Opt, Value, "-accept", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile,"-key", KeyFile] + end, + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port), - Cmd = "openssl s_server " ++ OpensslServerOpts ++ " -accept " ++ - integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile, + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive, [Data]}}, + {options, ClientOpts}]), - ct:log("openssl cmd: ~p~n", [Cmd]), + Callback(Client, OpensslPort), - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), - wait_for_openssl_server(), + ssl_test_lib:close(Client), + process_flag(trap_exit, false). + +start_erlang_client_and_openssl_server_for_alpn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_opts, Config), + ClientOpts0 = proplists:get_value(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {?MODULE, - erlang_ssl_receive, [Data]}}, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, {options, ClientOpts}]), Callback(Client, OpensslPort), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false). -start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> +start_erlang_server_and_openssl_client_for_alpn_negotiation(Config, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts = ?config(server_opts, Config), - ClientOpts0 = ?config(client_opts, Config), - ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], + ServerOpts0 = proplists:get_value(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), + + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.0,spdy/2", "-msg", "-port", + integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), + + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + +start_erlang_client_and_openssl_server_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = proplists:get_value(server_opts, Config), + ClientOpts0 = proplists:get_value(client_opts, Config), + ClientOpts = [{alpn_advertised_protocols, [<<"spdy/2">>]}, + {client_preferred_next_protocols, {client, [<<"spdy/3">>, <<"http/1.1">>]}} | ClientOpts0], {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), @@ -1134,35 +1517,100 @@ start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callbac Port = ssl_test_lib:inet_port(node()), CertFile = proplists:get_value(certfile, ServerOpts), KeyFile = proplists:get_value(keyfile, ServerOpts), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", + "spdy/3", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port), + + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ClientOpts}]), + + Callback(Client, OpensslPort), + + %% Clean close down! Server needs to be closed first !! + ssl_test_lib:close_port(OpensslPort), + + ssl_test_lib:close(Client), + process_flag(trap_exit, false). - Cmd = "openssl s_server -msg -nextprotoneg http/1.1,spdy/2 -accept " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -cert " ++ CertFile ++ " -key " ++ KeyFile, +start_erlang_server_and_openssl_client_for_alpn_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts0 = proplists:get_value(server_opts, Config), + ServerOpts = [{alpn_preferred_protocols, [<<"spdy/2">>]}, + {next_protocols_advertised, [<<"spdy/3">>, <<"http/1.1">>]} | ServerOpts0], + + {_, ServerNode, _} = ssl_test_lib:run_where(Config), - ct:log("openssl cmd: ~p~n", [Cmd]), - OpensslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Version = ssl_test_lib:protocol_version(Config), + Exe = "openssl", + Args = ["s_client", "-alpn", "http/1.1,spdy/2", "-nextprotoneg", "spdy/3", + "-msg", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), + "-host", "localhost"], + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), - wait_for_openssl_server(), + Callback(Server, OpenSslPort), + + ssl_test_lib:close(Server), + ssl_test_lib:close_port(OpenSslPort), + process_flag(trap_exit, false). + +start_erlang_client_and_openssl_server_for_npn_negotiation(Config, Data, Callback) -> + process_flag(trap_exit, true), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts0 = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = [{client_preferred_next_protocols, {client, [<<"spdy/2">>], <<"http/1.1">>}} | ClientOpts0], + + {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), + + Data = "From openssl to erlang", + + Port = ssl_test_lib:inet_port(node()), + CertFile = proplists:get_value(certfile, ServerOpts), + KeyFile = proplists:get_value(keyfile, ServerOpts), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_server", "-msg", "-nextprotoneg", "http/1.1,spdy/2", "-accept", integer_to_list(Port), + ssl_test_lib:version_flag(Version), + "-cert", CertFile, "-key", KeyFile], + OpensslPort = ssl_test_lib:portable_open_port(Exe, Args), + + ssl_test_lib:wait_for_openssl_server(Port), Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, {from, self()}, {mfa, {?MODULE, - erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}}, + erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, {options, ClientOpts}]), Callback(Client, OpensslPort), %% Clean close down! Server needs to be closed first !! - close_port(OpensslPort), + ssl_test_lib:close_port(OpensslPort), ssl_test_lib:close(Client), process_flag(trap_exit, false). start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts0 = ?config(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), ServerOpts = [{next_protocols_advertised, [<<"spdy/2">>]}, ServerOpts0], {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -1170,28 +1618,28 @@ start_erlang_server_and_openssl_client_for_npn_negotiation(Config, Data, Callbac Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, - {mfa, {?MODULE, erlang_ssl_receive_and_assert_npn, [<<"spdy/2">>, Data]}}, + {mfa, {?MODULE, erlang_ssl_receive_and_assert_negotiated_protocol, [<<"spdy/2">>, Data]}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_client -nextprotoneg http/1.0,spdy/2 -msg -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -host localhost", + Version = ssl_test_lib:protocol_version(Config), - ct:log("openssl cmd: ~p~n", [Cmd]), + Exe = "openssl", + Args = ["s_client", "-nextprotoneg", "http/1.0,spdy/2", "-msg", "-connect", "localhost:" + ++ integer_to_list(Port), ssl_test_lib:version_flag(Version)], - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), Callback(Server, OpenSslPort), ssl_test_lib:close(Server), - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), process_flag(trap_exit, false). start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenSSLClientOpts, Data, Callback) -> process_flag(trap_exit, true), - ServerOpts0 = ?config(server_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config), ServerOpts = ErlangServerOpts ++ ServerOpts0, {_, ServerNode, _} = ssl_test_lib:run_where(Config), @@ -1202,31 +1650,31 @@ start_erlang_server_and_openssl_client_with_opts(Config, ErlangServerOpts, OpenS {mfa, {?MODULE, erlang_ssl_receive, [Data]}}, {options, ServerOpts}]), Port = ssl_test_lib:inet_port(Server), - Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), - Cmd = "openssl s_client " ++ OpenSSLClientOpts ++ " -msg -port " ++ integer_to_list(Port) ++ version_flag(Version) ++ - " -host localhost", - - ct:log("openssl cmd: ~p~n", [Cmd]), + Version = ssl_test_lib:protocol_version(Config), + + Exe = "openssl", + Args = ["s_client"] ++ OpenSSLClientOpts ++ ["-msg", "-connect", "localhost:" ++ integer_to_list(Port), + ssl_test_lib:version_flag(Version)], - OpenSslPort = open_port({spawn, Cmd}, [stderr_to_stdout]), + OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), Callback(Server, OpenSslPort), ssl_test_lib:close(Server), - close_port(OpenSslPort), + ssl_test_lib:close_port(OpenSslPort), process_flag(trap_exit, false). -erlang_ssl_receive_and_assert_npn(Socket, Protocol, Data) -> - {ok, Protocol} = ssl:negotiated_next_protocol(Socket), +erlang_ssl_receive_and_assert_negotiated_protocol(Socket, Protocol, Data) -> + {ok, Protocol} = ssl:negotiated_protocol(Socket), erlang_ssl_receive(Socket, Data), - {ok, Protocol} = ssl:negotiated_next_protocol(Socket), + {ok, Protocol} = ssl:negotiated_protocol(Socket), ok. erlang_ssl_receive(Socket, Data) -> ct:log("Connection info: ~p~n", - [ssl:connection_info(Socket)]), + [ssl:connection_information(Socket)]), receive {ssl, Socket, Data} -> io:format("Received ~p~n",[Data]), @@ -1240,21 +1688,19 @@ erlang_ssl_receive(Socket, Data) -> erlang_ssl_receive(Socket,Data); Other -> ct:fail({unexpected_message, Other}) - after 4000 -> - ct:fail({did_not_get, Data}) end. connection_info(Socket, Version) -> - case ssl:connection_info(Socket) of - {ok, {Version, _} = Info} -> + case ssl:connection_information(Socket, [version]) of + {ok, [{version, Version}] = Info} -> ct:log("Connection info: ~p~n", [Info]), ok; - {ok, {OtherVersion, _}} -> + {ok, [{version, OtherVersion}]} -> {wrong_version, OtherVersion} end. connection_info_result(Socket) -> - ssl:connection_info(Socket). + ssl:connection_information(Socket). delayed_send(Socket, [ErlData, OpenSslData]) -> @@ -1262,39 +1708,6 @@ delayed_send(Socket, [ErlData, OpenSslData]) -> ssl:send(Socket, ErlData), erlang_ssl_receive(Socket, OpenSslData). -close_port(Port) -> - catch port_command(Port, ?OPENSSL_QUIT), - close_loop(Port, 500, false). - -close_loop(Port, Time, SentClose) -> - receive - {Port, {data,Debug}} when is_port(Port) -> - ct:log("openssl ~s~n",[Debug]), - close_loop(Port, Time, SentClose); - {ssl,_,Msg} -> - ct:log("ssl Msg ~s~n",[Msg]), - close_loop(Port, Time, SentClose); - {Port, closed} -> - ct:log("Port Closed~n",[]), - ok; - {'EXIT', Port, Reason} -> - ct:log("Port Closed ~p~n",[Reason]), - ok; - Msg -> - ct:log("Port Msg ~p~n",[Msg]), - close_loop(Port, Time, SentClose) - after Time -> - case SentClose of - false -> - ct:log("Closing port ~n",[]), - catch erlang:port_close(Port), - close_loop(Port, Time, true); - true -> - ct:log("Timeout~n",[]) - end - end. - - server_sent_garbage(Socket) -> receive server_sent_garbage -> @@ -1302,26 +1715,15 @@ server_sent_garbage(Socket) -> end. -wait_for_openssl_server() -> - receive - {Port, {data, Debug}} when is_port(Port) -> - ct:log("openssl ~s~n",[Debug]), - %% openssl has started make sure - %% it will be in accept. Parsing - %% output is too error prone. (Even - %% more so than sleep!) - ct:sleep(?SLEEP) +check_openssl_sni_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "-servername") of + 0 -> + {skip, "Current openssl doesn't support SNI"}; + _ -> + Config end. -version_flag(tlsv1) -> - " -tls1 "; -version_flag('tlsv1.1') -> - " -tls1_1 "; -version_flag('tlsv1.2') -> - " -tls1_2 "; -version_flag(sslv3) -> - " -ssl3 ". - check_openssl_npn_support(Config) -> HelpText = os:cmd("openssl s_client --help"), case string:str(HelpText, "nextprotoneg") of @@ -1331,8 +1733,36 @@ check_openssl_npn_support(Config) -> Config end. +check_openssl_alpn_support(Config) -> + HelpText = os:cmd("openssl s_client --help"), + case string:str(HelpText, "alpn") of + 0 -> + {skip, "Openssl not compiled with alpn support"}; + _ -> + Config + end. + +check_sane_openssl_renegotaite(Config, Version) when Version == 'tlsv1.1'; + Version == 'tlsv1.2' -> + case os:cmd("openssl version") of + "OpenSSL 1.0.1c" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1b" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1a" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + "OpenSSL 1.0.1 " ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; + _ -> + check_sane_openssl_renegotaite(Config) + end; +check_sane_openssl_renegotaite(Config, _) -> + check_sane_openssl_renegotaite(Config). + check_sane_openssl_renegotaite(Config) -> - case os:cmd("openssl version") of + case os:cmd("openssl version") of + "OpenSSL 1.0.0" ++ _ -> + {skip, "Known renegotiation bug in OpenSSL"}; "OpenSSL 0.9.8" ++ _ -> {skip, "Known renegotiation bug in OpenSSL"}; "OpenSSL 0.9.7" ++ _ -> @@ -1341,42 +1771,32 @@ check_sane_openssl_renegotaite(Config) -> Config end. -check_sane_openssl_sslv2(Config) -> - Port = open_port({spawn, "openssl s_client -ssl2 "}, [stderr_to_stdout]), - case supports_sslv2(Port) of - true -> - Config; - false -> - {skip, "sslv2 not supported by openssl"} +workaround_openssl_s_clinent() -> + %% http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=683159 + %% https://bugs.archlinux.org/task/33919 + %% Bug seems to manifests it self if TLS version is not + %% explicitly specified + case os:cmd("openssl version") of + "OpenSSL 1.0.1c" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1d" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1e" ++ _ -> + ["-no_tls1_2"]; + "OpenSSL 1.0.1f" ++ _ -> + ["-no_tls1_2"]; + _ -> + [] end. -supports_sslv2(Port) -> - receive - {Port, {data, "unknown option -ssl2" ++ _}} -> - false; - {Port, {data, Data}} -> - case lists:member("error", string:tokens(Data, ":")) of - true -> - false; - false -> - supports_sslv2(Port) - end - after 500 -> - true - end. - -check_sane_openssl_version(Version) -> - case {Version, os:cmd("openssl version")} of - {_, "OpenSSL 1.0.1" ++ _} -> - true; - {'tlsv1.2', "OpenSSL 1.0" ++ _} -> - false; - {'tlsv1.1', "OpenSSL 1.0" ++ _} -> - false; - {'tlsv1.2', "OpenSSL 0" ++ _} -> - false; - {'tlsv1.1', "OpenSSL 0" ++ _} -> - false; - {_, _} -> - true - end. +openssl_client_args(false, Hostname, Port) -> + ["s_client", "-connect", Hostname ++ ":" ++ integer_to_list(Port)]; +openssl_client_args(true, Hostname, Port) -> + ["s_client", "-no_ssl2", "-connect", Hostname ++ ":" ++ integer_to_list(Port)]. + +openssl_client_args(false, Hostname, Port, ServerName) -> + ["s_client", "-connect", Hostname ++ ":" ++ + integer_to_list(Port), "-servername", ServerName]; +openssl_client_args(true, Hostname, Port, ServerName) -> + ["s_client", "-no_ssl2", "-connect", Hostname ++ ":" ++ + integer_to_list(Port), "-servername", ServerName]. diff --git a/lib/ssl/test/ssl_upgrade_SUITE.erl b/lib/ssl/test/ssl_upgrade_SUITE.erl new file mode 100644 index 0000000000..113b3b4158 --- /dev/null +++ b/lib/ssl/test/ssl_upgrade_SUITE.erl @@ -0,0 +1,277 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2014-2015. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% +-module(ssl_upgrade_SUITE). + +%% Note: This directive should only be used in test suites. +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +-record(state, { + config, + server, + client, + soft, + result_proxy + }). + +all() -> + [ + minor_upgrade, + major_upgrade + ]. + +init_per_suite(Config0) -> + catch crypto:stop(), + try crypto:start() of + ok -> + case ct_release_test:init(Config0) of + {skip, Reason} -> + {skip, Reason}; + Config -> + Result = + {ok, _} = make_certs:all(proplists:get_value(data_dir, Config), + proplists:get_value(priv_dir, Config)), + ssl_test_lib:cert_options(Config) + end + catch _:_ -> + {skip, "Crypto did not start"} + end. + +end_per_suite(Config) -> + ct_release_test:cleanup(Config), + crypto:stop(). + +init_per_testcase(_TestCase, Config) -> + ssl_test_lib:ct_log_supported_protocol_versions(Config), + ct:timetrap({minutes, 1}), + Config. + +end_per_testcase(_TestCase, Config) -> + Config. + +major_upgrade(Config) when is_list(Config) -> + ct_release_test:upgrade(ssl, major,{?MODULE, #state{config = Config}}, Config). + +minor_upgrade(Config) when is_list(Config) -> + ct_release_test:upgrade(ssl, minor,{?MODULE, #state{config = Config}}, Config). + +upgrade_init(CTData, #state{config = Config} = State) -> + {ok, {_, _, Up, _Down}} = ct_release_test:get_appup(CTData, ssl), + ct:pal("Up: ~p", [Up]), + Soft = is_soft(Up), %% It is symmetrical, if upgrade is soft so is downgrade + Pid = spawn(?MODULE, result_proxy_init, [[]]), + case Soft of + true -> + {Server, Client} = soft_start_connection(Config, Pid), + State#state{server = Server, client = Client, + soft = Soft, + result_proxy = Pid}; + false -> + State#state{soft = Soft, result_proxy = Pid} + end. + +upgrade_upgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = State) -> + ct:pal("Restart upgrade ~n", []), + {Server, Client} = restart_start_connection(Config, Pid), + Result = check_result(Pid, Server, Client), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ok = Result, + State; + +upgrade_upgraded(_, #state{server = Server0, client = Client0, + config = Config, soft = true, + result_proxy = Pid} = State) -> + ct:pal("Soft upgrade: ~n", []), + Server0 ! changed_version, + Client0 ! changed_version, + Result = check_result(Pid, Server0, Client0), + ssl_test_lib:close(Server0), + ssl_test_lib:close(Client0), + ok = Result, + {Server, Client} = soft_start_connection(Config, Pid), + State#state{server = Server, client = Client}. + +upgrade_downgraded(_, #state{soft = false, config = Config, result_proxy = Pid} = State) -> + ct:pal("Restart downgrade: ~n", []), + {Server, Client} = restart_start_connection(Config, Pid), + Result = check_result(Pid, Server, Client), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + Pid ! stop, + ok = Result, + State; + +upgrade_downgraded(_, #state{server = Server, client = Client, soft = true, result_proxy = Pid} = State) -> + ct:pal("Soft downgrade: ~n", []), + Server ! changed_version, + Client ! changed_version, + Result = check_result(Pid, Server, Client), + Pid ! stop, + ssl_test_lib:close(Server), + ssl_test_lib:close(Client), + ok = Result, + State. + +use_connection(Socket) -> + ssl_test_lib:send_recv_result_active(Socket), + receive + changed_version -> + ssl_test_lib:send_recv_result_active(Socket) + end. + +soft_start_connection(Config, ResulProxy) -> + ClientOpts = proplists:get_value(client_verification_opts, Config), + ServerOpts = proplists:get_value(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = start_server([{node, ServerNode}, {port, 0}, + {from, ResulProxy}, + {mfa, {?MODULE, use_connection, []}}, + {options, ServerOpts}]), + + Port = inet_port(ResulProxy, Server), + Client = start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, ResulProxy}, + {mfa, {?MODULE, use_connection, []}}, + {options, ClientOpts}]), + {Server, Client}. + +restart_start_connection(Config, ResulProxy) -> + ClientOpts = proplists:get_value(client_verification_opts, Config), + ServerOpts = proplists:get_value(server_verification_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = start_server([{node, ServerNode}, {port, 0}, + {from, ResulProxy}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = inet_port(ResulProxy, Server), + Client = start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, ResulProxy}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ClientOpts}]), + {Server, Client}. + +is_soft([{restart_application, ssl}]) -> + false; +is_soft(_) -> + true. + +result_proxy_init(Args) -> + result_proxy_loop(Args). + +result_proxy_loop(Args) -> + receive + {Pid, {check_result, Server, Client}} -> + Result = do_check_result(Server, ok, Client, ok), + Pid ! {self(), Result}, + result_proxy_loop(Args); + {Pid, port, Server} -> + Port = recv_port(Server), + Pid ! Port, + result_proxy_loop(Args); + {Pid, listen} -> + recv_listen(), + Pid ! ok, + result_proxy_loop(Args); + {Pid, connected} -> + Connected = recv_connected(), + Pid ! Connected, + result_proxy_loop(Args) + end. + +check_result(Pid, Server, Client) -> + Pid ! {self(), {check_result, Server, Client}}, + receive + {Pid, Result} -> + Result + end. + +do_check_result(Server, ServerMsg, Client, ClientMsg) -> + receive + {Server, ServerMsg} -> + do_check_result(Client, ClientMsg); + + {Client, ClientMsg} -> + do_check_result(Server, ServerMsg); + Unexpected -> + {{expected, {Client, ClientMsg}}, + {expected, {Server, ServerMsg}}, {got, Unexpected}} + end. + +do_check_result(Pid, Msg) -> + receive + {Pid, Msg} -> + ok; + Unexpected -> + {{expected, {Pid, Msg}}, + {got, Unexpected}} + end. + +inet_port(Pid, Server) -> + Pid ! {self(), port, Server}, + receive + {port, Port} -> + Port + end. + +recv_port(Server) -> + receive + {Server, {port, Port}} -> + {port, Port} + end. + +recv_connected() -> + receive + {connected, _Socket} -> + ok; + {connect_failed, Reason} -> + {connect_failed, Reason} + end. + + +start_server(Args) -> + Pid = proplists:get_value(from, Args), + Result = spawn_link(ssl_test_lib, run_server, [Args]), + Pid ! {self(), listen}, + receive + ok -> + ok + end, + Result. + +start_client(Args) -> + Pid = proplists:get_value(from, Args), + Result = spawn_link(ssl_test_lib, run_client_init, [lists:delete(return_socket, Args)]), + Pid ! {self(), connected}, + receive + ok -> + Result; + Reason -> + exit(Reason) + end. + +recv_listen()-> + receive + {listen, up} -> + ok + end. diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 9dd151553c..3b51fa8c6b 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 5.3 +SSL_VSN = 8.0 |