Skip to content

Commit

Permalink
Merge pull request #929 from pguyot/w44/ssl-over-socket-with-mbedtls
Browse files Browse the repository at this point in the history
Add minimal support for ssl module using Mbed TLS and sockets

Details of changes:
- Add support for ssl client in binary and passive modes, with no certificate
verification
- Add APIs to otp_socket so it can be called from ssl bio callbacks
- Fix a bug in lwIP otp_socket's recv revealed by ssl tests
- Fix a bug in BSD otp_socket's recvfrom revealed by refactoring
- Fix a bug in esp32 tests where main context and its resources were not
properly destroyed
- Update documentation and workflows to reflect the requirement on Mbed TLS
- Fix exported types of inet module

This code was tested on:
- Pico-W
- ESP32 using ESP-IDF 5.1 release branch
- Unix (macOS)

using atomvm_netbench associated test.

The test takes 0.5s with Erlang/OTP or AtomVM on macOS.
It takes 7.3s then 1.0s on ESP32
It takes 12.0s then from 2.2s to 2.6s on Pico-W

These changes are made under both the "Apache 2.0" and the "GNU Lesser General
Public License 2.1 or later" license terms (dual license).

SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
  • Loading branch information
bettio committed Nov 6, 2023
2 parents f908ef4 + 71cc082 commit 8b3dee0
Show file tree
Hide file tree
Showing 35 changed files with 2,157 additions and 221 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test-macos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
submodules: 'recursive'

- name: "Install deps"
run: brew install gperf doxygen erlang@${{ matrix.otp }} ninja
run: brew install gperf doxygen erlang@${{ matrix.otp }} ninja mbedtls

# Builder info
- name: "System info"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test-on-freebsd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
echo "%%"
echo "**freebsd-version:**"
freebsd-version
sudo pkg install -y cmake gperf erlang elixir
sudo pkg install -y cmake gperf erlang elixir mbedtls
echo "**uname:**"
uname -a
echo "**C Compiler version:**"
Expand All @@ -73,7 +73,7 @@ jobs:
echo "%%"
mkdir build
cd build
cmake ..
cmake .. -DMBEDTLS_ROOT_DIR=/usr/local
echo "%%"
echo "%% Building AtomVM ..."
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test-other.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ jobs:
apt update &&
apt install -y -t stretch-backports-sloppy libarchive13 &&
apt install -y -t stretch-backports cmake &&
apt install -y file gcc g++ binutils make doxygen gperf zlib1g-dev libssl-dev
apt install -y file gcc g++ binutils make doxygen gperf zlib1g-dev libssl-dev libmbedtls-dev
- arch: "arm32v7"
platform: "arm/v7"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ jobs:
cmake_opts_other: "-DOPENSSL_CRYPTO_LIBRARY=/usr/lib/i386-linux-gnu/libcrypto.so -DAVM_CREATE_STACKTRACES=off"
arch: "i386"
compiler_pkgs: "gcc-10 g++-10 gcc-10-multilib g++-10-multilib libc6-dev-i386
libc6-dbg:i386 zlib1g-dev:i386 libssl-dev:i386"
libc6-dbg:i386 zlib1g-dev:i386 libssl-dev:i386 libmbedtls-dev:i386"

env:
CC: ${{ matrix.cc }}
Expand Down Expand Up @@ -202,7 +202,7 @@ jobs:
run: sudo apt update -y

- name: "Install deps"
run: sudo apt install -y ${{ matrix.compiler_pkgs}} cmake gperf zlib1g-dev doxygen valgrind
run: sudo apt install -y ${{ matrix.compiler_pkgs}} cmake gperf zlib1g-dev doxygen valgrind libmbedtls-dev

# Builder info
- name: "System info"
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for interrupts to STM32 GPIO port driver.
- Added suppoprt for PicoW extra gpio pins (led) to the gpio driver.
- Added support for `net:getaddrinfo/1,2`
- Added minimal support for the OTP `ssl` interface.

## [0.6.0-alpha.1] - 2023-10-09

Expand Down
97 changes: 97 additions & 0 deletions CMakeModules/MbedTLS.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
#
# This file is part of AtomVM.
#
# Copyright 2023 Paul Guyot <pguyot@kallisys.net>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
#

# Find MbedTLS
# Search for MbedTLS 2.x or 3.x and define libraries like MbedTLS 3.x does.

# This script is not called FindMbedTLS.cmake because it would conflict with
# installed MbedTLS 3.x

