aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/asn1/doc/src/asn1ct.xml4
-rw-r--r--lib/common_test/doc/src/common_test_app.xml54
-rw-r--r--lib/common_test/doc/src/config_file_chapter.xml16
-rw-r--r--lib/common_test/doc/src/cover_chapter.xml10
-rw-r--r--lib/common_test/doc/src/ct_hooks_chapter.xml40
-rw-r--r--lib/common_test/doc/src/ct_master_chapter.xml2
-rw-r--r--lib/common_test/doc/src/ct_run.xml6
-rw-r--r--lib/common_test/doc/src/event_handler_chapter.xml12
-rw-r--r--lib/common_test/doc/src/getting_started_chapter.xml12
-rw-r--r--lib/common_test/doc/src/install_chapter.xml4
-rw-r--r--lib/common_test/doc/src/run_test_chapter.xml96
-rw-r--r--lib/common_test/doc/src/write_test_chapter.xml26
-rw-r--r--lib/common_test/include/ct.hrl4
-rw-r--r--lib/common_test/src/cth_log_redirect.erl13
-rw-r--r--lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl17
-rw-r--r--lib/compiler/doc/src/notes.xml10
-rw-r--r--lib/compiler/src/core_lint.erl2
-rw-r--r--lib/debugger/doc/src/notes.xml8
-rw-r--r--lib/edoc/doc/src/notes.xml4
-rw-r--r--lib/erl_docgen/doc/src/notes.xml4
-rw-r--r--lib/erl_docgen/priv/dtd/common.dtd2
-rw-r--r--lib/erl_interface/doc/src/notes.xml4
-rw-r--r--lib/et/doc/src/notes.xml6
-rw-r--r--lib/hipe/doc/src/notes.xml6
-rw-r--r--lib/inets/doc/src/notes.xml6
-rw-r--r--lib/kernel/doc/src/notes.xml10
-rw-r--r--lib/megaco/src/app/megaco.appup.src4
-rw-r--r--lib/mnesia/doc/src/notes.xml6
-rw-r--r--lib/observer/doc/src/notes.xml4
-rw-r--r--lib/odbc/doc/src/notes.xml6
-rw-r--r--lib/odbc/doc/src/odbc.xml3
-rw-r--r--lib/parsetools/src/leex.erl6
-rw-r--r--lib/parsetools/test/leex_SUITE.erl24
-rw-r--r--lib/percept/doc/src/notes.xml4
-rw-r--r--lib/reltool/doc/src/notes.xml4
-rw-r--r--lib/runtime_tools/doc/src/notes.xml4
-rw-r--r--lib/snmp/doc/src/notes.xml4
-rw-r--r--lib/ssh/doc/src/notes.xml4
-rw-r--r--lib/ssl/src/Makefile9
-rw-r--r--lib/ssl/src/dtls_connection.erl322
-rw-r--r--lib/ssl/src/dtls_handshake.erl410
-rw-r--r--lib/ssl/src/dtls_handshake.hrl18
-rw-r--r--lib/ssl/src/dtls_record.erl366
-rw-r--r--lib/ssl/src/dtls_v1.erl43
-rw-r--r--lib/ssl/src/ssl.app.src42
-rw-r--r--lib/ssl/src/ssl.appup.src2
-rw-r--r--lib/ssl/src/ssl_cipher.erl28
-rw-r--r--lib/ssl/src/ssl_cipher.hrl3
-rw-r--r--lib/ssl/src/ssl_handshake.erl1650
-rw-r--r--lib/ssl/src/ssl_handshake.hrl34
-rw-r--r--lib/ssl/src/ssl_internal.hrl9
-rw-r--r--lib/ssl/src/ssl_record.erl439
-rw-r--r--lib/ssl/src/ssl_record.hrl24
-rw-r--r--lib/ssl/src/ssl_v2.erl (renamed from lib/ssl/src/ssl_ssl2.erl)16
-rw-r--r--lib/ssl/src/ssl_v3.erl (renamed from lib/ssl/src/ssl_ssl3.erl)28
-rw-r--r--lib/ssl/src/tls_connection.erl249
-rw-r--r--lib/ssl/src/tls_handshake.erl1750
-rw-r--r--lib/ssl/src/tls_handshake.hrl9
-rw-r--r--lib/ssl/src/tls_record.erl536
-rw-r--r--lib/ssl/src/tls_record.hrl1
-rw-r--r--lib/ssl/src/tls_v1.erl (renamed from lib/ssl/src/ssl_tls1.erl)42
-rw-r--r--lib/ssl/test/ssl_handshake_SUITE.erl63
-rw-r--r--lib/ssl/test/ssl_npn_hello_SUITE.erl77
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/notes.xml18
-rw-r--r--lib/tools/doc/src/notes.xml8
-rw-r--r--lib/wx/doc/src/notes.xml4
67 files changed, 4037 insertions, 2616 deletions
diff --git a/lib/asn1/doc/src/asn1ct.xml b/lib/asn1/doc/src/asn1ct.xml
index f04bac9fec..23d122f69a 100644
--- a/lib/asn1/doc/src/asn1ct.xml
+++ b/lib/asn1/doc/src/asn1ct.xml
@@ -356,11 +356,11 @@ File3.asn </pre>
</item>
</list>
- <p>Schematically the following happens for each type in the module:
+ <p>Schematically the following happens for each type in the module:</p>
<code type="none">
{ok, Value} = asn1ct:value(Module, Type),
{ok, Bytes} = asn1ct:encode(Module, Type, Value),
-{ok, Value} = asn1ct:decode(Module, Type, Bytes).</code></p>
+{ok, Value} = asn1ct:decode(Module, Type, Bytes).</code>
<p>The <c>test</c> functions utilizes the <c>*.asn1db</c> files
for all included modules. If they are located in a different
diff --git a/lib/common_test/doc/src/common_test_app.xml b/lib/common_test/doc/src/common_test_app.xml
index 151159ad69..f446f8ac13 100644
--- a/lib/common_test/doc/src/common_test_app.xml
+++ b/lib/common_test/doc/src/common_test_app.xml
@@ -99,11 +99,11 @@
be executed by Common Test. A test case is represented by an atom,
the name of the test case function. A test case group is
represented by a <c>group</c> tuple, where <c>GroupName</c>,
- an atom, is the name of the group (defined in <c><seealso marker="#Module:groups-0">groups/0</seealso></c>).
+ an atom, is the name of the group (defined in <seealso marker="#Module:groups-0"><c>groups/0</c></seealso>).
Execution properties for groups may also be specified, both
for a top level group and for any of its sub-groups.
Group execution properties specified here, will override
- properties in the group definition (see <c><seealso marker="#Module:groups-0">groups/0</seealso></c>).
+ properties in the group definition (see <seealso marker="#Module:groups-0"><c>groups/0</c></seealso>).
(With value <c>default</c>, the group definition properties
will be used).</p>
@@ -186,8 +186,8 @@
test cases in the suite).</p>
<p>The <c>timetrap</c> tag sets the maximum time each
- test case is allowed to execute (including <c><seealso marker="#Module:init_per_testcase-2">init_per_testcase/2</seealso></c>
- and <c><seealso marker="#Module:end_per_testcase-2">end_per_testcase/2</seealso></c>). If the timetrap time is
+ test case is allowed to execute (including <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
+ and <seealso marker="#Module:end_per_testcase-2"><c>end_per_testcase/2</c></seealso>). If the timetrap time is
exceeded, the test case fails with reason
<c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
set a new timetrap by returning a <c>TimeVal</c>. It may also be
@@ -203,11 +203,11 @@
in any of the configuration files, all test cases are skipped. For more
information about the 'require' functionality, see the
reference manual for the function
- <c><seealso marker="ct#require-1">ct:require/1/2</seealso></c>.</p>
+ <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso>.</p>
<p>With <c>userdata</c>, it is possible for the user to
specify arbitrary test suite related information which can be
- read by calling <c><seealso marker="ct#userdata-2">ct:userdata/2</seealso></c>.</p>
+ read by calling <seealso marker="ct#userdata-2"><c>ct:userdata/2</c></seealso>.</p>
<p>The <c>ct_hooks</c> tag specifies which
<seealso marker="ct_hooks_chapter">Common Test Hooks</seealso>
@@ -266,7 +266,7 @@
<p>This function is called as the last test case in the
suite. It is meant to be used for cleaning up after
- <c><seealso marker="#Module:init_per_suite-1">init_per_suite/1</seealso></c>.
+ <seealso marker="#Module:init_per_suite-1"><c>init_per_suite/1</c></seealso>.
For information on <c>save_config</c>, please see
<seealso marker="dependencies_chapter#save_config">Dependencies
between Test Cases and Suites</seealso> in the User's Guide.</p>
@@ -313,13 +313,13 @@
return a list of tagged tuples that specify various properties
related to the execution of a test case group (i.e. its test cases
and sub-groups). Properties set by
- <c><seealso marker="#Module:group-1">group/1</seealso></c> override
+ <seealso marker="#Module:group-1"><c>group/1</c></seealso> override
properties with the same key that have been previously set by
- <c><seealso marker="#Module:suite-0">suite/0</seealso></c>.</p>
+ <seealso marker="#Module:suite-0"><c>suite/0</c></seealso>.</p>
<p>The <c>timetrap</c> tag sets the maximum time each
- test case is allowed to execute (including <c><seealso marker="#Module:init_per_testcase-2">init_per_testcase/2</seealso></c>
- and <c><seealso marker="#Module:end_per_testcase-2">end_per_testcase/2</seealso></c>). If the timetrap time is
+ test case is allowed to execute (including <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
+ and <seealso marker="#Module:end_per_testcase-2"><c>end_per_testcase/2</c></seealso>). If the timetrap time is
exceeded, the test case fails with reason
<c>timetrap_timeout</c>. A <c>TimeFunc</c> function can be used to
set a new timetrap by returning a <c>TimeVal</c>. It may also be
@@ -334,11 +334,11 @@
in any of the configuration files, all test cases in this group are skipped.
For more information about the 'require' functionality, see the
reference manual for the function
- <c><seealso marker="ct#require-1">ct:require/1/2</seealso></c>.</p>
+ <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso>.</p>
<p>With <c>userdata</c>, it is possible for the user to
specify arbitrary test case group related information which can be
- read by calling <c><seealso marker="ct#userdata-2">ct:userdata/2</seealso></c>.</p>
+ read by calling <seealso marker="ct#userdata-2"><c>ct:userdata/2</c></seealso>.</p>
<p>The <c>ct_hooks</c> tag specifies which
<seealso marker="ct_hooks_chapter">Common Test Hooks</seealso>
@@ -371,7 +371,7 @@
test case group. It typically contains initializations which are
common for all test cases and sub-groups in the group, and which
shall only be performed once. <c>GroupName</c> is the name of the
- group, as specified in the group definition (see <c><seealso marker="#Module:groups-0">groups/0</seealso></c>). The
+ group, as specified in the group definition (see <seealso marker="#Module:groups-0"><c>groups/0</c></seealso>). The
<c>Config</c> parameter is the configuration data which can be modified
here. The return value of this function is given as <c>Config</c>
to all test cases and sub-groups in the group. If <c>{skip,Reason}</c>
@@ -400,10 +400,10 @@
<p> OPTIONAL </p>
<p>This function is called after the execution of a test case group is finished.
- It is meant to be used for cleaning up after <c><seealso marker="#Module:init_per_group-2">init_per_group/2</seealso></c>.
+ It is meant to be used for cleaning up after <seealso marker="#Module:init_per_group-2"><c>init_per_group/2</c></seealso>.
By means of <c>{return_group_result,Status}</c>, it is possible to return a
status value for a nested sub-group. The status can be retrieved in
- <c><seealso marker="#Module:end_per_group-2">end_per_group/2</seealso></c> for the group on the level above. The status will also
+ <seealso marker="#Module:end_per_group-2"><c>end_per_group/2</c></seealso> for the group on the level above. The status will also
be used by Common Test for deciding if execution of a group should proceed in
case the property <c>sequence</c> or <c>repeat_until_*</c> is set.</p>
@@ -454,7 +454,7 @@
<p> OPTIONAL </p>
<p> This function is called after each test case, and can be used
- to clean up after <c><seealso marker="#Module:init_per_testcase-2">init_per_testcase/2</seealso></c> and the test case.
+ to clean up after <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso> and the test case.
Any return value (besides <c>{fail,Reason}</c> and <c>{save_config,SaveConfig}</c>)
is ignored. By returning <c>{fail,Reason}</c>, <c>TestCase</c> will be marked as
failed (even though it was actually successful in the sense that it returned
@@ -496,15 +496,15 @@
<p>This is the test case info function. It is supposed to
return a list of tagged tuples that specify various properties
related to the execution of this particular test case.
- Properties set by <c><seealso marker="#Module:Testcase-0">Testcase/0</seealso></c> override
+ Properties set by <seealso marker="#Module:Testcase-0"><c>Testcase/0</c></seealso> override
properties that have been previously set for the test case
- by <c><seealso marker="#Module:group-1">group/1</seealso></c> or <c><seealso marker="#Module:suite-0">suite/0</seealso></c>.</p>
+ by <seealso marker="#Module:group-1"><c>group/1</c></seealso> or <seealso marker="#Module:suite-0"><c>suite/0</c></seealso>.</p>
<p>The <c>timetrap</c> tag sets the maximum time the
test case is allowed to execute. If the timetrap time is
exceeded, the test case fails with reason
- <c>timetrap_timeout</c>. <c><seealso marker="#Module:init_per_testcase-2">init_per_testcase/2</seealso></c>
- and <c><seealso marker="#Module:end_per_testcase-2">end_per_testcase/2</seealso></c> are included in the
+ <c>timetrap_timeout</c>. <seealso marker="#Module:init_per_testcase-2"><c>init_per_testcase/2</c></seealso>
+ and <seealso marker="#Module:end_per_testcase-2"><c>end_per_testcase/2</c></seealso> are included in the
timetrap time. A <c>TimeFunc</c> function can be used to
set a new timetrap by returning a <c>TimeVal</c>. It may also be
used to trigger a timetrap timeout by, at some point, returning a
@@ -518,15 +518,15 @@
configuration files, the test case is skipped. For more
information about the 'require' functionality, see the
reference manual for the function
- <c><seealso marker="ct#require-1">ct:require/1/2</seealso></c>.</p>
+ <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso>.</p>
<p>If <c>timetrap</c> and/or <c>require</c> is not set, the
- default values specified by <c><seealso marker="#Module:suite-0">suite/0</seealso></c> (or
- <c><seealso marker="#Module:group-1">group/1</seealso></c>) will be used.</p>
+ default values specified by <seealso marker="#Module:suite-0"><c>suite/0</c></seealso> (or
+ <seealso marker="#Module:group-1"><c>group/1</c></seealso>) will be used.</p>
<p>With <c>userdata</c>, it is possible for the user to
specify arbitrary test case related information which can be
- read by calling <c><seealso marker="ct#userdata-3">ct:userdata/3</seealso></c>.</p>
+ read by calling <seealso marker="ct#userdata-3"><c>ct:userdata/3</c></seealso>.</p>
<p>Other tuples than the ones defined will simply be ignored.</p>
@@ -554,7 +554,7 @@
<p>This is the implementation of a test case. Here you must
call the functions you want to test, and do whatever you
need to check the result. If something fails, make sure the
- function causes a runtime error, or call <c><seealso marker="ct#fail-1">ct:fail/1/2</seealso></c>
+ function causes a runtime error, or call <seealso marker="ct#fail-1"><c>ct:fail/1/2</c></seealso>
(which also causes the test case process to terminate).</p>
<p>Elements from the <c>Config</c> list can e.g. be read
@@ -582,7 +582,7 @@
Test Cases and Suites</seealso> in the User's Guide.</p>
</desc>
</func>
-
+
</funcs>
</erlref>
diff --git a/lib/common_test/doc/src/config_file_chapter.xml b/lib/common_test/doc/src/config_file_chapter.xml
index d90adf8d7b..99e25faf27 100644
--- a/lib/common_test/doc/src/config_file_chapter.xml
+++ b/lib/common_test/doc/src/config_file_chapter.xml
@@ -80,7 +80,7 @@
test is skipped (unless a default value has been specified, see the
<seealso marker="write_test_chapter#info_function">test case info
function</seealso> chapter for details). There is also a function
- <c><seealso marker="ct#require-1">ct:require/1/2</seealso></c> which can be called from a test case
+ <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso> which can be called from a test case
in order to check if a specific variable is available. The return
value from this function must be checked explicitly and appropriate
action be taken depending on the result (e.g. to skip the test case
@@ -90,7 +90,7 @@
info-list should look like this:
<c>{require,CfgVarName}</c> or <c>{require,AliasName,CfgVarName}</c>.
The arguments <c>AliasName</c> and <c>CfgVarName</c> are the same as the
- arguments to <c><seealso marker="ct#require-1">ct:require/1/2</seealso></c> which are described in the
+ arguments to <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso> which are described in the
reference manual for <seealso marker="ct">ct</seealso>.
<c>AliasName</c> becomes an alias for the configuration variable,
and can be used as reference to the configuration data value.
@@ -103,7 +103,7 @@
(or test case) and improve readability.</item>
</list>
<p>To read the value of a config variable, use the function
- <c><seealso marker="ct#get_config-1">get_config/1/2/3</seealso></c>
+ <seealso marker="ct#get_config-1"><c>get_config/1/2/3</c></seealso>
which is also described in the reference
manual for <seealso marker="ct">ct</seealso>.</p>
<p>Example:</p>
@@ -121,7 +121,7 @@
<section>
<title>Using configuration variables defined in multiple files</title>
<p>If a configuration variable is defined in multiple files and you
- want to access all possible values, you may use the <c><seealso marker="ct#get_config-3">ct:get_config/3</seealso></c>
+ want to access all possible values, you may use the <seealso marker="ct#get_config-3"><c>ct:get_config/3</c></seealso>
function and specify <c>all</c> in the options list. The values will then
be returned in a list and the order of the elements corresponds to the order
that the config files were specified at startup. Please see
@@ -133,7 +133,7 @@
<marker id="encrypted_config_files"></marker>
<p>It is possible to encrypt configuration files containing sensitive data
if these files must be stored in open and shared directories.</p>
- <p>Call <c><seealso marker="ct#encrypt_config_file-2">ct:encrypt_config_file/2/3</seealso></c> to have Common Test encrypt a
+ <p>Call <seealso marker="ct#encrypt_config_file-2"><c>ct:encrypt_config_file/2/3</c></seealso> to have Common Test encrypt a
specified file using the DES3 function in the OTP <c>crypto</c> application.
The encrypted file can then be used as a regular configuration file,
in combination with other encrypted files or normal text files. The key
@@ -142,7 +142,7 @@
<c>decrypt_file</c> flag/option, or a key file in a predefined location.</p>
<p>Common Test also provides decryption functions,
- <c><seealso marker="ct#decrypt_config_file-2">ct:decrypt_config_file/2/3</seealso></c>, for recreating the original text
+ <seealso marker="ct#decrypt_config_file-2"><c>ct:decrypt_config_file/2/3</c></seealso>, for recreating the original text
files.</p>
<p>Please see the <seealso marker="ct">ct</seealso> reference manual for
@@ -152,8 +152,8 @@
<section>
<title>Opening connections by using configuration data</title>
<p>There are two different methods for opening a connection
- by means of the support functions in e.g. <c><seealso marker="ct_ssh">ct_ssh</seealso></c>, <c><seealso marker="ct_ftp">ct_ftp</seealso></c>,
- and <c><seealso marker="ct_telnet">ct_telnet</seealso></c>:</p>
+ by means of the support functions in e.g. <seealso marker="ct_ssh"><c>ct_ssh</c></seealso>, <seealso marker="ct_ftp"><c>ct_ftp</c></seealso>,
+ and <seealso marker="ct_telnet"><c>ct_telnet</c></seealso>:</p>
<list>
<item>Using a configuration target name (an alias) as reference.</item>
<item>Using the configuration variable as reference.</item>
diff --git a/lib/common_test/doc/src/cover_chapter.xml b/lib/common_test/doc/src/cover_chapter.xml
index 736486350b..b952df58f1 100644
--- a/lib/common_test/doc/src/cover_chapter.xml
+++ b/lib/common_test/doc/src/cover_chapter.xml
@@ -94,7 +94,7 @@
<p><c>$ ct_run -dir $TESTOBJS/db -cover $TESTOBJS/db/config/db.coverspec</c></p>
<p>You may also pass the cover specification file name in a
- call to <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>, by adding a <c>{cover,CoverSpec}</c>
+ call to <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>, by adding a <c>{cover,CoverSpec}</c>
tuple to the <c>Opts</c> argument. Also, you can of course
enable code coverage in your test specifications (read
more in the chapter about
@@ -102,8 +102,8 @@
specifications</seealso>).</p>
</section>
- <marker id="cover_stop"></marker>
<section>
+ <marker id="cover_stop"></marker>
<title>Stopping the cover tool when tests are completed</title>
<p>By default the Cover tool is automatically stopped when the
tests are completed. This causes the original (non cover
@@ -120,8 +120,8 @@
<p>The option can be set by using the <c>-cover_stop</c> flag with
<c>ct_run</c>, by adding <c>{cover_stop,true|false}</c> to the
- Opts argument to <c><seealso
- marker="ct#run_test-1">ct:run_test/1</seealso></c>, or by adding
+ Opts argument to <seealso
+ marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>, or by adding
a <c>cover_stop</c> term in your test specification (see chapter
about <seealso
marker="run_test_chapter#test_specifications">test
@@ -189,8 +189,8 @@
specification file for Common Test).</p>
</section>
- <marker id="cross_cover"/>
<section>
+ <marker id="cross_cover"/>
<title>Cross cover analysis</title>
<p>The cross cover mechanism allows cover analysis of modules
across multiple tests. It is useful if some code, e.g. a library
diff --git a/lib/common_test/doc/src/ct_hooks_chapter.xml b/lib/common_test/doc/src/ct_hooks_chapter.xml
index fe871eb516..60cd9be918 100644
--- a/lib/common_test/doc/src/ct_hooks_chapter.xml
+++ b/lib/common_test/doc/src/ct_hooks_chapter.xml
@@ -192,12 +192,12 @@
<section>
<title>External configuration data and Logging</title>
<p>It's possible in the CTH to read configuration data values
- by calling <c><seealso marker="ct#get_config-1">ct:get_config/1/2/3</seealso></c> (as explained in the
+ by calling <seealso marker="ct#get_config-1"><c>ct:get_config/1/2/3</c></seealso> (as explained in the
<seealso marker="config_file_chapter#require_config_data">
External configuration data</seealso>
chapter). The config variables in question must, as always, first have been
<c>required</c> by means of a suite-, group-, or test case info function,
- or the <c><seealso marker="ct#require-1">ct:require/1/2</seealso></c> function. Note that the latter can also be used
+ or the <seealso marker="ct#require-1"><c>ct:require/1/2</c></seealso> function. Note that the latter can also be used
in CT hook functions.</p>
<p>The CT hook functions may call any of the logging functions available
in the <c>ct</c> interface to print information to the log files, or to
@@ -252,13 +252,13 @@
{ok, Handle} -&gt;
{[{db_handle, Handle} | Config], CTHState#state{ handle = Handle }}
end.</code>
- <note>If using multiple CTHs, the first part of the return tuple will be
+ <note><p>If using multiple CTHs, the first part of the return tuple will be
used as input for the next CTH. So in the case above the next CTH might
get <c>{fail,Reason}</c> as the second parameter. If you have many CTHs
which interact, it might be a good idea to not let each CTH return
<c>fail</c> or <c>skip</c>. Instead return that an action should be taken
through the <c>Config</c> list and implement a CTH which at the end takes
- the correct action. </note>
+ the correct action.</p></note>
</section>
@@ -301,9 +301,9 @@ post_end_per_testcase(_TC, Config, Return, CTHState) -&gt;
%% Do nothing if tc does not crash.
{Return, CTHState}.</code>
- <note>Recovering from a testcase failure using CTHs should only be done as
+ <note><p>Recovering from a testcase failure using CTHs should only be done as
a last resort. If used wrongly it could become very difficult to
- determine which tests pass or fail in a test run</note>
+ determine which tests pass or fail in a test run</p></note>
</section>
@@ -322,6 +322,34 @@ post_end_per_testcase(_TC, Config, Return, CTHState) -&gt;
</section>
+ <marker id="synchronizing"/>
+ <section>
+ <title>Synchronizing external user applications with Common Test</title>
+ <p>CTHs can be used to synchronize test runs with external user applications.
+ The init function may e.g. start and/or communicate with an application that
+ has the purpose of preparing the SUT for an upcoming test run, or maybe
+ initialize a database for saving test data to during the test run. The
+ terminate function may similarly order such an application to reset the SUT
+ after the test run, and/or tell the application to finish active sessions
+ and terminate.
+ Any system error- or progress reports generated during the init- or
+ termination stage will be saved in the
+ <seealso marker="run_test_chapter#pre_post_test_io_log">Pre-
+ and post test I/O log</seealso>. (This is also true for any printouts made
+ with <c>ct:log/2</c> and <c>ct:pal/2</c>).</p>
+ <p>In order to ensure that Common Test doesn't start executing tests, or
+ closes its log files and shuts down, before the external application
+ is ready for it, Common Test may be synchronized with the application.
+ During startup and shutdown, Common Test can be suspended, simply by
+ having a CTH evaluate a <c>receive</c> expression in the init- or terminate
+ function. The macros <c>?CT_HOOK_INIT_PROCESS</c> (the process executing the hook
+ init function) and <c>?CT_HOOK_TERMINATE_PROCESS</c> (the process executing
+ the hook terminate function), each specifies the name of the correct Common Test
+ process to send a message to in order to return from the <c>receive</c>.
+ These macros are defined in <c>ct.hrl</c>.
+ </p>
+ </section>
+
<marker id="example"/>
<section>
<title>Example CTH</title>
diff --git a/lib/common_test/doc/src/ct_master_chapter.xml b/lib/common_test/doc/src/ct_master_chapter.xml
index 9e848e99bb..1d2d64a166 100644
--- a/lib/common_test/doc/src/ct_master_chapter.xml
+++ b/lib/common_test/doc/src/ct_master_chapter.xml
@@ -220,6 +220,7 @@
<p>The default <seealso marker="ct_slave">ct_slave</seealso> callback module,
which is part of the Common Test application, has the following features:
+ </p>
<list>
<item>Starting Erlang target nodes on local or remote hosts
(ssh is used for communication).
@@ -237,7 +238,6 @@
Functions can be given as a list of {Module, Function, Arguments} tuples.
</item>
</list>
- </p>
<p>Note that it is possible to specify an <c>eval</c> term for the node as well
as <c>startup_functions</c> in the <c>node_start</c> options list. In this
case first the node will be started, then the <c>startup_functions</c> are
diff --git a/lib/common_test/doc/src/ct_run.xml b/lib/common_test/doc/src/ct_run.xml
index c87c765ae7..d857b20d88 100644
--- a/lib/common_test/doc/src/ct_run.xml
+++ b/lib/common_test/doc/src/ct_run.xml
@@ -36,8 +36,6 @@
OS command line.
</comsummary>
- <marker id="top"></marker>
-
<description>
<p>The <c>ct_run</c> program is automatically installed with Erlang/OTP
and Common Test (please see the Installation chapter in the Common
@@ -48,7 +46,7 @@
particular mode.</p>
<p>There is an interface function that corresponds to this program,
- called <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>, for starting Common Test from the Erlang
+ called <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>, for starting Common Test from the Erlang
shell (or an Erlang program). Please see the <c>ct</c> man page for
details.</p>
@@ -83,9 +81,9 @@
<p>it prints all valid start flags to stdout.</p>
</description>
- <marker id="ct_run"></marker>
<section>
+ <marker id="ct_run"></marker>
<title>Run tests from command line</title>
<pre>
ct_run [-dir TestDir1 TestDir2 .. TestDirN] |
diff --git a/lib/common_test/doc/src/event_handler_chapter.xml b/lib/common_test/doc/src/event_handler_chapter.xml
index 10a9b52d39..3cc21f28de 100644
--- a/lib/common_test/doc/src/event_handler_chapter.xml
+++ b/lib/common_test/doc/src/event_handler_chapter.xml
@@ -64,7 +64,7 @@
<marker id="usage"></marker>
<title>Usage</title>
<p>Event handlers may be installed by means of an <c>event_handler</c>
- start flag (<c>ct_run</c>) or option (<c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>), where the
+ start flag (<c>ct_run</c>) or option (<seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>), where the
argument specifies the names of one or more event handler modules.
Example:</p>
<p><c>$ ct_run -suite test/my_SUITE -event_handler handlers/my_evh1
@@ -78,7 +78,7 @@
example).</p>
<p>An event_handler tuple in the argument <c>Opts</c> has the following
- definition (see also <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c> in the reference manual):</p>
+ definition (see also <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> in the reference manual):</p>
<pre>
{event_handler,EventHandlers}
@@ -224,8 +224,9 @@
<c>end_per_testcase</c> for the case failed.
</p></item>
+ <item>
<marker id="tc_auto_skip"></marker>
- <item><c>#event{name = tc_auto_skip, data = {Suite,Func,Reason}}</c>
+ <c>#event{name = tc_auto_skip, data = {Suite,Func,Reason}}</c>
<p><c>Suite = atom()</c>, the name of the suite.</p>
<p><c>Func = atom()</c>, the name of the test case or configuration function.</p>
<p><c>Reason = {failed,FailReason} |
@@ -251,8 +252,9 @@
the <c>tc_done</c> event.
</p></item>
+ <item>
<marker id="tc_user_skip"></marker>
- <item><c>#event{name = tc_user_skip, data = {Suite,TestCase,Comment}}</c>
+ <c>#event{name = tc_user_skip, data = {Suite,TestCase,Comment}}</c>
<p><c>Suite = atom()</c>, name of the suite.</p>
<p><c>TestCase = atom()</c>, name of the test case.</p>
<p><c>Comment = string()</c>, reason for skipping the test case.</p>
@@ -308,7 +310,7 @@
manager can look like.</p>
<note><p>To ensure that printouts to standard out (or printouts made with
- <c><seealso marker="ct#log-2">ct:log/2/3</seealso></c> or <c><seealso marker="ct:pal-2">ct:pal/2/3</seealso></c>) get written to the test case log
+ <seealso marker="ct#log-2"><c>ct:log/2/3</c></seealso> or <seealso marker="ct:pal-2"><c>ct:pal/2/3</c></seealso>) get written to the test case log
file, and not to the Common Test framework log, you can syncronize
with the Common Test server by matching on the <c>tc_start</c> and <c>tc_done</c>
events. In the period between these events, all IO gets directed to the
diff --git a/lib/common_test/doc/src/getting_started_chapter.xml b/lib/common_test/doc/src/getting_started_chapter.xml
index 3cf04bb1a2..0b42445540 100644
--- a/lib/common_test/doc/src/getting_started_chapter.xml
+++ b/lib/common_test/doc/src/getting_started_chapter.xml
@@ -61,13 +61,11 @@
<title>Test case execution</title>
<p>Execution of test cases is handled this way:</p>
- <p>
<image file="tc_execution.gif">
<icaption>
Successful vs unsuccessful test case execution.
</icaption>
</image>
- </p>
<p>For each test case that Common Test is told to execute, it spawns a
dedicated process on which the test case function in question starts
@@ -90,7 +88,7 @@
<p>As you can understand from the illustration above, Common Test requires
that a test case generates a runtime error to indicate failure (e.g.
by causing a bad match error or by calling <c>exit/1</c>, preferrably
- through the <c><seealso marker="ct#fail-1">ct:fail/1,2</seealso></c> help function). A succesful execution is
+ through the <seealso marker="ct#fail-1"><c>ct:fail/1,2</c></seealso> help function). A succesful execution is
indicated by means of a normal return from the test case function.
</p>
</section>
@@ -100,13 +98,15 @@
<p>As you've seen in the basics chapter, the test suite module implements
<seealso marker="common_test">callback functions</seealso>
(mandatory or optional) for various purposes, e.g:
+ </p>
<list>
<item>Init/end configuration function for the test suite</item>
<item>Init/end configuration function for a test case</item>
<item>Init/end configuration function for a test case group</item>
<item>Test cases</item>
</list>
- The configuration functions are optional and if you don't need them for
+ <p>
+ The configuration functions are optional and if you don't need them for
your test, a test suite with one simple test case could look like this:
</p>
<pre>
@@ -136,13 +136,11 @@
"lower level"). The data flow looks like this:
</p>
- <p>
<image file="config.gif">
<icaption>
Config data flow in the suite.
</icaption>
</image>
- </p>
<p>
Here's an example of a test suite which uses configuration functions
@@ -203,13 +201,11 @@
shows the log file structure:
</p>
- <p>
<image file="html_logs.gif">
<icaption>
HTML log file structure.
</icaption>
</image>
- </p>
</section>
<section>
diff --git a/lib/common_test/doc/src/install_chapter.xml b/lib/common_test/doc/src/install_chapter.xml
index 89c497962d..4ef4e6de94 100644
--- a/lib/common_test/doc/src/install_chapter.xml
+++ b/lib/common_test/doc/src/install_chapter.xml
@@ -56,13 +56,13 @@
shell script version run_test, however, this script needs to be
generated first, according to the instructions below.</p>
- <p><note>Before reading on, please note that since Common Test version
+ <note><p>Before reading on, please note that since Common Test version
1.5, the run_test shell script is no longer required for starting
tests with Common Test from the OS command line. The ct_run
program (descibed above) is the new recommended command line interface
for Common Test. The shell script exists mainly for legacy reasons and
may not be updated in future releases of Common Test. It may even be removed.
- </note></p>
+ </p></note>
<p>Optional step to generate a shell script for starting Common Test:</p>
<p>To generate the run_test shell script, navigate to the
diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml
index afaed29626..6b37e183dd 100644
--- a/lib/common_test/doc/src/run_test_chapter.xml
+++ b/lib/common_test/doc/src/run_test_chapter.xml
@@ -21,7 +21,7 @@
</legalnotice>
- <title>Running Tests</title>
+ <title>Running Tests and Analyzing Results</title>
<prepared>Peter Andersson, Kenneth Lundin</prepared>
<docno></docno>
<date></date>
@@ -105,8 +105,8 @@
RPC from a remote node.</p>
</section>
- <marker id="ct_run"></marker>
<section>
+ <marker id="ct_run"></marker>
<title>Running tests from the OS command line</title>
<p>The <c>ct_run</c> program can be used for running tests from
@@ -225,15 +225,15 @@
<p>Common Test provides an Erlang API for running tests. The main (and most
flexible) function for specifying and executing tests is called
- <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>.
+ <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>.
This function takes the same start parameters as
- the <c><seealso marker="run_test_chapter#ct_run">ct_run</seealso></c>
+ the <seealso marker="run_test_chapter#ct_run"><c>ct_run</c></seealso>
program described above, only the flags are instead
given as options in a list of key-value tuples. E.g. a test specified
with <c>ct_run</c> like:</p>
<p><c>$ ct_run -suite ./my_SUITE -logdir ./results</c></p>
- <p>is with <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c> specified as:</p>
+ <p>is with <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> specified as:</p>
<p><c>1> ct:run_test([{suite,"./my_SUITE"},{logdir,"./results"}]).</c></p>
<p>The function returns the test result, represented by the tuple:
@@ -245,7 +245,7 @@
<section>
<title>Releasing the Erlang shell</title>
<p>During execution of tests, started with
- <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>,
+ <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>,
the Erlang shell process, controlling stdin, will remain the top
level process of the Common Test system of processes. The result
is that the Erlang shell is not available for interaction during
@@ -260,19 +260,19 @@
<c>ct:run_test/1</c> returns the pid of this process rather than the
test result - which instead is printed to tty at the end of the test run.</p>
<note><p>Note that in order to use the
- <c><seealso marker="ct#break-1">ct:break/1/2</seealso></c> and
- <c><seealso marker="ct#continue-0">ct:continue/0/1</seealso></c> functions,
+ <seealso marker="ct#break-1"><c>ct:break/1/2</c></seealso> and
+ <seealso marker="ct#continue-0"><c>ct:continue/0/1</c></seealso> functions,
<c>release_shell</c> <em>must</em> be set to <c>true</c>.</p></note>
</section>
<p>For detailed documentation about
- <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>,
+ <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>,
please see the
- <c><seealso marker="ct#run_test-1">ct</seealso></c> manual page.</p>
+ <seealso marker="ct#run_test-1"><c>ct</c></seealso> manual page.</p>
</section>
- <marker id="group_execution"></marker>
<section>
+ <marker id="group_execution"></marker>
<title>Test case group execution</title>
<p>With the <c>ct_run</c> flag, or <c>ct:run_test/1</c> option <c>group</c>,
@@ -442,9 +442,9 @@
for trying out various operations during test suite development.</p>
<p>To invoke the interactive shell mode, you can start an Erlang shell
- manually and call <c><seealso marker="ct#install-1">ct:install/1</seealso></c> to install any configuration
+ manually and call <seealso marker="ct#install-1"><c>ct:install/1</c></seealso> to install any configuration
data you might need (use <c>[]</c> as argument otherwise), then
- call <c><seealso marker="ct#start_interactive-0">ct:start_interactive/0</seealso></c> to start Common Test. If you use
+ call <seealso marker="ct#start_interactive-0"><c>ct:start_interactive/0</c></seealso> to start Common Test. If you use
the <c>ct_run</c> program, you may start the Erlang shell and Common Test
in the same go by using the <c>-shell</c> and, optionally, the <c>-config</c>
and/or <c>-userconfig</c> flag. Examples:
@@ -463,8 +463,8 @@
<p>If any functions using "required config data" (e.g. ct_telnet or
ct_ftp functions) are to be called from the erlang shell, config
- data must first be required with <c><seealso marker="ct#require-1">
- ct:require/1/2</seealso></c>. This is
+ data must first be required with <seealso marker="ct#require-1"><c>
+ ct:require/1/2</c></seealso>. This is
equivalent to a <c>require</c> statement in the <seealso
marker="write_test_chapter#suite">Test Suite Info
Function</seealso> or in the <seealso
@@ -491,11 +491,11 @@
is not supported.</p>
<p>If you wish to exit the interactive mode (e.g. to start an
- automated test run with <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>), call the function
- <c><seealso marker="ct#stop_interactive-0">ct:stop_interactive/0</seealso></c>. This shuts down the
+ automated test run with <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>), call the function
+ <seealso marker="ct#stop_interactive-0"><c>ct:stop_interactive/0</c></seealso>. This shuts down the
running <c>ct</c> application. Associations between
configuration names and data created with <c>require</c> are
- consequently deleted. <c><seealso marker="ct#start_interactive-0">ct:start_interactive/0</seealso></c> will get you
+ consequently deleted. <seealso marker="ct#start_interactive-0"><c>ct:start_interactive/0</c></seealso> will get you
back into interactive mode, but the previous state is not restored.</p>
</section>
@@ -503,7 +503,7 @@
<title>Step by step execution of test cases with the Erlang Debugger</title>
<p>By means of <c>ct_run -step [opts]</c>, or by passing the
- <c>{step,Opts}</c> option to <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>, it is possible
+ <c>{step,Opts}</c> option to <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>, it is possible
to get the Erlang Debugger started automatically and use its
graphical interface to investigate the state of the current test
case and to execute it step by step and/or set execution breakpoints.</p>
@@ -527,17 +527,17 @@
with <c>dir</c>.</p>
</section>
- <marker id="test_specifications"></marker>
<section>
+ <marker id="test_specifications"></marker>
<title>Test Specifications</title>
<section>
<title>General description</title>
<p>The most flexible way to specify what to test, is to use a so
called test specification. A test specification is a sequence of
Erlang terms. The terms are normally declared in one or more text files
- (see <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c>), but
+ (see <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso>), but
may also be passed to Common Test on the form of a list (see
- <c><seealso marker="ct#run_testspec-1">ct:run_testspec/1</seealso></c>).
+ <seealso marker="ct#run_testspec-1"><c>ct:run_testspec/1</c></seealso>).
There are two general types of terms: configuration terms and test
specification terms.</p>
<p>With configuration terms it is possible to e.g. label the test
@@ -989,7 +989,7 @@
<c>ct_run</c>. This forces Common Test to ignore unrecognizable terms.
Note that in this mode, Common Test is not able to check the specification
for errors as efficiently as if the scanner runs in default mode.
- If <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c> is used
+ If <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> is used
for starting the tests, the relaxed scanner
mode is enabled by means of the tuple: <c>{allow_user_terms,true}</c></p>
</section>
@@ -999,7 +999,7 @@
<title>Running tests from the Web based GUI</title>
<p>The web based GUI, VTS, is started with the
- <c><seealso marker="run_test_chapter#ct_run">ct_run</seealso></c>
+ <seealso marker="run_test_chapter#ct_run"><c>ct_run</c></seealso>
program. From the GUI you can load config files, and select
directories, suites and cases to run. You can also state the
config files, directories, suites and cases on the command line
@@ -1087,18 +1087,18 @@
</list>
<p>On the test run index page there is a link to the Common Test
- Framework log file in which information about imported
- configuration data and general test progress is written. This
- log file is useful to get snapshot information about the test
- run during execution. It can also be very helpful when
- analyzing test results or debugging test suites.</p>
+ Framework Log file in which information about imported
+ configuration data and general test progress is written. This
+ log file is useful to get snapshot information about the test
+ run during execution. It can also be very helpful when
+ analyzing test results or debugging test suites.</p>
<p>On the test run index page it is noted if a test has missing
suites (i.e. suites that Common Test has failed to
compile). Names of the missing suites can be found in the
- Common Test Framework log file.</p>
+ Common Test Framework Log file.</p>
- <p>The major logfile shows a detailed report of the test run. It
+ <p>The major log file shows a detailed report of the test run. It
includes test suite and test case names, execution time, the
exact reason for failures etc. The information is available in both
a file with textual and with HTML representation. The HTML file shows a
@@ -1172,6 +1172,40 @@
<url href="http://tablesorter.com">tablesorter</url> plugin, with customized sorting
functions, for this implementation.</p>
</section>
+
+ <section>
+ <title>The Unexpected I/O Log</title>
+ <p>On the test suites overview page you find a link to the Unexpected I/O Log.
+ In this log, Common Test saves printouts made with
+ <c>ct:log/2</c> and <c>ct:pal/2</c>, as well as captured system error- and
+ progress reports, that cannot be associated with particular test cases and
+ therefore cannot be written to individual test case log files. This happens e.g.
+ if a log printout is made from an external process (not a test case process),
+ or if an error- or progress report comes in, during a short interval while Common
+ Test is not executing a test case or configuration function, <em>or</em> while
+ Common Test is currently executing a parallell test case group.</p>
+ </section>
+
+ <section>
+ <marker id="pre_post_test_io_log"></marker>
+ <title>The Pre- and Post Test I/O Log</title>
+ <p>On the Common Test Framework Log page you find links to the so called
+ Pre- and Post Test I/O Log. In this log, Common Test saves printouts made with
+ <c>ct:log/2</c> and <c>ct:pal/2</c>, as well as captured system error-
+ and progress reports, that take place before - and after - the actual test run.
+ Examples of this are printouts from a CT hook init- or terminate function, or
+ progress reports generated when an OTP application is started from a CT hook
+ init function. Another example is an error report generated due to
+ a failure when an external application is stopped from a CT hook terminate function.
+ All information in these examples ends up in the Pre- and Post Test I/O Log.
+ For more information on how to synchronize test runs with external user
+ applications, please see the
+ <seealso marker="ct_hooks_chapter#synchronizing">Synchronizing</seealso>
+ section in the Common Test Hooks chapter.</p>
+ <p>Note that logging to file with <c>ct:log/2</c> or <c>ct:pal/2</c>
+ only works when Common Test is running. Printouts with <c>ct:pal/2</c>
+ are however always displayed on screen.</p>
+ </section>
</section>
<section>
diff --git a/lib/common_test/doc/src/write_test_chapter.xml b/lib/common_test/doc/src/write_test_chapter.xml
index cc8d913994..a33b22ac39 100644
--- a/lib/common_test/doc/src/write_test_chapter.xml
+++ b/lib/common_test/doc/src/write_test_chapter.xml
@@ -129,8 +129,8 @@
</p>
</section>
- <marker id="per_testcase"/>
<section>
+ <marker id="per_testcase"/>
<title>Init and end per test case</title>
<p>Each test suite module can contain the optional configuration functions
@@ -173,7 +173,7 @@
</p>
<p>The <c>end_per_testcase/2</c> function is called even after a
- test case terminates due to a call to <c><seealso marker="ct#abort_current_testcase-1">ct:abort_current_testcase/1</seealso></c>,
+ test case terminates due to a call to <seealso marker="ct#abort_current_testcase-1"><c>ct:abort_current_testcase/1</c></seealso>,
or after a timetrap timeout. However, <c>end_per_testcase</c>
will then execute on a different process than the test case
function, and in this situation, <c>end_per_testcase</c> will
@@ -243,8 +243,8 @@
<note><p>The test case function argument <c>Config</c> should not be
confused with the information that can be retrieved from
- configuration files (using <c><seealso marker="ct#get_config-1">
- ct:get_config/1/2</seealso></c>). The Config argument
+ configuration files (using <seealso marker="ct#get_config-1"><c>
+ ct:get_config/1/2</c></seealso>). The Config argument
should be used for runtime configuration of the test suite and the
test cases, while configuration files should typically contain data
related to the SUT. These two types of configuration data are handled
@@ -303,7 +303,7 @@
<item>
<p>
Use this to specify arbitrary data related to the testcase. This
- data can be retrieved at any time using the <c><seealso marker="ct#userdata-3">ct:userdata/3</seealso></c>
+ data can be retrieved at any time using the <seealso marker="ct#userdata-3"><c>ct:userdata/3</c></seealso>
utility function.
</p>
</item>
@@ -348,8 +348,8 @@
</taglist>
<p>See the <seealso marker="config_file_chapter#require_config_data">Config files</seealso>
- chapter and the <c><seealso marker="ct#require-1">
- ct:require/1/2</seealso></c> function in the
+ chapter and the <seealso marker="ct#require-1"><c>
+ ct:require/1/2</c></seealso> function in the
<seealso marker="ct">ct</seealso> reference manual for more information about
<c>require</c>.</p>
@@ -826,14 +826,16 @@
Common Test to create one dedicated private directory per
test case and execution instead. This is accomplished by means of
the flag/option: <c>create_priv_dir</c> (to be used with the
- <c>ct_run</c> program, the <c><seealso marker="ct#run_test-1">ct:run_test/1</seealso></c> function, or
+ <c>ct_run</c> program, the <seealso marker="ct#run_test-1"><c>ct:run_test/1</c></seealso> function, or
as test specification term). There are three possible values
for this option:
+ </p>
<list>
<item><c>auto_per_run</c></item>
<item><c>auto_per_tc</c></item>
<item><c>manual_per_tc</c></item>
</list>
+ <p>
The first value indicates the default priv_dir behaviour, i.e.
one private directory created per test run. The two latter
values tell Common Test to generate a unique test directory name
@@ -842,7 +844,7 @@
become very inefficient for test runs with many test cases and/or
repetitions. Therefore, in case the manual version is instead used, the
test case must tell Common Test to create priv_dir when it needs it.
- It does this by calling the function <c><seealso marker="ct#make_priv_dir-0">ct:make_priv_dir/0</seealso></c>.
+ It does this by calling the function <seealso marker="ct#make_priv_dir-0"><c>ct:make_priv_dir/0</c></seealso>.
</p>
<note><p>You should not depend on current working directory for
@@ -890,7 +892,7 @@
<p>It is also possible to dynamically set/reset a timetrap during the
excution of a test case, or configuration function. This is done by calling
- <c><seealso marker="ct#timetrap-1">ct:timetrap/1</seealso></c>. This function cancels the current timetrap
+ <seealso marker="ct#timetrap-1"><c>ct:timetrap/1</c></seealso>. This function cancels the current timetrap
and starts a new one (that stays active until timeout, or end of the
current function).</p>
@@ -903,12 +905,12 @@
<p>If a test case needs to suspend itself for a time that also gets
multipled by <c>multiply_timetraps</c> (and possibly also scaled up if
- <c>scale_timetraps</c> is enabled), the function <c><seealso marker="ct#sleep-1">ct:sleep/1</seealso></c>
+ <c>scale_timetraps</c> is enabled), the function <seealso marker="ct#sleep-1"><c>ct:sleep/1</c></seealso>
may be used (instead of e.g. <c>timer:sleep/1</c>).</p>
<p>A function (<c>fun/0</c> or <c>MFA</c>) may be specified as
timetrap value in the suite-, group- and test case info function, as
- well as argument to the <c><seealso marker="ct#timetrap-1">ct:timetrap/1</seealso></c> function. Examples:</p>
+ well as argument to the <seealso marker="ct#timetrap-1"><c>ct:timetrap/1</c></seealso> function. Examples:</p>
<p><c>{timetrap,{my_test_utils,timetrap,[?MODULE,system_start]}}</c></p>
<p><c>ct:timetrap(fun() -> my_timetrap(TestCaseName, Config) end)</c></p>
diff --git a/lib/common_test/include/ct.hrl b/lib/common_test/include/ct.hrl
index bde2709ad1..44cc33f01e 100644
--- a/lib/common_test/include/ct.hrl
+++ b/lib/common_test/include/ct.hrl
@@ -32,3 +32,7 @@
-define(STD_VERBOSITY, 50 ).
-define(HI_VERBOSITY, 75 ).
-define(MAX_VERBOSITY, 100).
+
+%% name of process executing the CT Hook init and terminate function
+-define(CT_HOOK_INIT_PROCESS, ct_util_server).
+-define(CT_HOOK_TERMINATE_PROCESS, ct_util_server).
diff --git a/lib/common_test/src/cth_log_redirect.erl b/lib/common_test/src/cth_log_redirect.erl
index 11af1aa346..4ee7e48a67 100644
--- a/lib/common_test/src/cth_log_redirect.erl
+++ b/lib/common_test/src/cth_log_redirect.erl
@@ -135,8 +135,19 @@ handle_event(Event, #eh_state{log_func = LogFunc} = State) ->
end,
{ok, State}.
+handle_info({'EXIT',User,killed}, State) ->
+ case whereis(user) of
+ %% init:stop/1/2 has been called, let's finish!
+ undefined ->
+ remove_handler;
+ User ->
+ remove_handler;
+ _ ->
+ {ok,State}
+ end;
-handle_info(_,State) -> {ok, State}.
+handle_info(_, State) ->
+ {ok,State}.
handle_call(flush,State) ->
{ok, ok, State};
diff --git a/lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl b/lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl
index a9ea7b14dd..c8c08a5735 100644
--- a/lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl
+++ b/lib/common_test/test/ct_pre_post_test_io_SUITE_data/cth_ctrl.erl
@@ -21,6 +21,8 @@
-export([proceed/0,
init/2, terminate/1]).
+-include_lib("common_test/include/ct.hrl").
+
%%%===================================================================
%%% API
%%%===================================================================
@@ -39,12 +41,12 @@ init(_Id, _Opts) ->
ok
end,
WhoAmI = self(),
+ WhoAmI = whereis(?CT_HOOK_INIT_PROCESS),
DispPid = spawn_link(fun() -> dispatcher(WhoAmI) end),
register(?MODULE, DispPid),
- io:format(user,
- "~n~n+++ Startup of ~w on ~p finished, "
- "call ~w:proceed() to run tests...~n",
- [?MODULE,node(),?MODULE]),
+ ct:pal("~n~n+++ Startup of ~w on ~p finished, "
+ "call ~w:proceed() to run tests...~n",
+ [?MODULE,node(),?MODULE]),
start_external_logger(cth_logger),
receive
{?MODULE,proceed} -> ok
@@ -55,9 +57,10 @@ init(_Id, _Opts) ->
{ok,[],ct_last}.
terminate(_State) ->
- io:format(user,
- "~n~n+++ Tests finished, call ~w:proceed() to shut down...~n",
- [?MODULE]),
+ WhoAmI = whereis(?CT_HOOK_TERMINATE_PROCESS),
+ WhoAmI = self(),
+ ct:pal("~n~n+++ Tests finished, call ~w:proceed() to shut down...~n",
+ [?MODULE]),
receive
{?MODULE,proceed} -> ok
after
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 33b32a3dce..837fcf4368 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -96,7 +96,7 @@
<item>
<p>
Forbid multiple values in Core Erlang sequence arguments.
- Thanks to Jos� Valim and Anthony Ramine.</p>
+ Thanks to José Valim and Anthony Ramine.</p>
<p>
Own Id: OTP-10818</p>
</item>
@@ -195,7 +195,7 @@
<p>
<c>compile:forms/2</c> will now use a
{source,SourceFilePath} to set the source returned by
- <c>module_info(compile)</c> (Thanks to Jos� Valim)</p>
+ <c>module_info(compile)</c> (Thanks to José Valim)</p>
<p>
Own Id: OTP-10150</p>
</item>
@@ -307,7 +307,7 @@
<item>
<p>
Fix typo in `compile' doc: unmatched parenthesis (Thanks
- to Ricardo Catalinas Jim�nez)</p>
+ to Ricardo Catalinas Jiménez)</p>
<p>
Own Id: OTP-9919</p>
</item>
@@ -808,7 +808,7 @@
(Thanks to Paul Fisher.)</p>
<p>Using filter expressions containing <c>andalso</c> or
<c>orelse</c> in a list comprehension could cause a
- compiler crash. (Thanks to Martin Engstr�m.)</p>
+ compiler crash. (Thanks to Martin Engström.)</p>
<p>
Own Id: OTP-8054</p>
</item>
diff --git a/lib/compiler/src/core_lint.erl b/lib/compiler/src/core_lint.erl
index e5d5fa2bcd..67d37ff1fc 100644
--- a/lib/compiler/src/core_lint.erl
+++ b/lib/compiler/src/core_lint.erl
@@ -68,7 +68,7 @@
| {'undefined_function', fa(), fa()}
| {'tail_segment_not_at_end', fa()}.
--type error() :: {module(), err_desc()}.
+-type error() :: {'none', module(), err_desc()}.
-type warning() :: {module(), term()}.
%%-----------------------------------------------------------------------
diff --git a/lib/debugger/doc/src/notes.xml b/lib/debugger/doc/src/notes.xml
index aef7ef8619..5f1d411db2 100644
--- a/lib/debugger/doc/src/notes.xml
+++ b/lib/debugger/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -84,7 +84,7 @@
<item>
<p>
Fix Debugger settings dialog due to changed behavior in
- wxFileDialog (Thanks to H�kan Mattsson)</p>
+ wxFileDialog (Thanks to Håkan Mattsson)</p>
<p>
Own Id: OTP-10621</p>
</item>
@@ -126,7 +126,7 @@
<item>
<p>
Fixed disappearing breakpoints bug, reported by Ricardo
- Catalinas Jim�nez.</p>
+ Catalinas Jiménez.</p>
<p>
Own Id: OTP-9950</p>
</item>
@@ -147,7 +147,7 @@
occurrences of "Ok" to "OK" in the code, variable names
and strings. This improves the consistency of the code
and follows the GTK UI where "OK" is always used.(Thanks
- to Ricardo Catalinas Jim�nez)</p>
+ to Ricardo Catalinas Jiménez)</p>
<p>
Own Id: OTP-9699</p>
</item>
diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml
index 12f93ab400..d2ec82efc8 100644
--- a/lib/edoc/doc/src/notes.xml
+++ b/lib/edoc/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -219,7 +219,7 @@
<list>
<item>
<p> Add encoding when parsing Wiki text. EDoc used to
- fail on strings such as "���". (Thanks to Richard
+ fail on strings such as "äåö". (Thanks to Richard
Carlsson.) </p>
<p>
Own Id: OTP-9109</p>
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index abd3a663bd..2ae5f93165 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -120,7 +120,7 @@
have it installed.</p>
<p>
Also adds minor cosmetic changes to the CSS. (Thanks to
- Ricardo Catalinas Jim�nez)</p>
+ Ricardo Catalinas Jiménez)</p>
<p>
Own Id: OTP-9918</p>
</item>
diff --git a/lib/erl_docgen/priv/dtd/common.dtd b/lib/erl_docgen/priv/dtd/common.dtd
index fdc02c55a1..f999ef8ea4 100644
--- a/lib/erl_docgen/priv/dtd/common.dtd
+++ b/lib/erl_docgen/priv/dtd/common.dtd
@@ -67,7 +67,7 @@
<!-- References -->
-<!ELEMENT seealso (#PCDATA) >
+<!ELEMENT seealso (#PCDATA|c|em)* >
<!ATTLIST seealso marker CDATA #REQUIRED >
<!ELEMENT url (#PCDATA) >
<!ATTLIST url href CDATA #REQUIRED >
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index 2fdc839c7b..930b24c26a 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -262,7 +262,7 @@
<item>
<p>
Initialize <c>to</c> and <c>to_name</c> in
- <c>erl_receive_msg</c>. (Thanks to G�ran Larsson)</p>
+ <c>erl_receive_msg</c>. (Thanks to Göran Larsson)</p>
<p>
Own Id: OTP-9241</p>
</item>
diff --git a/lib/et/doc/src/notes.xml b/lib/et/doc/src/notes.xml
index d45d0a89bf..a34dd4a541 100644
--- a/lib/et/doc/src/notes.xml
+++ b/lib/et/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -43,7 +43,7 @@
<item>
<p>
Use erlang:demonitor(Ref, [flush]) where applicable.
- Thanks to Lo�c Hoguin.</p>
+ Thanks to Loïc Hoguin.</p>
<p>
Own Id: OTP-11039</p>
</item>
@@ -82,7 +82,7 @@
<list>
<item>
<p>
- Fix typo in ET doc (Thanks to Ricardo Catalinas Jim�nez)</p>
+ Fix typo in ET doc (Thanks to Ricardo Catalinas Jiménez)</p>
<p>
Own Id: OTP-10119</p>
</item>
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index b3c41bd396..86e10a06fc 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -37,7 +37,7 @@
<item>
<p>
Fix the title of hipe_app documentation page. Thanks to
- Lo�c Hoguin.</p>
+ Loïc Hoguin.</p>
<p>
Own Id: OTP-10904</p>
</item>
@@ -76,7 +76,7 @@
<p>
Fix bug in hipe compiled code related to the handling of
<c>is_number/1</c>. (Thanks to Sebastian Egner and
- Johannes Wei�l for minimal test code and Kostis for quick
+ Johannes Weißl for minimal test code and Kostis for quick
patch)</p>
<p>
Own Id: OTP-10897</p>
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index f6bb2cca49..42183b8cc5 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -616,7 +616,7 @@
<p>[httpd] Fix httpd directory traversal on Windows.
Directory traversal was possible on Windows where
backward slash is used as directory separator. </p>
- <p>Andr�s Veres-Szentkir�lyi.</p>
+ <p>András Veres-Szentkirályi.</p>
<p>Own Id: OTP-9561</p>
</item>
@@ -724,7 +724,7 @@
<item>
<p>[httpd] Improved error messages. </p>
- <p>Ricardo Catalinas Jim�nez</p>
+ <p>Ricardo Catalinas Jiménez</p>
<p>Own Id: OTP-9157</p>
</item>
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 0175c38397..6b703b7c2b 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -390,7 +390,7 @@
<p>
Fixed issue where using controlling_process/2 with self()
as the second argument caused the port to leak if self()
- crashes. (Thanks to Ricardo Catalinas Jim�nez)</p>
+ crashes. (Thanks to Ricardo Catalinas Jiménez)</p>
<p>
Own Id: OTP-10094</p>
</item>
@@ -532,7 +532,7 @@
Various typographical errors corrected in documentation
for the global, error_logger, etop, lists, ets and
supervisor modules and in the c_portdriver and kernel_app
- documentation. (Thanks to Ricardo Catalinas Jim�nez)</p>
+ documentation. (Thanks to Ricardo Catalinas Jiménez)</p>
<p>
Own Id: OTP-9987</p>
</item>
@@ -727,7 +727,7 @@
Fixes net_kernel:get_net_ticktime() doc</p>
<p>
Adds missing description when `ignored' is returned.
- (Thanks to Ricardo Catalinas Jim�nez )</p>
+ (Thanks to Ricardo Catalinas Jiménez )</p>
<p>
Own Id: OTP-9713</p>
</item>
@@ -913,7 +913,7 @@
<item>
<p>
Fix typo in doc of rpc:pmap/3 (Thanks to Ricardo
- Catalinas Jim�nez)</p>
+ Catalinas Jiménez)</p>
<p>
Own Id: OTP-9168</p>
</item>
diff --git a/lib/megaco/src/app/megaco.appup.src b/lib/megaco/src/app/megaco.appup.src
index b3659366a4..da171e0c18 100644
--- a/lib/megaco/src/app/megaco.appup.src
+++ b/lib/megaco/src/app/megaco.appup.src
@@ -170,6 +170,8 @@
{"%VSN%",
[
+ {"3.17.0.1", []},
+ {"3.17", []},
{"3.16.0.3",
[
{update, megaco_flex_scanner_handler, {advanced, upgrade_from_pre_3_17},
@@ -178,6 +180,8 @@
}
],
[
+ {"3.17.0.1", []},
+ {"3.17", []},
{"3.16.0.3",
[
{update, megaco_flex_scanner_handler, {advanced, downgrade_to_pre_3_17},
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 790f5d92b5..99a26dd281 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -115,7 +115,7 @@
<item>
<p>
Remove support for the query keyword and query
- expressions. Thanks to Lo�c Hoguin.</p>
+ expressions. Thanks to Loïc Hoguin.</p>
<p>
Own Id: OTP-10729</p>
</item>
@@ -352,7 +352,7 @@
"-f" is a non-standard chmod option which at least SGI
IRIX and HP UX do not support. As the only effect of the
"-f" flag is to suppress warning messages, it can be
- safely omitted. (Thanks to Holger Wei�)</p>
+ safely omitted. (Thanks to Holger Weiß)</p>
<p>
Own Id: OTP-9170</p>
</item>
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index 34e87374a2..998a4e4f9f 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -139,7 +139,7 @@
<p>
Start position was lost after a 'Found' -&gt; 'Not found'
search sequence leading an undefined position in the next
- search. Thanks to Peti G�mori</p>
+ search. Thanks to Peti Gömori</p>
<p>
Own Id: OTP-10218</p>
</item>
diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml
index ebbacb2327..a1c6f999e0 100644
--- a/lib/odbc/doc/src/notes.xml
+++ b/lib/odbc/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -272,7 +272,7 @@
<item>
<p>
ODBC now handles the types SQL_WCHAR and SQL_WVARCHAR.
- Thanks to Juhani R�nkimies. ODBC also has a new
+ Thanks to Juhani Ränkimies. ODBC also has a new
connection option to return all strings as binaries and
also expect strings to be binaries in the param_query
function. These changes provides some unicode support.</p>
@@ -282,7 +282,7 @@
<item>
<p>
Now supports SQL_TYPE_TIMESTAMP on the format {{YY, MM,
- DD}, {HH, MM, SS}}. Thanks to Juhani R�nkimies.</p>
+ DD}, {HH, MM, SS}}. Thanks to Juhani Ränkimies.</p>
<p>
*** POTENTIAL INCOMPATIBILITY ***</p>
<p>
diff --git a/lib/odbc/doc/src/odbc.xml b/lib/odbc/doc/src/odbc.xml
index a984bf4485..48b105d1e4 100644
--- a/lib/odbc/doc/src/odbc.xml
+++ b/lib/odbc/doc/src/odbc.xml
@@ -213,12 +213,13 @@
Note that this information is probably of little use when writing database-independent code,
but can be of assistance in providing more sophisticated error handling when dealing with
a known underlying database.
+ </p>
<list type="bulleted">
<item><c>ODBCErrorCode</c> is the ODBC error string returned by the ODBC driver.</item>
<item><c>NativeErrorCode</c> is the numberic error code returned by the underlying database. The possible values
and their meanings are dependent on the database being used.</item>
<item><c>Reason</c> is as per the <c>Reason</c> field when extended errors are not enabled.</item>
- </list></p>
+ </list>
</desc>
</func>
<func>
diff --git a/lib/parsetools/src/leex.erl b/lib/parsetools/src/leex.erl
index 7039aea1ae..03f864ff03 100644
--- a/lib/parsetools/src/leex.erl
+++ b/lib/parsetools/src/leex.erl
@@ -436,7 +436,7 @@ parse_defs(_, {eof,L}, St) ->
parse_defs(Ifile, {ok,Chars,L}=Line, Ms, St) ->
%% This little beauty matches out a macro definition, RE's are so clear.
MS = "^[ \t]*([A-Z_][A-Za-z0-9_]*)[ \t]*=[ \t]*([^ \t\r\n]*)[ \t\r\n]*\$",
- case re:run(Chars, MS, [{capture,all_but_first,list}]) of
+ case re:run(Chars, MS, [{capture,all_but_first,list},unicode]) of
{match,[Name,Def]} ->
%%io:fwrite("~p = ~p\n", [Name,Def]),
parse_defs(Ifile, nextline(Ifile, L, St), [{Name,Def}|Ms], St);
@@ -491,7 +491,7 @@ parse_rules_end(_, NextLine, REAs, As, St) ->
collect_rule(Ifile, Chars, L0) ->
%% Erlang strings are 1 based, but re 0 :-(
- {match,[{St0,Len}|_]} = re:run(Chars, "[^ \t\r\n]+"),
+ {match,[{St0,Len}|_]} = re:run(Chars, "[^ \t\r\n]+", [unicode]),
St = St0 + 1,
%%io:fwrite("RE = ~p~n", [substr(Chars, St, Len)]),
case collect_action(Ifile, substr(Chars, St+Len), L0, []) of
@@ -548,7 +548,7 @@ var_used(Name, Toks) ->
%% here as it uses info in replace string (&).
parse_rule_regexp(RE0, [{M,Exp}|Ms], St) ->
- Split= re:split(RE0, "\\{" ++ M ++ "\\}", [{return,list}]),
+ Split= re:split(RE0, "\\{" ++ M ++ "\\}", [{return,list},unicode]),
RE1 = string:join(Split, Exp),
parse_rule_regexp(RE1, Ms, St);
parse_rule_regexp(RE, [], St) ->
diff --git a/lib/parsetools/test/leex_SUITE.erl b/lib/parsetools/test/leex_SUITE.erl
index 7cbc72accb..ff49c853f6 100644
--- a/lib/parsetools/test/leex_SUITE.erl
+++ b/lib/parsetools/test/leex_SUITE.erl
@@ -45,7 +45,7 @@
pt/1, man/1, ex/1, ex2/1, not_yet/1,
- otp_10302/1, otp_11286/1]).
+ otp_10302/1, otp_11286/1, unicode/1]).
% Default timetrap timeout (set in init_per_testcase).
-define(default_timeout, ?t:minutes(1)).
@@ -66,7 +66,7 @@ all() ->
groups() ->
[{checks, [], [file, compile, syntax]},
- {examples, [], [pt, man, ex, ex2, not_yet]},
+ {examples, [], [pt, man, ex, ex2, not_yet, unicode]},
{tickets, [], [otp_10302, otp_11286]}].
init_per_suite(Config) ->
@@ -401,6 +401,24 @@ pt(Config) when is_list(Config) ->
?line run(Config, Ts),
ok.
+unicode(suite) ->
+ [];
+unicode(Config) when is_list(Config) ->
+ Ts = [{unicode_1,
+ <<"%% -*- coding: utf-8 -*-\n"
+ "Definitions.\n"
+ "RTLarrow = (←)\n"
+ "Rules.\n"
+ "{RTLarrow} : {token,{'<-',TokenLine}}.\n"
+ "Erlang code.\n"
+ "-export([t/0]).\n"
+ "t() -> {ok, [{'<-', 1}], 1} = string(\"←\"), ok.">>,
+ default,
+ ok}],
+
+ ?line run(Config, Ts),
+ ok.
+
man(doc) ->
"Examples from the manpage.";
man(suite) -> [];
@@ -1076,7 +1094,7 @@ run_test(Config, Def, Pre) ->
XrlFile = filename:join(DataDir, DefFile),
ErlFile = filename:join(DataDir, Filename),
Opts = [return, warn_unused_vars,{outdir,DataDir}],
- ok = file:write_file(XrlFile, Def),
+ ok = file:write_file(XrlFile, Def, [{encoding, unicode}]),
LOpts = [return, {report, false} |
case Pre of
default ->
diff --git a/lib/percept/doc/src/notes.xml b/lib/percept/doc/src/notes.xml
index 795dd98545..391f4d6d47 100644
--- a/lib/percept/doc/src/notes.xml
+++ b/lib/percept/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -295,7 +295,7 @@
<list>
<item>
<p>Performance enhancement for the egd render engine
- (Thanks to Magnus Tho�ng).</p>
+ (Thanks to Magnus Thoäng).</p>
<p>
Own Id: OTP-7616</p>
</item>
diff --git a/lib/reltool/doc/src/notes.xml b/lib/reltool/doc/src/notes.xml
index 598d3333f8..1728515cb4 100644
--- a/lib/reltool/doc/src/notes.xml
+++ b/lib/reltool/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -61,7 +61,7 @@
even if the application was explicitly excluded in the
config. This has been changed and will only produce a
warning. If the application is not explicitly excluded it
- will still cause reltool to fail. Thanks to H�kan
+ will still cause reltool to fail. Thanks to Håkan
Mattsson!</p>
<p>
Own Id: OTP-10988</p>
diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml
index 2281ac4a49..db35eba436 100644
--- a/lib/runtime_tools/doc/src/notes.xml
+++ b/lib/runtime_tools/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -69,7 +69,7 @@
<item>
<p>
Fix Table Viewer refresh crash on no more existing ets
- tables (Thanks to Peti G�mori)</p>
+ tables (Thanks to Peti Gömori)</p>
<p>
Own Id: OTP-10635</p>
</item>
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index 8ba2161a9d..7514c52dda 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -214,7 +214,7 @@
<seealso marker="snmpa_mib_storage">mib storage</seealso>.
At present there are three simple modules
(<c>snmpa_mib_storage_ets</c>, <c>snmpa_mib_storage_dets</c> and
- <c>snmpa_mib_storage_mnesia</c>) implement�ng this behaviour,
+ <c>snmpa_mib_storage_mnesia</c>) implementing this behaviour,
provided with the app. </p>
<p>A config option for the (agent)
<seealso marker="snmp_config#agent_mib_storage">mib storage</seealso>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 299dd5058a..6b7ff7b238 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="iso-8859-1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -138,7 +138,7 @@
<item>
<p>
Fix link to documentation for ssh:connect/3,4. Thanks to
- Martin H�ssler.</p>
+ Martin Hässler.</p>
<p>
Own Id: OTP-10862</p>
</item>
diff --git a/lib/ssl/src/Makefile b/lib/ssl/src/Makefile
index cf9f7d5001..6744e2f256 100644
--- a/lib/ssl/src/Makefile
+++ b/lib/ssl/src/Makefile
@@ -58,15 +58,18 @@ MODULES= \
ssl_connection_sup \
tls_handshake \
dtls_handshake\
+ ssl_handshake\
ssl_manager \
ssl_session \
ssl_session_cache \
ssl_socket \
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 = \
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index ac2ee0d09f..fda488501c 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -17,3 +17,325 @@
%% %CopyrightEnd%
%%
-module(dtls_connection).
+
+%%-behaviour(gen_fsm).
+
+%% -include("dtls_handshake.hrl").
+%% -include("ssl_alert.hrl").
+%% -include("dtls_record.hrl").
+%% -include("ssl_cipher.hrl").
+%% -include("ssl_internal.hrl").
+%% -include("ssl_srp.hrl").
+%% -include_lib("public_key/include/public_key.hrl").
+
+
+%% %% Called by dtls_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(message_sequences, {
+%% read = 0,
+%% write = 0
+%% }).
+
+%% -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
+%% message_sequences = #message_sequences{},
+%% dtls_packets = [], % Not yet handled decode ssl/tls packets.
+%% dtls_record_buffer, % binary() buffer of incomplete records
+%% dtls_handshake_buffer, % binary() buffer of incomplete handshakes
+%% dtls_handshake_history, % tls_handshake_history()
+%% dtls_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}
+%% client_cookie = <<>>
+%% }).
+
+
+
+%% %%====================================================================
+%% %% Internal application API
+%% %%====================================================================
+
+
+%% %%====================================================================
+%% %% 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,
+%% connection_states = ConnectionStates0,
+%% renegotiation = {Renegotiation, _},
+%% client_cookie = Cookie} = State0) ->
+%% Hello = dtls_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts,
+%% Cache, CacheCb, Renegotiation, Cert),
+
+%% Version = Hello#client_hello.client_version,
+%% State1 = State0#state{negotiated_version = Version, %% Requested version
+%% session =
+%% Session0#session{session_id = Hello#client_hello.session_id},
+%% dtls_handshake_history = ssl_handshake:init_handshake_history()},
+
+%% State2 = send_flight(Hello, waiting, State1),
+
+%% {Record, State} = next_record(State2),
+%% next_state(hello, hello, Record, State);
+
+%% hello(start, #state{role = server} = State0) ->
+%% {Record, State} = next_record(State0),
+%% next_state(hello, hello, Record, State);
+
+%% hello(#hello_request{}, #state{role = client} = State0) ->
+%% {Record, State} = next_record(State0),
+%% next_state(hello, hello, Record, State);
+
+%% hello(#server_hello{cipher_suite = CipherSuite,
+%% compression_method = Compression} = Hello,
+%% #state{session = #session{session_id = OldId},
+%% connection_states = ConnectionStates0,
+%% role = client,
+%% negotiated_version = ReqVersion,
+%% renegotiation = {Renegotiation, _},
+%% ssl_options = SslOptions} = State1) ->
+%% State0 = flight_done(State1),
+%% case ssl_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
+%% end;
+
+%% hello(#hello_verify_request{cookie = Cookie},
+%% #state{host = Host, port = Port,
+%% session = #session{own_certificate = Cert},
+%% session_cache = Cache, session_cache_cb = CacheCb,
+%% ssl_options = SslOpts,
+%% connection_states = ConnectionStates0,
+%% renegotiation = {Renegotiation, _}} = State0) ->
+%% Hello = ssl_handshake:client_hello(Host, Port, Cookie, ConnectionStates0, SslOpts,
+%% Cache, CacheCb, Renegotiation, Cert),
+%% State1 = State0#state{
+%% tls_handshake_history = ssl_handshake:init_handshake_history(),
+%% client_cookie = Cookie},
+%% State2 = send_flight(Hello, waiting, State1),
+
+%% {Record, State} = next_record(State2),
+%% next_state(hello, hello, Record, State);
+
+%% 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 ssl_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().
+%% %%--------------------------------------------------------------------
+
+%% abbreviated(timeout, State) ->
+%% { next_state, abbreviated, State, hibernate };
+
+%% abbreviated(Msg, State) ->
+%% handle_unexpected_message(Msg, abbreviated, State).
+
+%% %%--------------------------------------------------------------------
+%% -spec certify(#hello_request{} | #certificate{} | #server_key_exchange{} |
+%% #certificate_request{} | #server_hello_done{} | #client_key_exchange{} | term(),
+%% #state{}) -> gen_fsm_state_return().
+%% %%--------------------------------------------------------------------
+
+
+%% certify(timeout, State) ->
+%% { next_state, certify, State, hibernate };
+
+%% certify(Msg, State) ->
+%% handle_unexpected_message(Msg, certify, State).
+
+
+%% %%--------------------------------------------------------------------
+%% -spec cipher(#hello_request{} | #certificate_verify{} | #finished{} | term(),
+%% #state{}) -> gen_fsm_state_return().
+%% %%--------------------------------------------------------------------
+
+%% cipher(timeout, State) ->
+%% { next_state, cipher, State, hibernate };
+
+%% cipher(Msg, State) ->
+%% handle_unexpected_message(Msg, cipher, State).
+
+%% %%--------------------------------------------------------------------
+%% -spec connection(#hello_request{} | #client_hello{} | term(),
+%% #state{}) -> gen_fsm_state_return().
+%% %%--------------------------------------------------------------------
+
+%% connection(timeout, State) ->
+%% {next_state, connection, State, hibernate};
+
+%% connection(Msg, State) ->
+%% handle_unexpected_message(Msg, connection, State).
+
+%% %%--------------------------------------------------------------------
+%% %%% Internal functions
+%% %%--------------------------------------------------------------------
+%% handle_unexpected_message(Msg, Info, #state{negotiated_version = Version} = State) ->
+%% Alert = ?ALERT_REC(?FATAL,?UNEXPECTED_MESSAGE),
+%% handle_own_alert(Alert, Version, {Info, Msg}, State).
+
+%% send_flight(HandshakeRec, FlightState, State) ->
+%% send_flight(FlightState, buffer_flight(HandshakeRec, State)).
+
+%% send_flight(FlightState, State = #state{negotiated_version = Version,
+%% flight_buffer = Buffer}) ->
+
+%% State1 = do_send_flight(queue:to_list(Buffer), [], State),
+%% finish_send_flight(Version, FlightState, State1).
+
+%% resend_flight(State = #state{negotiated_version = Version,
+%% flight_state = FlightState,
+%% flight_buffer = Buffer})
+%% when FlightState == finished; FlightState == waiting ->
+%% State1 = do_send_flight(queue:to_list(Buffer), [], State),
+%% finish_send_flight(Version, FlightState, State1);
+
+%% resend_flight(State) ->
+%% State.
+
+%% flight_done(State) ->
+%% cancel_dtls_retransmit_timer(State#state{flight_state = done,
+%% flight_buffer = undefined}).
+
+%% do_send_flight([], BinMsgs, State = #state{transport_cb = Transport, socket = Socket}) ->
+%% Transport:send(Socket, lists:reverse(BinMsgs)),
+%% State;
+%% do_send_flight([{Epoch, MsgSeq, HandshakeRec}|T], BinMsgs0,
+%% State = #state{negotiated_version = Version,
+%% connection_states = ConnectionStates0}) ->
+%% CS0 = ssl_record:connection_state_by_epoch(ConnectionStates0, Epoch, write),
+%% {BinMsgs, CS1} = encode_handshake_rec(HandshakeRec, Version, MsgSeq, BinMsgs0, CS0),
+%% ConnectionStates1 = ssl_record:set_connection_state_by_epoch(ConnectionStates0, CS1, write),
+%% do_send_flight(T, BinMsgs, State#state{connection_states = ConnectionStates1}).
+
+%% cancel_dtls_retransmit_timer(State = #state{dtls_retransmit_timer = TimerRef}) ->
+%% cancel_timer(TimerRef),
+%% State#state{dtls_retransmit_timer = undefined}.
+
+%% rearm_dtls_retransmit_timer(State = #state{dtls_retransmit_timer = undefined}) ->
+%% TimerRef = erlang:start_timer(1000, self(), dtls_retransmit),
+%% State#state{dtls_retransmit_timer = TimerRef};
+%% rearm_dtls_retransmit_timer(State) ->
+%% State.
+
+%% finish_send_flight({254, _}, waiting, State) ->
+%% TimerRef = erlang:start_timer(1000, self(), dtls_retransmit),
+%% State#state{
+%% dtls_retransmit_timer = TimerRef,
+%% last_retransmit = timestamp(),
+%% flight_state = waiting};
+
+%% finish_send_flight(_, FlightState, State) ->
+%% State#state{flight_state = FlightState}.
+
+%% timestamp() ->
+%% {Mega, Sec, Micro} = erlang:now(),
+%% Mega * 1000000 * 1000 + Sec * 1000 + (Micro div 1000).
+
+%% encode_handshake_rec(HandshakeRec, Version, MsgSeq, BinMsgs0, CS0) ->
+%% {_, Fragments} = ssl_handshake:encode_handshake(HandshakeRec, Version, MsgSeq, 1400),
+%% lists:foldl(fun(F, {Bin, C0}) ->
+%% {B, C1} = ssl_record:encode_handshake(F, Version, C0),
+%% {[B|Bin], C1} end, {BinMsgs0, CS0}, Fragments).
diff --git a/lib/ssl/src/dtls_handshake.erl b/lib/ssl/src/dtls_handshake.erl
index b25daa59d9..26e8ce7503 100644
--- a/lib/ssl/src/dtls_handshake.erl
+++ b/lib/ssl/src/dtls_handshake.erl
@@ -16,3 +16,413 @@
%%
%% %CopyrightEnd%
-module(dtls_handshake).
+
+-include("dtls_handshake.hrl").
+-include("dtls_record.hrl").
+-include("ssl_internal.hrl").
+
+-export([client_hello/9, hello/3, get_dtls_handshake/2,
+ dtls_handshake_new_flight/1, dtls_handshake_new_epoch/1,
+ encode_handshake/4]).
+
+%%====================================================================
+%% Internal application API
+%%====================================================================
+
+%%--------------------------------------------------------------------
+-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(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(Address, Port,
+ #ssl_tls{epoch = _Epoch, record_seq = _Seq,
+ version = Version} = Record) ->
+ {[{Hello, _}], _, _} =
+ get_dtls_handshake(Record,
+ 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
+ HelloVerifyRequest = encode_handshake(#hello_verify_request{protocol_version = Version,
+ cookie = Cookie},
+ Version, 0, 1400),
+ {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>>.
+
+%%--------------------------------------------------------------------
+encode_handshake(Package, Version, MsgSeq, Mss) ->
+ {MsgType, Bin} = enc_hs(Package, Version),
+ Len = byte_size(Bin),
+ HsHistory = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(0), ?uint24(Len), Bin],
+ BinMsg = dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Bin, 0, []),
+ {HsHistory, BinMsg}.
+
+%--------------------------------------------------------------------
+-spec get_dtls_handshake(#ssl_tls{}, #dtls_hs_state{} | binary()) ->
+ {[dtls_handshake()], #ssl_tls{}}.
+%
+% 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(Record, <<>>) ->
+% get_dtls_handshake_aux(Record, dtls_hs_state_init());
+get_dtls_handshake(Record, HsState) ->
+ get_dtls_handshake_aux(Record, HsState).
+
+%--------------------------------------------------------------------
+-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
+%%--------------------------------------------------------------------
+
+dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Bin, Offset, Acc)
+ when byte_size(Bin) + 12 < Mss ->
+ FragmentLen = byte_size(Bin),
+ BinMsg = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragmentLen), Bin],
+ lists:reverse([BinMsg|Acc]);
+dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Bin, Offset, Acc) ->
+ FragmentLen = Mss - 12,
+ <<Fragment:FragmentLen/bytes, Rest/binary>> = Bin,
+ BinMsg = [MsgType, ?uint24(Len), ?uint16(MsgSeq), ?uint24(Offset), ?uint24(FragmentLen), Fragment],
+ dtls_split_handshake(Mss, MsgType, Len, MsgSeq, Rest, Offset + FragmentLen, [BinMsg|Acc]).
+
+get_dtls_handshake_aux(#ssl_tls{version = Version,
+ record_seq = 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
+ {retransmit, HsState1} ->
+ case Rest of
+ <<>> ->
+ {retransmit, HsState1};
+ _ ->
+ get_dtls_handshake_aux(Version, SeqNo, Rest, HsState1)
+ end;
+ {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) ->
+ {lists:reverse(HsState#dtls_hs_state.completed),
+ HsState#dtls_hs_state.highest_record_seq,
+ HsState#dtls_hs_state{completed = []}}.
+
+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 ->
+ {retransmit, HsState};
+
+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, 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_hs(#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_hs(#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_hs(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).
diff --git a/lib/ssl/src/dtls_handshake.hrl b/lib/ssl/src/dtls_handshake.hrl
index db7b8596ae..5bdf45f627 100644
--- a/lib/ssl/src/dtls_handshake.hrl
+++ b/lib/ssl/src/dtls_handshake.hrl
@@ -27,6 +27,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 +37,22 @@
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
+ }).
+
+-type dtls_handshake() :: #client_hello{} | #hello_verify_request{} | ssl_handshake().
-endif. % -ifdef(dtls_handshake).
diff --git a/lib/ssl/src/dtls_record.erl b/lib/ssl/src/dtls_record.erl
index 2469a7d26c..f667458a10 100644
--- a/lib/ssl/src/dtls_record.erl
+++ b/lib/ssl/src/dtls_record.erl
@@ -15,4 +15,370 @@
%% 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]).
+
+%% Protocol version handling
+-export([protocol_version/1, lowest_protocol_version/2,
+ highest_protocol_version/1, supported_protocol_versions/0,
+ is_acceptable_version/2, cipher/4, decipher/2]).
+
+-export([init_connection_state_seq/2, current_connection_state_epoch/2,
+ set_connection_state_by_epoch/3, connection_state_by_epoch/3]).
+
+-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, record_seq = 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, record_seq = 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, record_seq = 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, record_seq = 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_state{
+ compression_state = CompS0,
+ epoch = Epoch,
+ sequence_number = Seq,
+ security_parameters=
+ #security_parameters{compression_algorithm = CompAlg}
+ }= CS0) ->
+ {Comp, CompS1} = ssl_record: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, Epoch, Seq, CipherText),
+ {CTBin, CS2}.
+
+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} = ssl_record:uncompress(CompressAlg,
+ Compressed, CompressionS0),
+ ConnnectionStates = ConnnectionStates0#connection_states{
+ current_read = ReadState1#connection_state{
+ compression_state = CompressionS1}},
+ {Plain, ConnnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end.
+
+%%--------------------------------------------------------------------
+-spec protocol_version(tls_atom_version() | tls_version()) ->
+ tls_version() | tls_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(tls_version(), tls_version()) -> tls_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 highest_protocol_version([tls_version()]) -> tls_version().
+%%
+%% Description: Highest protocol version present in a list
+%%--------------------------------------------------------------------
+highest_protocol_version([Ver | Vers]) ->
+ highest_protocol_version(Ver, Vers).
+
+highest_protocol_version(Version, []) ->
+ 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).
+
+
+%%--------------------------------------------------------------------
+-spec supported_protocol_versions() -> [tls_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(Vsns);
+ {ok, Vsn} ->
+ supported_protocol_versions([Vsn])
+ end.
+
+supported_protocol_versions([]) ->
+ Vsns = supported_connection_protocol_versions([]),
+ application:set_env(ssl, dtls_protocol_version, Vsns),
+ Vsns;
+
+supported_protocol_versions([_|_] = Vsns) ->
+ Vsns.
+
+supported_connection_protocol_versions([]) ->
+ ?ALL_DATAGRAM_SUPPORTED_VERSIONS.
+
+%%--------------------------------------------------------------------
+-spec is_acceptable_version(tls_version(), Supported :: [tls_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(tls_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) -> ok.
+%%
+%% 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
+%%--------------------------------------------------------------------
+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].
+
+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={254, _},
+ epoch = Epoch, record_seq = SeqNo,
+ 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 = hash_with_seqno(CS1, Type, Version, Epoch, SeqNo, TLength, T),
+ case ssl_record:is_correct_mac(Mac, MacHash) of
+ true ->
+ {TLS#ssl_tls{fragment = T}, CS1};
+ false ->
+ ?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
+ end;
+ #alert{} = Alert ->
+ Alert
+ end.
+
+hash_with_seqno(#connection_state{mac_secret = MacSecret,
+ security_parameters =
+ SecPars},
+ Type, Version = {254, _},
+ Epoch, SeqNo, Length, Fragment) ->
+ mac_hash(Version,
+ SecPars#security_parameters.mac_algorithm,
+ MacSecret, (Epoch bsl 48) + SeqNo, Type,
+ Length, Fragment).
+
+hash_and_bump_seqno(#connection_state{epoch = Epoch,
+ sequence_number = SeqNo,
+ mac_secret = MacSecret,
+ security_parameters =
+ SecPars} = CS0,
+ Type, Version = {254, _}, Length, Fragment) ->
+ Hash = mac_hash(Version,
+ SecPars#security_parameters.mac_algorithm,
+ MacSecret, (Epoch bsl 48) + SeqNo, Type,
+ Length, Fragment),
+ {Hash, CS0#connection_state{sequence_number = SeqNo+1}}.
+
+mac_hash(Version, MacAlg, MacSecret, SeqNo, Type, Length, Fragment) ->
+ dtls_v1:mac_hash(MacAlg, MacSecret, SeqNo, Type, Version,
+ Length, Fragment).
diff --git a/lib/ssl/src/dtls_v1.erl b/lib/ssl/src/dtls_v1.erl
new file mode 100644
index 0000000000..c12e12e424
--- /dev/null
+++ b/lib/ssl/src/dtls_v1.erl
@@ -0,0 +1,43 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. 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%
+%%
+-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) -> [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, 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/ssl.app.src b/lib/ssl/src/ssl.app.src
index 582a60635f..44798f8c12 100644
--- a/lib/ssl/src/ssl.app.src
+++ b/lib/ssl/src/ssl.app.src
@@ -3,40 +3,44 @@
{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
+ dtls_handshake,
+ dtls_record,
+ dtls_v1,
+ %% API
+ tls, %% Future API module
+ dtls, %% Future API module
ssl,
+ ssl_session_cache_api,
%% Both TLS/SSL and DTLS
- ssl_app,
- ssl_sup,
+ ssl_handshake,
+ ssl_record,
+ ssl_cipher,
+ ssl_srp_primes,
+ ssl_alert,
+ ssl_socket,
+ %%ssl_connection,
+ %% Erlang Distribution over SSL/TLS
inet_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
+ %% App structure
+ ssl_app,
+ ssl_sup,
+ ssl_connection_sup
]},
{registered, [ssl_sup, ssl_manager]},
{applications, [crypto, public_key, kernel, stdlib]},
diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src
index 9e5bec26f1..c090b6ebfb 100644
--- a/lib/ssl/src/ssl.appup.src
+++ b/lib/ssl/src/ssl.appup.src
@@ -1,6 +1,7 @@
%% -*- erlang -*-
{"%VSN%",
[
+ {<<"5.3\\*">>, [{restart_application, ssl}]},
{<<"5.2\\*">>, [{restart_application, ssl}]},
{<<"5.1\\*">>, [{restart_application, ssl}]},
{<<"5.0\\*">>, [{restart_application, ssl}]},
@@ -8,6 +9,7 @@
{<<"3\\.*">>, [{restart_application, ssl}]}
],
[
+ {<<"5.3\\*">>, [{restart_application, ssl}]},
{<<"5.2\\*">>, [{restart_application, ssl}]},
{<<"5.1\\*">>, [{restart_application, ssl}]},
{<<"5.0\\*">>, [{restart_application, ssl}]},
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 09aad8e414..6513042e98 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -32,7 +32,7 @@
-include("ssl_alert.hrl").
-include_lib("public_key/include/public_key.hrl").
--export([security_parameters/3, suite_definition/1,
+-export([security_parameters/2, 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,
@@ -41,7 +41,17 @@
-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(tls_version() | undefined, cipher_suite(), #security_parameters{}) ->
#security_parameters{}.
%%
%% Description: Returns a security parameters record where the
@@ -195,9 +205,9 @@ block_decipher(Fun, #cipher_state{key=Key, iv=IV} = CipherState0,
%% 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).
%%--------------------------------------------------------------------
-spec anonymous_suites() -> [cipher_suite()].
@@ -1192,15 +1202,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;
+ 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
%%
diff --git a/lib/ssl/src/ssl_cipher.hrl b/lib/ssl/src/ssl_cipher.hrl
index c7c71ee1a7..62a5269def 100644
--- a/lib/ssl/src/ssl_cipher.hrl
+++ b/lib/ssl/src/ssl_cipher.hrl
@@ -28,7 +28,8 @@
-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 hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512.
+-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()}.
-type int_cipher_suite() :: {key_algo(), cipher(), hash(), hash() | default_prf}.
-type cipher_suite() :: binary().
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
new file mode 100644
index 0000000000..29a8996bd6
--- /dev/null
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -0,0 +1,1650 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. 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: 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").
+
+%% Handshake messages
+-export([hello_request/0, server_hello_done/0,
+ certificate/4, certificate_request/4, key_exchange/3,
+ finished/5, next_protocol/1]).
+
+%% Handle handshake messages
+-export([certify/7, client_certificate_verify/6, certificate_verify/6, verify_signature/5,
+ master_secret/5, server_key_exchange_hash/2, verify_connection/6]).
+
+%% 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_suites/3, cipher_suites/2,
+ select_session/10]).
+
+%% Extensions handling
+-export([client_hello_extensions/5,
+ handle_client_hello_extensions/8, %% Returns server hello extensions
+ handle_server_hello_extensions/9
+ ]).
+
+%% MISC
+-export([select_version/3, prf/5, select_hashsign/2, select_cert_hashsign/3,
+ decrypt_premaster_secret/2]).
+
+%%====================================================================
+%% 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_done() -> #server_hello_done{}.
+%%
+%% Description: Creates a server hello done message.
+%%--------------------------------------------------------------------
+server_hello_done() ->
+ #server_hello_done{}.
+
+client_hello_extensions(Version, CipherSuites, SslOpts, ConnectionStates, Renegotiation) ->
+ {EcPointFormats, EllipticCurves} =
+ case advertises_ec_ciphers(lists:map(fun ssl_cipher:suite_definition/1, CipherSuites)) of
+ true ->
+ 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,
+ hash_signs = advertised_hash_signs(Version),
+ ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves,
+ next_protocol_negotiation =
+ encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector,
+ Renegotiation)}.
+
+%%--------------------------------------------------------------------
+-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 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(),
+ 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_request(erl_cipher_suite(), db_handle(), certdb_ref(), tls_version()) ->
+ #certificate_request{}.
+%%
+%% Description: Creates a certificate_request message, called by the server.
+%%--------------------------------------------------------------------
+certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version) ->
+ Types = certificate_types(CipherSuite),
+ HashSigns = advertised_hash_signs(Version),
+ 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).
+
+%%--------------------------------------------------------------------
+-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)}.
+
+%% ---------- Handle handshake messages ----------
+
+%%--------------------------------------------------------------------
+-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 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 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)
+ end.
+%%--------------------------------------------------------------------
+-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.
+%%--------------------------------------------------------------------
+-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, tls_v1:prf(?MD5SHA, Secret, Label, Seed, WantedLength)};
+prf({3,_N}, Secret, Label, Seed, WantedLength) ->
+ {ok, tls_v1:prf(?SHA256, Secret, Label, Seed, WantedLength)}.
+%%--------------------------------------------------------------------
+-spec select_hashsign(#hash_sign_algos{}| undefined, undefined | binary()) ->
+ [{atom(), atom()}] | undefined.
+
+%%
+%% Description:
+%%--------------------------------------------------------------------
+select_hashsign(_, undefined) ->
+ {null, anon};
+select_hashsign(undefined, Cert) ->
+ #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
+ #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ select_cert_hashsign(undefined, Algo, {undefined, undefined});
+select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert) ->
+ #'OTPCertificate'{tbsCertificate = TBSCert} =public_key:pkix_decode_cert(Cert, otp),
+ #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
+ DefaultHashSign = {_, Sign} = select_cert_hashsign(undefined, Algo, {undefined, undefined}),
+ case lists:filter(fun({sha, dsa}) ->
+ true;
+ ({_, dsa}) ->
+ false;
+ ({Hash, S}) when S == Sign ->
+ ssl_cipher:is_acceptable_hash(Hash, proplists:get_value(hashs, crypto:supports()));
+ (_) ->
+ false
+ end, HashSigns) of
+ [] ->
+ DefaultHashSign;
+ [HashSign| _] ->
+ HashSign
+ end.
+%%--------------------------------------------------------------------
+-spec select_cert_hashsign(#hash_sign_algos{}| undefined, oid(), tls_version() | {undefined, undefined}) ->
+ {atom(), atom()}.
+
+%%
+%% Description: For TLS 1.2 selected cert_hash_sign will be recived
+%% in the handshake message, for previous versions use appropriate defaults.
+%% This function is also used by select_hashsign to extract
+%% the alogrithm of the server cert key.
+%%--------------------------------------------------------------------
+select_cert_hashsign(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso Major >= 3 andalso Minor >= 3 ->
+ HashSign;
+select_cert_hashsign(undefined,?'id-ecPublicKey', _) ->
+ {sha, ecdsa};
+select_cert_hashsign(undefined, ?rsaEncryption, _) ->
+ {md5sha, rsa};
+select_cert_hashsign(undefined, ?'id-dsa', _) ->
+ {sha, dsa}.
+
+%%--------------------------------------------------------------------
+-spec master_secret(atom(), 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(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: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(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:Reason ->
+ Report = io_lib:format("Master secret calculation failed"
+ " due to ~p", [Reason]),
+ error_logger:error_report(Report),
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_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([#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>>).
+
+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(), 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).
+
+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(_, _, _) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+
+%%--------------------------------------------------------------------
+-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(_, _, _) ->
+ throw(?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE)).
+
+%%--------------------------------------------------------------------
+-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) ->
+ 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.
+
+select_session(SuggestedSessionId, CipherSuites, Compressions, Port, Session, Version,
+ #ssl_options{ciphers = UserSuites} = SslOpts, Cache, CacheCb, Cert) ->
+ {SessionId, Resumed} = ssl_session:server_id(Port, SuggestedSessionId,
+ SslOpts, Cert,
+ Cache, CacheCb),
+ Suites = ssl_handshake:available_suites(Cert, UserSuites, Version),
+ case Resumed of
+ undefined ->
+ CipherSuite = select_cipher_suite(CipherSuites, Suites),
+ Compression = select_compression(Compressions),
+ {new, Session#session{session_id = SessionId,
+ cipher_suite = CipherSuite,
+ compression_method = Compression}};
+ _ ->
+ {resumed, Resumed}
+ end.
+
+%%-------------certificate handling --------------------------------
+
+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)>>.
+
+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,
+ #hello_extensions{renegotiation_info = Info,
+ srp = SRP,
+ next_protocol_negotiation = NextProtocolNegotiation,
+ ec_point_formats = EcPointFormats0,
+ elliptic_curves = EllipticCurves0}, Version,
+ #ssl_options{secure_renegotiate = SecureRenegotation} = Opts,
+ #session{cipher_suite = CipherSuite, compression_method = Compression} = Session0,
+ ConnectionStates0, Renegotiation) ->
+ Session = handle_srp_extension(SRP, Session0),
+ ConnectionStates = handle_renegotiation_extension(server, RecordCB, Version, Info,
+ Random, CipherSuite, Compression,
+ ConnectionStates0, Renegotiation, SecureRenegotation),
+ ProtocolsToAdvertise = handle_next_protocol_extension(NextProtocolNegotiation, Renegotiation, Opts),
+ {EcPointFormats, EllipticCurves} = handle_ecc_extensions(Version, EcPointFormats0, EllipticCurves0),
+ ServerHelloExtensions = #hello_extensions{
+ renegotiation_info = renegotiation_info(RecordCB, server,
+ ConnectionStates, Renegotiation),
+ ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves,
+ next_protocol_negotiation =
+ encode_protocols_advertised_on_server(ProtocolsToAdvertise)
+ },
+ {Session, ConnectionStates, ServerHelloExtensions}.
+
+handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression,
+ #hello_extensions{renegotiation_info = Info,
+ 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,
+ Compression, ConnectionStates0,
+ Renegotiation, SecureRenegotation),
+ case handle_next_protocol(NextProtocolNegotiation, NextProtoSelector, Renegotiation) of
+ #alert{} = Alert ->
+ Alert;
+ Protocol ->
+ {ConnectionStates, Protocol}
+ 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)
+ 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);
+ 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)
+ 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);
+ 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);
+ {true, false} ->
+ ?ALERT_REC(?FATAL, ?NO_RENEGOTIATION);
+ {false, false} ->
+ {ok, ConnectionStates}
+ end.
+
+hello_extensions_list(#hello_extensions{renegotiation_info = RenegotiationInfo,
+ srp = SRP,
+ hash_signs = HashSigns,
+ ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves,
+ next_protocol_negotiation = NextProtocolNegotiation}) ->
+ [Ext || Ext <- [RenegotiationInfo, SRP, HashSigns,
+ EcPointFormats,EllipticCurves, NextProtocolNegotiation], Ext =/= undefined].
+
+srp_user(#ssl_options{srp_identity = {UserName, _}}) ->
+ #srp{username = UserName};
+srp_user(_) ->
+ undefined.
+
+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.
+
+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 = tls_v1:ecc_curves(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).
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+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.
+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).
+
+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.
+
+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_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{
+ 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 = #cipher_state{iv = ClientIV, key = ClientWriteKey},
+ ServerCipherState = #cipher_state{iv = ServerIV, key = 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, CipherSuite, Compression,
+ ConnectionStates0, Renegotiation, SecureRenegotation) ->
+ case handle_renegotiation_info(RecordCB, Role, Info, ConnectionStates0,
+ Renegotiation, SecureRenegotation,
+ [CipherSuite]) of
+ {ok, ConnectionStates} ->
+ hello_pending_connection_states(RecordCB, Role,
+ Version,
+ CipherSuite,
+ 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));
+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_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)).
+
+dec_hello_extensions(<<>>, Acc) ->
+ Acc;
+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{hash_signs =
+ #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,
+ EllipticCurves = [tls_v1:enum_to_oid(X) || <<X: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}});
+%% 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)}.
+
+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}.
+
+%% 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 --------------------------------
+
+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);
+select_next_protocol(Protocols, NextProtocolSelector) ->
+ case NextProtocolSelector(Protocols) of
+ ?NO_PROTOCOL ->
+ ?ALERT_REC(?FATAL, ?HANDSHAKE_FAILURE);
+ 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([], _) ->
+ 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.
+
+-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)).
+
+advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
+ 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 = crypto:supports(),
+ HasECC = proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)),
+ Hashs = proplists:get_value(hashs, CryptoSupport),
+ #hash_sign_algos{hash_sign_algos =
+ lists:filter(fun({Hash, ecdsa}) -> HasECC andalso proplists:get_bool(Hash, Hashs);
+ ({Hash, _}) -> proplists:get_bool(Hash, Hashs) end, HashSigns)};
+advertised_hash_signs(_) ->
+ undefined.
diff --git a/lib/ssl/src/ssl_handshake.hrl b/lib/ssl/src/ssl_handshake.hrl
index eb1a1dbf62..3a3ad8cf35 100644
--- a/lib/ssl/src/ssl_handshake.hrl
+++ b/lib/ssl/src/ssl_handshake.hrl
@@ -28,11 +28,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
@@ -96,17 +91,22 @@
%% client_hello defined in tls_handshake.hrl and dtls_handshake.hrl
+-record(hello_extensions, {
+ renegotiation_info,
+ hash_signs, % supported combinations of hashes/signature algos
+ next_protocol_negotiation = undefined, % [binary()]
+ srp,
+ ec_point_formats,
+ elliptic_curves
+ }).
+
-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
}).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -337,6 +337,20 @@
-define(EXPLICIT_CHAR2, 2).
-define(NAMED_CURVE, 3).
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Dialyzer types
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-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()]}.
+
+-type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} |
+ #client_key_exchange{} | #finished{} | #certificate_verify{} |
+ #hello_request{} | #next_protocol{}.
+
+
-endif. % -ifdef(ssl_handshake).
diff --git a/lib/ssl/src/ssl_internal.hrl b/lib/ssl/src/ssl_internal.hrl
index de8d20d399..96e3280fb5 100644
--- a/lib/ssl/src/ssl_internal.hrl
+++ b/lib/ssl/src/ssl_internal.hrl
@@ -37,7 +37,6 @@
-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().
@@ -50,6 +49,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,6 +57,7 @@
-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").
@@ -71,6 +72,8 @@
-define(ALL_SUPPORTED_VERSIONS, ['tlsv1.2', 'tlsv1.1', tlsv1, sslv3]).
-define(MIN_SUPPORTED_VERSIONS, ['tlsv1.1', tlsv1, sslv3]).
+-define(ALL_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]).
+-define(MIN_DATAGRAM_SUPPORTED_VERSIONS, ['dtlsv1.2', dtlsv1]).
-record(ssl_options, {
versions, % 'tlsv1.2' | 'tlsv1.1' | tlsv1 | sslv3
@@ -124,6 +127,10 @@
active = true
}).
+-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_record.erl b/lib/ssl/src/ssl_record.erl
new file mode 100644
index 0000000000..50a45dc16b
--- /dev/null
+++ b/lib/ssl/src/ssl_record.erl
@@ -0,0 +1,439 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013-2013. 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: 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/1,
+ 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]).
+
+-export([is_correct_mac/2]).
+
+%%====================================================================
+%% 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) ->
+ 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},
+ 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{current_write = Current,
+ pending_write = Pending},
+ write) ->
+ NewCurrent = Pending#connection_state{epoch = dtls_next_epoch(Current),
+ 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_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(), 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).
+
+%%--------------------------------------------------------------------
+-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).
+
+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)].
+
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
+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>>.
+
+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) ->
+ #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(?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) 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.
+
+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..c17fa53a62 100644
--- a/lib/ssl/src/ssl_record.hrl
+++ b/lib/ssl/src/ssl_record.hrl
@@ -29,6 +29,18 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% 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
+ }).
-record(connection_states, {
current_read,
@@ -56,17 +68,7 @@
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
%% Sequence numbers can not wrap so when max is about to be reached we should renegotiate.
diff --git a/lib/ssl/src/ssl_ssl2.erl b/lib/ssl/src/ssl_v2.erl
index a9ab6a2678..07876366f1 100644
--- a/lib/ssl/src/ssl_ssl2.erl
+++ b/lib/ssl/src/ssl_v2.erl
@@ -1,30 +1,30 @@
%%
%% %CopyrightBegin%
-%%
-%% Copyright Ericsson AB 2007-2011. All Rights Reserved.
-%%
+%%
+%% Copyright Ericsson AB 2007-2013. 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
+%% Purpose: Handles sslv2 hello as clients supporting sslv2 and higher
%% will send an sslv2 hello.
%%----------------------------------------------------------------------
--module(ssl_ssl2).
-
+-module(ssl_v2).
+
-export([client_random/2]).
client_random(ChallengeData, 32) ->
diff --git a/lib/ssl/src/ssl_ssl3.erl b/lib/ssl/src/ssl_v3.erl
index 013c27ebb5..d477b3df81 100644
--- a/lib/ssl/src/ssl_ssl3.erl
+++ b/lib/ssl/src/ssl_v3.erl
@@ -22,14 +22,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 +40,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 +62,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 +95,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,
@@ -133,7 +133,7 @@ setup_keys(MasterSecret, ServerRandom, ClientRandom, HS, KML, _EKML, IVS) ->
-spec suites() -> [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,
@@ -143,7 +143,7 @@ suites() ->
?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_IDEA_CBC_SHA,
?TLS_RSA_WITH_RC4_128_SHA,
?TLS_RSA_WITH_RC4_128_MD5,
?TLS_RSA_WITH_DES_CBC_SHA
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index 0415ea6ecc..5618837506 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -106,10 +106,6 @@
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
@@ -367,7 +363,6 @@ hello(#hello_request{}, #state{role = client} = State0) ->
next_state(hello, hello, Record, State);
hello(#server_hello{cipher_suite = CipherSuite,
- hash_signs = HashSign,
compression_method = Compression} = Hello,
#state{session = #session{session_id = OldId},
connection_states = ConnectionStates0,
@@ -392,8 +387,6 @@ hello(#server_hello{cipher_suite = CipherSuite,
end,
State = State0#state{key_algorithm = KeyAlgorithm,
- hashsign_algorithm =
- negotiated_hashsign(HashSign, KeyAlgorithm, Version),
negotiated_version = Version,
connection_states = ConnectionStates,
premaster_secret = PremasterSecret,
@@ -410,27 +403,27 @@ hello(#server_hello{cipher_suite = CipherSuite,
end;
hello(Hello = #client_hello{client_version = ClientVersion,
- hash_signs = HashSigns},
+ extensions = #hello_extensions{hash_signs = HashSigns}},
State = #state{connection_states = ConnectionStates0,
port = Port, session = #session{own_certificate = Cert} = Session0,
renegotiation = {Renegotiation, _},
- session_cache = Cache,
+ session_cache = Cache,
session_cache_cb = CacheCb,
ssl_options = SslOpts}) ->
-
- HashSign = tls_handshake:select_hashsign(HashSigns, Cert),
+ HashSign = ssl_handshake:select_hashsign(HashSigns, Cert),
case tls_handshake:hello(Hello, SslOpts, {Port, Session0, Cache, CacheCb,
ConnectionStates0, Cert}, Renegotiation) of
- {Version, {Type, #session{cipher_suite = CipherSuite} = Session}, ConnectionStates, ProtocolsToAdvertise,
- EcPointFormats, EllipticCurves} ->
- {KeyAlgorithm, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
- NH = negotiated_hashsign(HashSign, KeyAlgorithm, Version),
- do_server_hello(Type, ProtocolsToAdvertise,
- EcPointFormats, EllipticCurves,
+ {Version, {Type, #session{cipher_suite = CipherSuite} = Session},
+ ConnectionStates,
+ #hello_extensions{ec_point_formats = EcPointFormats,
+ elliptic_curves = EllipticCurves} = ServerHelloExt} ->
+ {KeyAlg, _, _, _} = ssl_cipher:suite_definition(CipherSuite),
+ NegotiatedHashSign = negotiated_hashsign(HashSign, KeyAlg, Version),
+ do_server_hello(Type, ServerHelloExt,
State#state{connection_states = ConnectionStates,
negotiated_version = Version,
session = Session,
- hashsign_algorithm = NH,
+ hashsign_algorithm = NegotiatedHashSign,
client_ecc = {EllipticCurves, EcPointFormats}});
#alert{} = Alert ->
handle_own_alert(Alert, ClientVersion, hello, State)
@@ -456,11 +449,11 @@ abbreviated(#finished{verify_data = Data} = Finished,
session = #session{master_secret = MasterSecret},
connection_states = ConnectionStates0} =
State) ->
- case tls_handshake:verify_connection(Version, Finished, client,
+ case ssl_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),
+ ConnectionStates = ssl_record:set_client_verify_data(current_both, Data, ConnectionStates0),
next_state_connection(abbreviated,
ack_connection(State#state{connection_states = ConnectionStates}));
#alert{} = Alert ->
@@ -472,11 +465,11 @@ abbreviated(#finished{verify_data = Data} = Finished,
session = #session{master_secret = MasterSecret},
negotiated_version = Version,
connection_states = ConnectionStates0} = State) ->
- case tls_handshake:verify_connection(Version, Finished, server,
+ case ssl_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),
+ ConnectionStates1 = ssl_record:set_server_verify_data(current_read, Data, ConnectionStates0),
{ConnectionStates, Handshake} =
finalize_handshake(State#state{connection_states = ConnectionStates1}, abbreviated),
next_state_connection(abbreviated,
@@ -531,7 +524,7 @@ certify(#certificate{} = Cert,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
ssl_options = Opts} = State) ->
- case tls_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth,
+ case ssl_handshake:certify(Cert, CertDbHandle, CertDbRef, Opts#ssl_options.depth,
Opts#ssl_options.verify,
Opts#ssl_options.verify_fun, Role) of
{PeerCert, PublicKeyInfo} ->
@@ -563,7 +556,7 @@ certify(#server_key_exchange{} = Msg,
certify(#certificate_request{hashsign_algorithms = HashSigns},
#state{session = #session{own_certificate = Cert}} = State0) ->
- HashSign = tls_handshake:select_hashsign(HashSigns, Cert),
+ HashSign = ssl_handshake:select_hashsign(HashSigns, Cert),
{Record, State} = next_record(State0#state{client_certificate_requested = true}),
next_state(certify, certify, Record, State#state{cert_hashsign_algorithm = HashSign});
@@ -613,7 +606,7 @@ certify(#server_hello_done{},
negotiated_version = Version,
premaster_secret = undefined,
role = client} = State0) ->
- case tls_handshake:master_secret(Version, Session,
+ case ssl_handshake:master_secret(tls_record, Version, Session,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
State = State0#state{connection_states = ConnectionStates},
@@ -629,7 +622,7 @@ certify(#server_hello_done{},
negotiated_version = Version,
premaster_secret = PremasterSecret,
role = client} = State0) ->
- case tls_handshake:master_secret(Version, PremasterSecret,
+ case ssl_handshake:master_secret(tls_record, Version, PremasterSecret,
ConnectionStates0, client) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
@@ -650,7 +643,7 @@ certify(#client_key_exchange{} = Msg,
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)
+ certify_client_key_exchange(ssl_handshake:decode_client_key(Keys, KeyAlg, Version), State)
catch
#alert{} = Alert ->
handle_own_alert(Alert, Version, certify, State)
@@ -668,8 +661,8 @@ certify_client_key_exchange(#encrypted_premaster_secret{premaster_secret= EncPMS
connection_states = ConnectionStates0,
session = Session0,
private_key = Key} = State0) ->
- PremasterSecret = tls_handshake:decrypt_premaster_secret(EncPMS, Key),
- case tls_handshake:master_secret(Version, PremasterSecret,
+ PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key),
+ case ssl_handshake:master_secret(tls_record, Version, PremasterSecret,
ConnectionStates0, server) of
{MasterSecret, ConnectionStates} ->
Session = Session0#session{master_secret = MasterSecret},
@@ -735,7 +728,7 @@ certify_client_key_exchange(#client_rsa_psk_identity{
#encrypted_premaster_secret{premaster_secret= EncPMS}},
#state{negotiated_version = Version,
private_key = Key} = State0) ->
- PremasterSecret = tls_handshake:decrypt_premaster_secret(EncPMS, Key),
+ PremasterSecret = ssl_handshake:decrypt_premaster_secret(EncPMS, Key),
case server_rsa_psk_master_secret(PskIdentity, PremasterSecret, State0) of
#state{} = State1 ->
{Record, State} = next_record(State1),
@@ -774,8 +767,8 @@ cipher(#certificate_verify{signature = Signature, hashsign_algorithm = CertHashS
tls_handshake_history = Handshake
} = State0) ->
- HashSign = tls_handshake:select_cert_hashsign(CertHashSign, Algo, Version),
- case tls_handshake:certificate_verify(Signature, PublicKeyInfo,
+ HashSign = ssl_handshake:select_cert_hashsign(CertHashSign, Algo, Version),
+ case ssl_handshake:certificate_verify(Signature, PublicKeyInfo,
Version, HashSign, MasterSecret, Handshake) of
valid ->
{Record, State} = next_record(State0),
@@ -798,7 +791,7 @@ cipher(#finished{verify_data = Data} = Finished,
= Session0,
connection_states = ConnectionStates0,
tls_handshake_history = Handshake0} = State) ->
- case tls_handshake:verify_connection(Version, Finished,
+ case ssl_handshake:verify_connection(Version, Finished,
opposite_role(Role),
get_current_connection_state_prf(ConnectionStates0, read),
MasterSecret, Handshake0) of
@@ -1033,7 +1026,7 @@ 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),
+ ssl_record:current_connection_state(ConnectionStates, read),
SecParams = ConnectionState#connection_state.security_parameters,
#security_parameters{master_secret = MasterSecret,
client_random = ClientRandom,
@@ -1048,7 +1041,7 @@ handle_sync_event({prf, Secret, Label, Seed, WantedLength}, _, StateName,
(client_random, Acc) -> [ClientRandom|Acc];
(server_random, Acc) -> [ServerRandom|Acc]
end, [], Seed)),
- tls_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength)
+ ssl_handshake:prf(Version, SecretToUse, Label, SeedToUse, WantedLength)
catch
exit:_ -> {error, badarg};
error:Reason -> {error, Reason}
@@ -1414,7 +1407,7 @@ certify_client(#state{client_certificate_requested = true, role = client,
session = #session{own_certificate = OwnCert},
socket = Socket,
tls_handshake_history = Handshake0} = State) ->
- Certificate = tls_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
+ Certificate = ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, client),
{BinCert, ConnectionStates, Handshake} =
encode_handshake(Certificate, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinCert),
@@ -1434,7 +1427,7 @@ verify_client_cert(#state{client_certificate_requested = true, role = client,
cert_hashsign_algorithm = HashSign,
tls_handshake_history = Handshake0} = State) ->
- case tls_handshake:client_certificate_verify(OwnCert, MasterSecret,
+ case ssl_handshake:client_certificate_verify(OwnCert, MasterSecret,
Version, HashSign, PrivateKey, Handshake0) of
#certificate_verify{} = Verified ->
{BinVerified, ConnectionStates, Handshake} =
@@ -1451,21 +1444,17 @@ verify_client_cert(#state{client_certificate_requested = true, role = client,
verify_client_cert(#state{client_certificate_requested = false} = State) ->
State.
-do_server_hello(Type, NextProtocolsToSend,
- EcPointFormats, EllipticCurves,
+do_server_hello(Type, #hello_extensions{next_protocol_negotiation = NextProtocols} = ServerHelloExt,
#state{negotiated_version = Version,
session = #session{session_id = SessId},
- connection_states = ConnectionStates0,
- renegotiation = {Renegotiation, _}}
+ connection_states = ConnectionStates0}
= State0) when is_atom(Type) ->
ServerHello =
- tls_handshake:server_hello(SessId, Version,
- ConnectionStates0, Renegotiation,
- NextProtocolsToSend, EcPointFormats, EllipticCurves),
+ tls_handshake:server_hello(SessId, Version, ConnectionStates0, ServerHelloExt),
State = server_hello(ServerHello,
State0#state{expecting_next_protocol_negotiation =
- NextProtocolsToSend =/= undefined}),
+ NextProtocols =/= undefined}),
case Type of
new ->
new_server_hello(ServerHello, State);
@@ -1496,7 +1485,7 @@ resumed_server_hello(#state{session = Session,
connection_states = ConnectionStates0,
negotiated_version = Version} = State0) ->
- case tls_handshake:master_secret(Version, Session,
+ case ssl_handshake:master_secret(tls_record, Version, Session,
ConnectionStates0, server) of
{_, ConnectionStates1} ->
State1 = State0#state{connection_states = ConnectionStates1,
@@ -1525,7 +1514,7 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0,
session_cache = Cache,
session_cache_cb = CacheCb} = State0) ->
Session = CacheCb:lookup(Cache, {{Host, Port}, SessId}),
- case tls_handshake:master_secret(Version, Session,
+ case ssl_handshake:master_secret(tls_record, Version, Session,
ConnectionStates0, client) of
{_, ConnectionStates} ->
{Record, State} =
@@ -1584,7 +1573,7 @@ server_hello_done(#state{transport_cb = Transport,
connection_states = ConnectionStates0,
tls_handshake_history = Handshake0} = State) ->
- HelloDone = tls_handshake:server_hello_done(),
+ HelloDone = ssl_handshake:server_hello_done(),
{BinHelloDone, ConnectionStates, Handshake} =
encode_handshake(HelloDone, Version, ConnectionStates0, Handshake0),
@@ -1604,7 +1593,7 @@ certify_server(#state{transport_cb = Transport,
cert_db = CertDbHandle,
cert_db_ref = CertDbRef,
session = #session{own_certificate = OwnCert}} = State) ->
- case tls_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
+ case ssl_handshake:certificate(OwnCert, CertDbHandle, CertDbRef, server) of
CertMsg = #certificate{} ->
{BinCertMsg, ConnectionStates, Handshake} =
encode_handshake(CertMsg, Version, ConnectionStates0, Handshake0),
@@ -1633,11 +1622,11 @@ key_exchange(#state{role = server, key_algorithm = Algo,
Algo == dh_anon ->
DHKeys = public_key:generate_key(Params),
ConnectionState =
- tls_record:pending_connection_state(ConnectionStates0, read),
+ ssl_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,
+ Msg = ssl_handshake:key_exchange(server, Version, {dh, DHKeys, Params,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
@@ -1665,11 +1654,11 @@ key_exchange(#state{role = server, key_algorithm = Algo,
ECDHKeys = public_key:generate_key(select_curve(State)),
ConnectionState =
- tls_record:pending_connection_state(ConnectionStates0, read),
+ ssl_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,
+ Msg = ssl_handshake:key_exchange(server, Version, {ecdh, ECDHKeys,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
@@ -1694,11 +1683,11 @@ key_exchange(#state{role = server, key_algorithm = psk,
transport_cb = Transport
} = State) ->
ConnectionState =
- tls_record:pending_connection_state(ConnectionStates0, read),
+ ssl_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,
+ Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
@@ -1721,11 +1710,11 @@ key_exchange(#state{role = server, key_algorithm = dhe_psk,
} = State) ->
DHKeys = public_key:generate_key(Params),
ConnectionState =
- tls_record:pending_connection_state(ConnectionStates0, read),
+ ssl_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,
+ Msg = ssl_handshake:key_exchange(server, Version, {dhe_psk, PskIdentityHint, DHKeys, Params,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
@@ -1750,11 +1739,11 @@ key_exchange(#state{role = server, key_algorithm = rsa_psk,
transport_cb = Transport
} = State) ->
ConnectionState =
- tls_record:pending_connection_state(ConnectionStates0, read),
+ ssl_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,
+ Msg = ssl_handshake:key_exchange(server, Version, {psk, PskIdentityHint,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
@@ -1786,11 +1775,11 @@ key_exchange(#state{role = server, key_algorithm = Algo,
Keys0
end,
ConnectionState =
- tls_record:pending_connection_state(ConnectionStates0, read),
+ ssl_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,
+ Msg = ssl_handshake:key_exchange(server, Version, {srp, Keys, SrpParams,
HashSignAlgo, ClientRandom,
ServerRandom,
PrivateKey}),
@@ -1826,7 +1815,7 @@ key_exchange(#state{role = client,
when Algorithm == dhe_dss;
Algorithm == dhe_rsa;
Algorithm == dh_anon ->
- Msg = tls_handshake:key_exchange(client, Version, {dh, DhPubKey}),
+ Msg = ssl_handshake:key_exchange(client, Version, {dh, DhPubKey}),
{BinMsg, ConnectionStates, Handshake} =
encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinMsg),
@@ -1843,7 +1832,7 @@ key_exchange(#state{role = client,
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}),
+ Msg = ssl_handshake:key_exchange(client, Version, {ecdh, Keys}),
{BinMsg, ConnectionStates, Handshake} =
encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinMsg),
@@ -1857,7 +1846,7 @@ key_exchange(#state{role = client,
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}),
+ Msg = ssl_handshake:key_exchange(client, Version, {psk, SslOpts#ssl_options.psk_identity}),
{BinMsg, ConnectionStates, Handshake} =
encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinMsg),
@@ -1872,7 +1861,7 @@ key_exchange(#state{role = client,
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}),
+ Msg = ssl_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),
@@ -1905,7 +1894,7 @@ key_exchange(#state{role = client,
when Algorithm == srp_dss;
Algorithm == srp_rsa;
Algorithm == srp_anon ->
- Msg = tls_handshake:key_exchange(client, Version, {srp, ClientPubKey}),
+ Msg = ssl_handshake:key_exchange(client, Version, {srp, ClientPubKey}),
{BinMsg, ConnectionStates, Handshake} =
encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinMsg),
@@ -1922,7 +1911,7 @@ rsa_key_exchange(Version, PremasterSecret, PublicKeyInfo = {Algorithm, _, _})
Algorithm == ?sha384WithRSAEncryption;
Algorithm == ?sha512WithRSAEncryption
->
- tls_handshake:key_exchange(client, Version,
+ ssl_handshake:key_exchange(client, Version,
{premaster_secret, PremasterSecret,
PublicKeyInfo});
rsa_key_exchange(_, _, _) ->
@@ -1938,7 +1927,7 @@ rsa_psk_key_exchange(Version, PskIdentity, PremasterSecret, PublicKeyInfo = {Alg
Algorithm == ?sha384WithRSAEncryption;
Algorithm == ?sha512WithRSAEncryption
->
- tls_handshake:key_exchange(client, Version,
+ ssl_handshake:key_exchange(client, Version,
{psk_premaster_secret, PskIdentity, PremasterSecret,
PublicKeyInfo});
rsa_psk_key_exchange(_, _, _, _) ->
@@ -1952,7 +1941,11 @@ request_client_cert(#state{ssl_options = #ssl_options{verify = verify_peer},
negotiated_version = Version,
socket = Socket,
transport_cb = Transport} = State) ->
- Msg = tls_handshake:certificate_request(ConnectionStates0, CertDbHandle, CertDbRef, Version),
+ #connection_state{security_parameters =
+ #security_parameters{cipher_suite = CipherSuite}} =
+ ssl_record:pending_connection_state(ConnectionStates0, read),
+ Msg = ssl_handshake:certificate_request(CipherSuite, CertDbHandle, CertDbRef, Version),
+
{BinMsg, ConnectionStates, Handshake} =
encode_handshake(Msg, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinMsg),
@@ -1967,7 +1960,7 @@ finalize_handshake(State, StateName) ->
ConnectionStates0 = cipher_protocol(State),
ConnectionStates =
- tls_record:activate_pending_connection_state(ConnectionStates0,
+ ssl_record:activate_pending_connection_state(ConnectionStates0,
write),
State1 = State#state{connection_states = ConnectionStates},
@@ -1985,7 +1978,7 @@ next_protocol(#state{transport_cb = Transport, socket = Socket,
next_protocol = NextProtocol,
connection_states = ConnectionStates0,
tls_handshake_history = Handshake0} = State) ->
- NextProtocolMessage = tls_handshake:next_protocol(NextProtocol),
+ NextProtocolMessage = ssl_handshake:next_protocol(NextProtocol),
{BinMsg, ConnectionStates, Handshake} = encode_handshake(NextProtocolMessage, Version, ConnectionStates0, Handshake0),
Transport:send(Socket, BinMsg),
State#state{connection_states = ConnectionStates,
@@ -2007,7 +2000,7 @@ finished(#state{role = Role, socket = Socket, negotiated_version = Version,
connection_states = ConnectionStates0,
tls_handshake_history = Handshake0}, StateName) ->
MasterSecret = Session#session.master_secret,
- Finished = tls_handshake:finished(Version, Role,
+ Finished = ssl_handshake:finished(Version, Role,
get_current_connection_state_prf(ConnectionStates0, write),
MasterSecret, Handshake0),
ConnectionStates1 = save_verify_data(Role, Finished, ConnectionStates0, StateName),
@@ -2017,18 +2010,19 @@ finished(#state{role = Role, socket = Socket, negotiated_version = Version,
{ConnectionStates, Handshake}.
save_verify_data(client, #finished{verify_data = Data}, ConnectionStates, certify) ->
- tls_record:set_client_verify_data(current_write, Data, ConnectionStates);
+ ssl_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);
+ ssl_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);
+ ssl_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).
+ ssl_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),
+
+ Params = ssl_handshake:decode_server_key(Keys, KeyAlg, Version),
HashSign = negotiated_hashsign(Params#server_key_params.hashsign, KeyAlg, Version),
case is_anonymous(KeyAlg) of
true ->
@@ -2046,15 +2040,15 @@ verify_server_key(#server_key_params{params = Params,
public_key_info = PubKeyInfo,
connection_states = ConnectionStates} = State) ->
ConnectionState =
- tls_record:pending_connection_state(ConnectionStates, read),
+ ssl_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,
+ Hash = ssl_handshake:server_key_exchange_hash(HashAlgo,
<<ClientRandom/binary,
ServerRandom/binary,
EncParams/binary>>),
- case tls_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of
+ case ssl_handshake:verify_signature(Version, Hash, HashSign, Signature, PubKeyInfo) of
true ->
server_master_secret(Params, State);
false ->
@@ -2090,7 +2084,7 @@ master_from_premaster_secret(PremasterSecret,
#state{session = Session,
negotiated_version = Version, role = Role,
connection_states = ConnectionStates0} = State) ->
- case tls_handshake:master_secret(Version, PremasterSecret,
+ case ssl_handshake:master_secret(tls_record, Version, PremasterSecret,
ConnectionStates0, Role) of
{MasterSecret, ConnectionStates} ->
State#state{
@@ -2243,12 +2237,12 @@ client_srp_master_secret(Generator, Prime, Salt, ServerPub, ClientKeys,
end.
cipher_role(client, Data, Session, #state{connection_states = ConnectionStates0} = State) ->
- ConnectionStates = tls_record:set_server_verify_data(current_both, Data, ConnectionStates0),
+ ConnectionStates = ssl_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),
+ ConnectionStates1 = ssl_record:set_client_verify_data(current_read, Data, ConnectionStates0),
{ConnectionStates, Handshake} =
finalize_handshake(State#state{connection_states = ConnectionStates1,
session = Session}, cipher),
@@ -2258,16 +2252,16 @@ cipher_role(server, Data, Session, #state{connection_states = ConnectionStates0
tls_handshake_history =
Handshake})).
encode_alert(#alert{} = Alert, Version, ConnectionStates) ->
- tls_record:encode_alert_record(Alert, Version, ConnectionStates).
+ ssl_record:encode_alert_record(Alert, Version, ConnectionStates).
encode_change_cipher(#change_cipher_spec{}, Version, ConnectionStates) ->
- tls_record:encode_change_cipher_spec(Version, ConnectionStates).
+ ssl_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),
+ ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
{E, ConnectionStates1, Handshake1}.
encode_packet(Data, #socket_options{packet=Packet}) ->
@@ -2362,7 +2356,7 @@ write_application_data(Data0, From, #state{socket = Socket,
renegotiate(State#state{send_queue = queue:in_r({From, Data}, SendQueue),
renegotiation = {true, internal}});
false ->
- {Msgs, ConnectionStates} = tls_record:encode_data(Data, Version, ConnectionStates0),
+ {Msgs, ConnectionStates} = ssl_record:encode_data(Data, Version, ConnectionStates0),
Result = Transport:send(Socket, Msgs),
{reply, Result,
connection, State#state{connection_states = ConnectionStates}, get_timeout(State)}
@@ -2567,7 +2561,7 @@ 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),
+ ssl_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) ->
@@ -2619,7 +2613,7 @@ next_state_connection(StateName, #state{send_queue = Queue0,
case queue:out(Queue0) of
{{value, {From, Data}}, Queue} ->
{Msgs, ConnectionStates} =
- tls_record:encode_data(Data, Version, ConnectionStates0),
+ ssl_record:encode_data(Data, Version, ConnectionStates0),
Result = Transport:send(Socket, Msgs),
gen_fsm:reply(From, Result),
next_state_connection(StateName,
@@ -2664,7 +2658,7 @@ invalidate_session(server, _, Port, Session) ->
initial_state(Role, Host, Port, Socket, {SSLOptions, SocketOptions}, User,
{CbModule, DataTag, CloseTag, ErrorTag}) ->
- ConnectionStates = tls_record:init_connection_states(Role),
+ ConnectionStates = ssl_record:init_connection_states(Role),
SessionCacheCb = case application:get_env(ssl, session_cb) of
{ok, Cb} when is_atom(Cb) ->
@@ -2926,11 +2920,11 @@ renegotiate(#state{role = server,
transport_cb = Transport,
negotiated_version = Version,
connection_states = ConnectionStates0} = State0) ->
- HelloRequest = tls_handshake:hello_request(),
+ HelloRequest = ssl_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),
+ ssl_record:encode_handshake(Frag, Version, ConnectionStates0),
Transport:send(Socket, BinMsg),
{Record, State} = next_record(State0#state{connection_states =
ConnectionStates,
@@ -3005,12 +2999,45 @@ handle_trusted_certs_db(#state{cert_db_ref = Ref,
end.
get_current_connection_state_prf(CStates, Direction) ->
- CS = tls_record:current_connection_state(CStates, Direction),
+ CS = ssl_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 = ssl_record:pending_connection_state(CStates, Direction),
CS#connection_state.security_parameters#security_parameters.prf_algorithm.
+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.
+negotiated_hashsign(undefined, Algo, Version) ->
+ default_hashsign(Version, Algo);
+negotiated_hashsign(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:
@@ -3025,11 +3052,6 @@ get_pending_connection_state_prf(CStates, Direction) ->
%% - If the negotiated key exchange algorithm is one of (ECDH_ECDSA,
%% ECDHE_ECDSA), behave as if the client had sent value {sha1,ecdsa}.
-negotiated_hashsign(undefined, Algo, Version) ->
- default_hashsign(Version, Algo);
-negotiated_hashsign(HashSign = {_, _}, _, _) ->
- HashSign.
-
default_hashsign(_Version = {Major, Minor}, KeyExchange)
when Major >= 3 andalso Minor >= 3 andalso
(KeyExchange == rsa orelse
@@ -3065,35 +3087,6 @@ default_hashsign(_Version, KeyExchange)
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.
-
select_curve(#state{client_ecc = {[Curve|_], _}}) ->
{namedCurve, Curve};
select_curve(_) ->
diff --git a/lib/ssl/src/tls_handshake.erl b/lib/ssl/src/tls_handshake.erl
index 6cc6e9e885..02bfa69fc5 100644
--- a/lib/ssl/src/tls_handshake.erl
+++ b/lib/ssl/src/tls_handshake.erl
@@ -18,7 +18,8 @@
%%
%%----------------------------------------------------------------------
-%% 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).
@@ -31,24 +32,9 @@
-include("ssl_srp.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/4, 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, select_hashsign/2,
- select_cert_hashsign/3]).
-
--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{}.
-
--define(NAMED_CURVE_TYPE, 3).
+-export([client_hello/8, server_hello/4, hello/4,
+ get_tls_handshake/3, encode_handshake/2, decode_handshake/3,
+ init_handshake_history/0, update_handshake_history/2]).
%%====================================================================
%% Internal application API
@@ -66,515 +52,105 @@ client_hello(Host, Port, ConnectionStates,
} = 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),
+ CipherSuites = ssl_handshake:available_suites(UserSuites, Version),
+
+ Extensions = ssl_handshake:client_hello_extensions(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 = cipher_suites(Ciphers, Renegotiation),
- compression_methods = tls_record:compressions(),
+ cipher_suites = ssl_handshake:cipher_suites(CipherSuites, Renegotiation),
+ compression_methods = ssl_record:compressions(),
random = SecParams#security_parameters.client_random,
-
- renegotiation_info =
- renegotiation_info(client, ConnectionStates, Renegotiation),
- srp = SRP,
- hash_signs = advertised_hash_signs(Version),
- ec_point_formats = EcPointFormats,
- elliptic_curves = EllipticCurves,
- next_protocol_negotiation =
- encode_client_protocol_negotiation(SslOpts#ssl_options.next_protocol_selector, Renegotiation)
+ extensions = Extensions
}.
-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{}.
+-spec server_hello(#session{}, tls_version(), #connection_states{},
+ #hello_extensions{}) -> #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),
+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,
- 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},
boolean()) ->
{tls_version(), session_id(), #connection_states{}, binary() | undefined}|
- {tls_version(), {resumed | new, #session{}}, #connection_states{}, [binary()] | undefined,
+ {tls_version(), {resumed | new, #session{}}, #connection_states{},
+ [binary()] | undefined,
[oid()] | undefined, [oid()] | 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) ->
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_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,
+ session_id = SugesstedId,
+ cipher_suites = CipherSuites,
+ compression_methods = Compressions,
+ random = Random,
+ extensions = HelloExt},
#ssl_options{versions = Versions} = SslOpts,
{Port, Session0, Cache, CacheCb, ConnectionStates0, Cert}, Renegotiation) ->
- Version = select_version(ClientVersion, Versions),
+ Version = ssl_handshake:select_version(tls_record, 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),
+ = ssl_handshake:select_session(SugesstedId, CipherSuites, Compressions,
+ 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
+ handle_hello_extensions(Version, Type, Random, HelloExt,
+ SslOpts, Session1, ConnectionStates0,
+ 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, _} = HashSign,
- 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 = HashSign}
- 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(), tls_version()) ->
- #certificate_request{}.
-%%
-%% Description: Creates a certificate_request message, called by the server.
-%%--------------------------------------------------------------------
-certificate_request(ConnectionStates, CertDbHandle, CertDbRef, Version) ->
- #connection_state{security_parameters =
- #security_parameters{cipher_suite = CipherSuite}} =
- tls_record:pending_connection_state(ConnectionStates, read),
- Types = certificate_types(CipherSuite),
- HashSigns = advertised_hash_signs(Version),
- 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)
- end.
-%%--------------------------------------------------------------------
--spec server_hello_done() -> #server_hello_done{}.
-%%
-%% 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].
@@ -592,30 +168,6 @@ 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().
%%
@@ -645,1231 +197,99 @@ update_handshake_history(Handshake, % special-case SSL2 client hello
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)}.
-
-
-%%--------------------------------------------------------------------
--spec select_hashsign(#hash_sign_algos{}| undefined, undefined | term()) ->
- [{atom(), atom()}] | undefined.
-
-%%
-%% Description:
-%%--------------------------------------------------------------------
-select_hashsign(_, undefined) ->
- {null, anon};
-select_hashsign(undefined, Cert) ->
- #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- select_cert_hashsign(undefined, Algo, {undefined, undefined});
-select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert) ->
- #'OTPCertificate'{tbsCertificate = TBSCert} =public_key:pkix_decode_cert(Cert, otp),
- #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo,
- DefaultHashSign = {_, Sign} = select_cert_hashsign(undefined, Algo, {undefined, undefined}),
- case lists:filter(fun({sha, dsa}) ->
- true;
- ({_, dsa}) ->
- false;
- ({Hash, S}) when S == Sign ->
- ssl_cipher:is_acceptable_hash(Hash, proplists:get_value(hashs, crypto:supports()));
- (_) ->
- false
- end, HashSigns) of
- [] ->
- DefaultHashSign;
- [HashSign| _] ->
- HashSign
- end.
-%%--------------------------------------------------------------------
--spec select_cert_hashsign(#hash_sign_algos{}| undefined, oid(), tls_version()) ->
- [{atom(), atom()}].
-
-%%
-%% Description:
-%%--------------------------------------------------------------------
-select_cert_hashsign(HashSign, _, {Major, Minor}) when HashSign =/= undefined andalso Major >= 3 andalso Minor >= 3 ->
- HashSign;
-select_cert_hashsign(undefined,?'id-ecPublicKey', _) ->
- {sha, ecdsa};
-select_cert_hashsign(undefined, ?rsaEncryption, _) ->
- {md5sha, rsa};
-select_cert_hashsign(undefined, ?'id-dsa', _) ->
- {sha, dsa}.
-
-%%--------------------------------------------------------------------
-%%% 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]);
+ Handshake = decode_handshake(Version, Type, Body),
+ get_tls_handshake_aux(Version, Rest, [{Handshake,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
- true ->
- {ok, ConnectionStates};
- 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)
- 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};
+%%--------------------------------------------------------------------
+%%% Internal functions
+%%--------------------------------------------------------------------
-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),
+decode_handshake(_Version, ?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor),
?UINT16(CSLength), ?UINT16(0),
?UINT16(CDLength),
CipherSuites:CSLength/binary,
ChallengeData:CDLength/binary>>) ->
#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,
+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 = 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),
+ 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)).
+decode_handshake(Version, Tag, Msg) ->
+ ssl_handshake:decode_handshake(Version, Tag, Msg).
-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, _/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) ->
- <<?UINT16(_), 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) ->
- <<?BYTE(_), 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]).
-
-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) ->
- [].
-
-hello_extensions(RenegotiationInfo, NextProtocolNegotiation) ->
- hello_extensions(RenegotiationInfo) ++ next_protocol_extension(NextProtocolNegotiation).
-
-hello_extensions(RenegotiationInfo, SRP, NextProtocolNegotiation) ->
- hello_extensions(RenegotiationInfo)
- ++ hello_extensions(SRP)
- ++ next_protocol_extension(NextProtocolNegotiation).
-
-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).
+ ExtensionsBin = ssl_handshake:encode_hello_extensions(HelloExtensions),
-%% 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) ->
- [].
+ {?CLIENT_HELLO, <<?BYTE(Major), ?BYTE(Minor), Random:32/binary,
+ ?BYTE(SIDLength), SessionID/binary,
+ ?UINT16(CsLength), BinCipherSuites/binary,
+ ?BYTE(CmLength), BinCompMethods/binary, ExtensionsBin/binary>>};
-next_protocol_extension(undefined) ->
- [];
-next_protocol_extension(#next_protocol_negotiation{} = Info) ->
- [Info].
+enc_handshake(HandshakeMsg, Version) ->
+ ssl_handshake:encode_handshake(HandshakeMsg, Version).
-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}}
+handle_hello_extensions(Version, Type, Random, HelloExt, SslOpts, Session0, ConnectionStates0, Renegotiation) ->
+ try ssl_handshake:handle_client_hello_extensions(tls_record, Random, HelloExt, Version, SslOpts,
+ Session0, ConnectionStates0, Renegotiation) of
+ {Session, ConnectionStates, ServerHelloExt} ->
+ {Version, {Type, Session}, ConnectionStates, ServerHelloExt}
+ catch throw:Alert ->
+ Alert
end.
-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_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)
+ Alert;
+ {ConnectionStates, Protocol} ->
+ {Version, SessionId, ConnectionStates, Protocol}
end.
-handle_next_protocol_extension(Hello, Renegotiation, SslOpts)->
- case handle_next_protocol_on_server(Hello, Renegotiation, SslOpts) of
- #alert{} = Alert ->
- throw(Alert);
- ProtocolsToAdvertise ->
- ProtocolsToAdvertise
- 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)>>.
-
--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)).
-
-advertised_hash_signs({Major, Minor}) when Major >= 3 andalso Minor >= 3 ->
- 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 = crypto:supports(),
- HasECC = proplists:get_bool(ecdsa, proplists:get_value(public_keys, CryptoSupport)),
- Hashs = proplists:get_value(hashs, CryptoSupport),
- #hash_sign_algos{hash_sign_algos =
- lists:filter(fun({Hash, ecdsa}) -> HasECC andalso proplists:get_bool(Hash, Hashs);
- ({Hash, _}) -> proplists:get_bool(Hash, Hashs) end, HashSigns)};
-advertised_hash_signs(_) ->
- undefined.
diff --git a/lib/ssl/src/tls_handshake.hrl b/lib/ssl/src/tls_handshake.hrl
index abf1b5abb6..dbe930cb90 100644
--- a/lib/ssl/src/tls_handshake.hrl
+++ b/lib/ssl/src/tls_handshake.hrl
@@ -34,12 +34,9 @@
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
}).
+-type tls_handshake() :: #client_hello{} | ssl_handshake().
+
-endif. % -ifdef(tls_handshake).
diff --git a/lib/ssl/src/tls_record.erl b/lib/ssl/src/tls_record.erl
index 1409a04763..54cf8d0b80 100644
--- a/lib/ssl/src/tls_record.erl
+++ b/lib/ssl/src/tls_record.erl
@@ -19,8 +19,7 @@
%%
%%----------------------------------------------------------------------
-%% 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 +30,27 @@
-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]).
-%% Misc.
+%% Encoding
+-export([encode_plain_text/4]).
+
+%% Protocol version handling
-export([protocol_version/1, lowest_protocol_version/2,
highest_protocol_version/1, supported_protocol_versions/0,
is_acceptable_version/1, is_acceptable_version/2]).
--export([compressions/0]).
-
-compile(inline).
--define(INITIAL_BYTES, 5).
-
%%====================================================================
%% 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 +120,43 @@ get_tls_records_aux(Data, Acc) ->
false ->
?ALERT_REC(?FATAL, ?UNEXPECTED_MESSAGE)
end.
+
+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} = ssl_record:compress(CompAlg, Data, CompS0),
+ CS1 = CS0#connection_state{compression_state = CompS1},
+ {CipherFragment, CS2} = cipher(Type, Version, Comp, CS1),
+ CTBin = encode_tls_cipher_text(Type, Version, CipherFragment),
+ {CTBin, ConnectionStates#connection_states{current_write = CS2}}.
+
+%%--------------------------------------------------------------------
+-spec decode_cipher_text(#ssl_tls{}, #connection_states{}) ->
+ {#ssl_tls{}, #connection_states{}}| #alert{}.
+%%
+%% Description: Decode cipher text
+%%--------------------------------------------------------------------
+decode_cipher_text(#ssl_tls{type = Type, version = Version,
+ fragment = CipherFragment} = 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(Type, Version, CipherFragment, ReadState0) of
+ {PlainFragment, ReadState1} ->
+ {Plain, CompressionS1} = ssl_record:uncompress(CompressAlg,
+ PlainFragment, CompressionS0),
+ ConnnectionStates = ConnnectionStates0#connection_states{
+ current_read = ReadState1#connection_state{
+ compression_state = CompressionS1}},
+ {CipherText#ssl_tls{fragment = Plain}, ConnnectionStates};
+ #alert{} = Alert ->
+ Alert
+ end.
+
%%--------------------------------------------------------------------
-spec protocol_version(tls_atom_version() | tls_version()) ->
tls_version() | tls_atom_version().
@@ -493,183 +274,39 @@ 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.
-
-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}}.
-
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 =
+cipher(Type, Version, Fragment,
+ #connection_state{cipher_state = CipherS0,
+ sequence_number = SeqNo,
+ 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,
+ } = WriteState0) ->
+ MacHash = calc_mac_hash(Type, Version, Fragment, WriteState0),
+ {CipherFragment, CipherS1} =
+ ssl_cipher:cipher(BCA, CipherS0, MacHash, Fragment, Version),
+ WriteState = WriteState0#connection_state{cipher_state=CipherS1},
+ {CipherFragment, WriteState#connection_state{sequence_number = SeqNo+1}}.
+
+decipher(Type, Version, CipherFragment,
+ #connection_state{sequence_number = SeqNo} = ReadState) ->
+ SP = ReadState#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};
+ CipherS0 = ReadState#connection_state.cipher_state,
+ case ssl_cipher:decipher(BCA, HashSz, CipherS0, CipherFragment, Version) of
+ {PlainFragment, Mac, CipherS1} ->
+ CS1 = ReadState#connection_state{cipher_state = CipherS1},
+ MacHash = calc_mac_hash(Type, Version, PlainFragment, ReadState),
+ case ssl_record:is_correct_mac(Mac, MacHash) of
+ true ->
+ {PlainFragment,
+ CS1#connection_state{sequence_number = SeqNo+1}};
false ->
?ALERT_REC(?FATAL, ?BAD_RECORD_MAC)
end;
@@ -677,40 +314,29 @@ decipher(TLS=#ssl_tls{type=Type, version=Version, fragment=Fragment}, CS0) ->
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()).
+
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).
diff --git a/lib/ssl/src/tls_record.hrl b/lib/ssl/src/tls_record.hrl
index c9350fa137..30d7343074 100644
--- a/lib/ssl/src/tls_record.hrl
+++ b/lib/ssl/src/tls_record.hrl
@@ -29,7 +29,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..2395e98642 100644
--- a/lib/ssl/src/ssl_tls1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -22,7 +22,7 @@
%% Purpose: Handles tls1 encryption.
%%----------------------------------------------------------------------
--module(ssl_tls1).
+-module(tls_v1).
-include("ssl_cipher.hrl").
-include("ssl_internal.hrl").
@@ -86,7 +86,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 +106,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,
@@ -167,22 +167,22 @@ setup_keys(Version, PrfAlgo, MasterSecret, ServerRandom, ClientRandom, HashSize,
ServerWriteKey, ClientIV, ServerIV}.
-spec mac_hash(integer(), binary(), integer(), integer(), tls_version(),
- integer(), binary()) -> binary().
+ 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 ->
@@ -199,8 +199,8 @@ suites(Minor) when Minor == 3 ->
no_ec_suites(3) ++ no_ec_suites(2)
end.
-all_suites(Minor) when Minor == 1; Minor == 2->
- [
+all_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,
@@ -224,7 +224,7 @@ all_suites(Minor) when Minor == 1; Minor == 2->
?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,
@@ -232,32 +232,32 @@ all_suites(Minor) when Minor == 1; Minor == 2->
?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
];
-all_suites(3) ->
+all_suites(3) ->
[
?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384,
?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384,
-
+
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
?TLS_RSA_WITH_AES_256_CBC_SHA256,
-
+
?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
?TLS_ECDHE_RSA_WITH_AES_128_CBC_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->
- [
+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,
@@ -272,7 +272,7 @@ no_ec_suites(Minor) when Minor == 1; Minor == 2->
?TLS_DHE_RSA_WITH_DES_CBC_SHA,
?TLS_RSA_WITH_DES_CBC_SHA
];
-no_ec_suites(3) ->
+no_ec_suites(3) ->
[
?TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,
?TLS_DHE_DSS_WITH_AES_256_CBC_SHA256,
@@ -323,7 +323,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)).
diff --git a/lib/ssl/test/ssl_handshake_SUITE.erl b/lib/ssl/test/ssl_handshake_SUITE.erl
index a40f07fd07..9695710230 100644
--- a/lib/ssl/test/ssl_handshake_SUITE.erl
+++ b/lib/ssl/test/ssl_handshake_SUITE.erl
@@ -32,43 +32,44 @@
%%--------------------------------------------------------------------
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() -> [
- decode_hello_handshake,
- decode_single_hello_extension_correctly,
- decode_unknown_hello_extension_correctly].
+all() -> [decode_hello_handshake,
+ decode_single_hello_extension_correctly,
+ decode_unknown_hello_extension_correctly].
%%--------------------------------------------------------------------
%% 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, <<>>),
+
+ {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_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.
diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl
index ef5a02abef..27e1090114 100644
--- a/lib/ssl/test/ssl_npn_hello_SUITE.erl
+++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl
@@ -52,7 +52,7 @@ encode_and_decode_client_hello_test(_Config) ->
Version = tls_record:protocol_version(tls_record:highest_protocol_version([])),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
- NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation,
+ NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation,
NextProtocolNegotiation = undefined.
%%--------------------------------------------------------------------
encode_and_decode_npn_client_hello_test(_Config) ->
@@ -60,7 +60,7 @@ encode_and_decode_npn_client_hello_test(_Config) ->
Version = tls_record:protocol_version(tls_record:highest_protocol_version([])),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
- NextProtocolNegotiation = DecodedHandshakeMessage#client_hello.next_protocol_negotiation,
+ NextProtocolNegotiation = (DecodedHandshakeMessage#client_hello.extensions)#hello_extensions.next_protocol_negotiation,
NextProtocolNegotiation = #next_protocol_negotiation{extension_data = <<>>}.
%%--------------------------------------------------------------------
encode_and_decode_server_hello_test(_Config) ->
@@ -68,7 +68,7 @@ encode_and_decode_server_hello_test(_Config) ->
Version = tls_record:protocol_version(tls_record:highest_protocol_version([])),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
- NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation,
+ NextProtocolNegotiation = (DecodedHandshakeMessage#server_hello.extensions)#hello_extensions.next_protocol_negotiation,
NextProtocolNegotiation = undefined.
%%--------------------------------------------------------------------
encode_and_decode_npn_server_hello_test(_Config) ->
@@ -76,56 +76,59 @@ encode_and_decode_npn_server_hello_test(_Config) ->
Version = tls_record:protocol_version(tls_record:highest_protocol_version([])),
{[{DecodedHandshakeMessage, _Raw}], _} =
tls_handshake:get_tls_handshake(Version, list_to_binary(HandShakeData), <<>>),
- NextProtocolNegotiation = DecodedHandshakeMessage#server_hello.next_protocol_negotiation,
+ 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 = tls_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_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/vsn.mk b/lib/ssl/vsn.mk
index 9dd151553c..78424ee578 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 5.3
+SSL_VSN = 5.3.1
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 38cd44def6..2c1daf10ba 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -126,20 +126,20 @@
</item>
<item>
<p>
- Optimizations to gen mechanism. Thanks to Lo�c Hoguin.</p>
+ Optimizations to gen mechanism. Thanks to Loïc Hoguin.</p>
<p>
Own Id: OTP-11025</p>
</item>
<item>
<p>
- Optimizations to gen.erl. Thanks to Lo�c Hoguin.</p>
+ Optimizations to gen.erl. Thanks to Loïc Hoguin.</p>
<p>
Own Id: OTP-11035</p>
</item>
<item>
<p>
Use erlang:demonitor(Ref, [flush]) where applicable.
- Thanks to Lo�c Hoguin.</p>
+ Thanks to Loïc Hoguin.</p>
<p>
Own Id: OTP-11039</p>
</item>
@@ -194,7 +194,7 @@
</item>
<item>
<p>
- Improve erl_lint performance. Thanks to Jos� Valim.</p>
+ Improve erl_lint performance. Thanks to José Valim.</p>
<p>
Own Id: OTP-11143</p>
</item>
@@ -297,7 +297,7 @@
<p>
Two adjacent * used as a single pattern will match all
files and zero or more directories and subdirectories.
- (Thanks to Jos� Valim)</p>
+ (Thanks to José Valim)</p>
<p>
Own Id: OTP-10431</p>
</item>
@@ -535,7 +535,7 @@
Fix filename:nativename/1 on Win32</p>
<p>
Don't choke on paths given as binary argument on Win32.
- Thanks to Jan Kl�tzke</p>
+ Thanks to Jan Klötzke</p>
<p>
Own Id: OTP-10188</p>
</item>
@@ -644,7 +644,7 @@
<item>
<p>
Fix the type spec from the doc of binary:part/3 (Thanks
- to Ricardo Catalinas Jim�nez)</p>
+ to Ricardo Catalinas Jiménez)</p>
<p>
Own Id: OTP-9920</p>
</item>
@@ -3209,7 +3209,7 @@
It is now possible to hibernate a
gen_server/gen_event/gen_fsm. In gen_server and gen_fsm,
hibernation is triggered by returning the atom
- 'hibernate'�instead of a timeout value. In the gen_event
+ 'hibernate' instead of a timeout value. In the gen_event
case hibernation is triggered by a event handler
returning a tuple with an extra element containing the
atom 'hibernate'.</p>
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 05049d7107..422d4538a6 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -103,7 +103,7 @@
<item>
<p>
Fix a bug in cover when used with no_auto_import. Thanks
- to Jos� Valim.</p>
+ to José Valim.</p>
<p>
Own Id: OTP-10778</p>
</item>
@@ -138,7 +138,7 @@
<item>
<p>
Add separate face for exported functions (Thanks to
- Thomas J�rvstrand)</p>
+ Thomas Järvstrand)</p>
<p>
Own Id: OTP-10637</p>
</item>
@@ -223,7 +223,7 @@
</item>
<item>
<p>
- Documentation fixes (Thanks to Ricardo Catalinas Jim�nez
+ Documentation fixes (Thanks to Ricardo Catalinas Jiménez
)</p>
<p>
Own Id: OTP-10121</p>
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index a6e89d32a9..6ecae3752b 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="latin1" ?>
+<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE chapter SYSTEM "chapter.dtd">
<chapter>
@@ -39,7 +39,7 @@
<p>
Add {silent_start, boolean()} option to wx:new/1 in order
to be able to suppress error messages during startup of
- wx. (Thanks to H�kan Mattsson)</p>
+ wx. (Thanks to Håkan Mattsson)</p>
<p>
Own Id: OTP-10585</p>
</item>