# If MBEDTLS_ROOT_DIR is set, no heuristic is applied.
# It must be set to the parent directory of include/mbedtls/version.h
# Libraries are at ${MBEDTLS_LIBRARIES_DIR} or, if unset, ${MBEDTLS_ROOT_DIR}/lib/

# If MBEDTLS_ROOT_DIR is not set, apply the following heuristic:
# Try to find mbedtls 3.x CMake package with find_package
# If it doesn't work, search for MBEDTLS_VERSION_NUMBER symbol as well as
# the three libraries we need with check_symbol_exists and find_library

if (MBEDTLS_ROOT_DIR)
set(MbedTLS_FOUND TRUE)
if (NOT MBEDTLS_LIBRARIES_DIR)
set(MBEDTLS_LIBRARIES_DIR ${MBEDTLS_ROOT_DIR}/lib)
endif()
message(STATUS "Will use MbedTLS from ${MBEDTLS_ROOT_DIR} and ${MBEDTLS_LIBRARIES_DIR}")

add_library(MbedTLS::mbedcrypto SHARED IMPORTED)
set_target_properties(MbedTLS::mbedcrypto PROPERTIES
IMPORTED_LOCATION "${MBEDTLS_LIBRARIES_DIR}/libmbedcrypto${CMAKE_SHARED_LIBRARY_SUFFIX}"
INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_ROOT_DIR}/include/"
)

add_library(MbedTLS::mbedx509 SHARED IMPORTED)
set_target_properties(MbedTLS::mbedx509 PROPERTIES
IMPORTED_LOCATION "${MBEDTLS_LIBRARIES_DIR}/libmbedx509${CMAKE_SHARED_LIBRARY_SUFFIX}"
INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_ROOT_DIR}/include/"
INTERFACE_LINK_LIBRARIES "MbedTLS::mbedcrypto"
)

add_library(MbedTLS::mbedtls SHARED IMPORTED)
set_target_properties(MbedTLS::mbedtls PROPERTIES
IMPORTED_LOCATION "${MBEDTLS_LIBRARIES_DIR}/libmbedtls${CMAKE_SHARED_LIBRARY_SUFFIX}"
INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_ROOT_DIR}/include/"
INTERFACE_LINK_LIBRARIES "MbedTLS::mbedx509"
)
else()
# MbedTLS 3.x is installed as a CMake package
find_package(MbedTLS QUIET)
if (MbedTLS_FOUND)
message(STATUS "Found MbedTLS package ${MbedTLS_FOUND}")
else()
include(CheckSymbolExists)
check_symbol_exists(MBEDTLS_VERSION_NUMBER "mbedtls/version.h" HAVE_MBEDTLS_VERSION_NUMBER)
find_library(MBEDCRYPTO mbedcrypto)
find_library(MBEDX509 mbedx509)
find_library(MBEDTLS mbedtls)
if (HAVE_MBEDTLS_VERSION_NUMBER
AND NOT ${MBEDCRYPTO} STREQUAL "MBEDCRYPTO-NOTFOUND"
AND NOT ${MBEDX509} STREQUAL "MBEDX509-NOTFOUND"
AND NOT ${MBEDTLS} STREQUAL "MBEDTLS-NOTFOUND")
message(STATUS "Found MbedTLS with mbedcrypto ${MBEDCRYPTO}, mbedx509 ${MBEDX509} and mbedtls ${MBEDTLS}")
set(MbedTLS_FOUND TRUE)
add_library(MbedTLS::mbedcrypto SHARED IMPORTED)
set_target_properties(MbedTLS::mbedcrypto PROPERTIES
IMPORTED_LOCATION "${MBEDCRYPTO}"
)

add_library(MbedTLS::mbedx509 SHARED IMPORTED)
set_target_properties(MbedTLS::mbedx509 PROPERTIES
IMPORTED_LOCATION "${MBEDX509}"
INTERFACE_LINK_LIBRARIES "MbedTLS::mbedcrypto"
)

add_library(MbedTLS::mbedtls SHARED IMPORTED)
set_target_properties(MbedTLS::mbedtls PROPERTIES
IMPORTED_LOCATION "${MBEDTLS}"
INTERFACE_LINK_LIBRARIES "MbedTLS::mbedx509"
)
endif()
endif()
endif()
1 change: 1 addition & 0 deletions README.Md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Required for building:
* gperf ([GNU Perfect Hash Function Generator](https://www.gnu.org/software/gperf/manual/gperf.html))
* erlc ([erlang compiler](https://www.erlang.org/))
* elixirc ([elixir compiler](https://elixir-lang.org))
* Mbed TLS ([portable TLS library, optionally required to support SSL](https://www.trustedfirmware.org/projects/mbed-tls/))
* zlib ([zlib compression and decompression library](https://zlib.net/))

Documentation and Coverage:
Expand Down
1 change: 1 addition & 0 deletions doc/src/build-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ The following software is required in order to build AtomVM in generic UNIX syst
* `make`
* `gperf`
* `zlib`
* `Mbed TLS`
* Erlang/OTP compiler (`erlc`)
* Elixir compiler

Expand Down
1 change: 1 addition & 0 deletions libs/estdlib/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ set(ERLANG_MODULES
logger_std_h
proplists
socket
ssl
string
timer
unicode
Expand Down
2 changes: 1 addition & 1 deletion libs/estdlib/src/gen_tcp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
%% @end
%%-----------------------------------------------------------------------------
-spec connect(
Address :: inet:address() | inet:hostname(),
Address :: inet:ip_address() | inet:hostname(),
Port :: inet:port_number(),
Options :: [connect_option()]
) ->
Expand Down
6 changes: 3 additions & 3 deletions libs/estdlib/src/gen_udp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ open(PortNum, Options) ->
%%-----------------------------------------------------------------------------
-spec send(
Socket :: inet:socket(),
Address :: inet:address(),
Address :: inet:ip_address(),
PortNum :: inet:port_number(),
Packet :: packet()
) -> ok | {error, reason()}.
Expand All @@ -121,7 +121,7 @@ send(Socket, Address, PortNum, Packet) ->
%% @end
%%-----------------------------------------------------------------------------
-spec recv(Socket :: inet:socket(), Length :: non_neg_integer()) ->
{ok, {inet:address(), inet:port_number(), packet()}} | {error, reason()}.
{ok, {inet:ip_address(), inet:port_number(), packet()}} | {error, reason()}.
recv(Socket, Length) ->
recv(Socket, Length, infinity).

Expand All @@ -143,7 +143,7 @@ recv(Socket, Length) ->
%% @end
%%-----------------------------------------------------------------------------
-spec recv(Socket :: inet:socket(), Length :: non_neg_integer(), Timeout :: timeout()) ->
{ok, {inet:address(), inet:port_number(), packet()}} | {error, reason()}.
{ok, {inet:ip_address(), inet:port_number(), packet()}} | {error, reason()}.
recv(Socket, Length, Timeout) ->
call(Socket, {recvfrom, Length, Timeout}).

Expand Down
13 changes: 7 additions & 6 deletions libs/estdlib/src/inet.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,11 @@

-type port_number() :: 0..65535.
-type socket() :: pid().
-type address() :: ipv4_address().
-type ipv4_address() :: {octet(), octet(), octet(), octet()}.
-type octet() :: 0..255.
-type ip_address() :: ip4_address().
-type ip4_address() :: {0..255, 0..255, 0..255, 0..255}.
-type hostname() :: iodata().

-export_type([socket/0, port_number/0, address/0, ipv4_address/0, octet/0, hostname/0]).
-export_type([socket/0, port_number/0, ip_address/0, ip4_address/0, hostname/0]).

%%-----------------------------------------------------------------------------
%% @param Socket the socket from which to obtain the port number
Expand Down Expand Up @@ -61,7 +60,8 @@ close(Socket) ->
%% This function should be called on a running socket instance.
%% @end
%%-----------------------------------------------------------------------------
-spec sockname(Socket :: socket()) -> {ok, {address(), port_number()}} | {error, Reason :: term()}.
-spec sockname(Socket :: socket()) ->
{ok, {ip_address(), port_number()}} | {error, Reason :: term()}.
sockname(Socket) ->
call(Socket, {sockname}).

Expand All @@ -72,7 +72,8 @@ sockname(Socket) ->
%% This function should be called on a running socket instance.
%% @end
%%-----------------------------------------------------------------------------
-spec peername(Socket :: socket()) -> {ok, {address(), port_number()}} | {error, Reason :: term()}.
-spec peername(Socket :: socket()) ->
{ok, {ip_address(), port_number()}} | {error, Reason :: term()}.
peername(Socket) ->
call(Socket, {peername}).

Expand Down
Loading

0 comments on commit 8b3dee0

Please sign in to comment.