// Copyright 2021 The Pigweed Authors // // 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 // // https://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. #include "mbedtls/ssl.h" #include "pw_assert/check.h" #include "pw_log/log.h" #include "pw_tls_client/entropy.h" #include "pw_tls_client/session.h" #include "pw_tls_client_mbedtls/backend_types.h" namespace pw::tls_client { namespace backend { int SessionImplementation::MbedTlsWrite(void* ctx, const uint8_t* buf, size_t len) { PW_CHECK_NOTNULL(ctx); PW_CHECK_NOTNULL(buf); auto writer = static_cast(ctx)->session_options_.transport(); PW_CHECK_NOTNULL(writer); return writer->Write(buf, len).ok() ? len : -1; } int SessionImplementation::MbedTlsRead(void* ctx, unsigned char* buf, size_t len) { PW_CHECK_NOTNULL(ctx); PW_CHECK_NOTNULL(buf); auto reader = static_cast(ctx)->session_options_.transport(); PW_CHECK_NOTNULL(reader); auto res = reader->Read(buf, len); if (!res.ok()) { return -1; } return res.value().size() == 0 ? MBEDTLS_ERR_SSL_WANT_READ : res.value().size(); } Status SessionImplementation::entropy_source_status_ = OkStatus(); void SessionImplementation::SetEntropySourceStatus(Status status) { entropy_source_status_ = status; } // Entropy source callback int SessionImplementation::MbedTlsEntropySource(void* ctx, unsigned char* out, size_t len, size_t* output_length) { Status status; if (entropy_source_status_ != OkStatus()) { status = entropy_source_status_; } else { status = GetRandomBytes({out, len}); } if (!status.ok()) { PW_LOG_DEBUG("Failed to generate random bytes"); auto session_impl = static_cast(ctx); session_impl->SetTlsStatus(pw::tls_client::TLSStatus::kEntropySourceFailed); return MBEDTLS_ERR_ENTROPY_SOURCE_FAILED; } *output_length = len; return 0; } SessionImplementation::SessionImplementation(SessionOptions options) : session_options_(options) { mbedtls_ssl_init(&ssl_ctx_); mbedtls_ssl_config_init(&ssl_config_); mbedtls_ctr_drbg_init(&drbg_ctx_); mbedtls_entropy_init(&entropy_ctx_); } SessionImplementation::~SessionImplementation() { mbedtls_ssl_free(&ssl_ctx_); mbedtls_ssl_config_free(&ssl_config_); mbedtls_ctr_drbg_free(&drbg_ctx_); mbedtls_entropy_free(&entropy_ctx_); } Status SessionImplementation::Setup() { int ret = 0; // Set up default configuration. ret = mbedtls_ssl_config_defaults( &ssl_config_, // Configured as client. MBEDTLS_SSL_IS_CLIENT, // Statndard TLS. The other option is MBEDTLS_SSL_TRANSPORT_DATAGRAM // for DTLS, which we'll consider later. MBEDTLS_SSL_TRANSPORT_STREAM, // This option is used in all MbedTLS native examples. // The other option is MBEDTLS_SSL_PRESET_SUITEB. // However, there is no document/comment availalbe on what they do. // Base on the source code, these options will restrict the version // of TLS protocol. MBEDTLS_SSL_PRESET_SUITEB forces TLS 1.2. // MBEDTLS_SSL_PRESET_DEFAULT is more relaxed. But since we // define MBEDTLS_SSL_PROTO_TLS1_2 for all configs. There shouldn't be // any difference. MBEDTLS_SSL_PRESET_DEFAULT); if (ret) { return Status::Internal(); } // Set up an entropy source. ret = mbedtls_entropy_add_source(&entropy_ctx_, MbedTlsEntropySource, this, 1, MBEDTLS_ENTROPY_SOURCE_STRONG); if (ret) { return Status::Internal(); } // Set up drbg. unsigned char personalized_bytes[] = "pw_tls_client"; ret = mbedtls_ctr_drbg_seed(&drbg_ctx_, mbedtls_entropy_func, &entropy_ctx_, personalized_bytes, sizeof(personalized_bytes)); if (ret) { if (ret == MBEDTLS_ERR_CTR_DRBG_ENTROPY_SOURCE_FAILED) { tls_status_ = TLSStatus::kEntropySourceFailed; } return Status::Internal(); } // The API does not fail. mbedtls_ssl_conf_rng(&ssl_config_, mbedtls_ctr_drbg_random, &drbg_ctx_); // The API does not fail. mbedtls_ssl_conf_authmode(&ssl_config_, MBEDTLS_SSL_VERIFY_REQUIRED); // TODO(pwbug/398): Add logic for loading trust anchors. // Load configuration to SSL. ret = mbedtls_ssl_setup(&ssl_ctx_, &ssl_config_); if (ret) { return Status::Internal(); } // Set up transport. // The API does not fail. mbedtls_ssl_set_bio(&ssl_ctx_, this, MbedTlsWrite, MbedTlsRead, NULL); ret = mbedtls_ssl_set_hostname(&ssl_ctx_, session_options_.server_name().data()); if (ret) { return Status::Internal(); } return OkStatus(); } } // namespace backend Session::Session(const SessionOptions& options) : session_impl_(options) {} Session::~Session() = default; Result Session::Create(const SessionOptions& options) { if (!options.transport()) { PW_LOG_DEBUG("Must provide a transport"); return Status::Internal(); } auto sess = new Session(options); if (!sess) { return Status::ResourceExhausted(); } // Set up the client. auto setup_status = sess->session_impl_.Setup(); if (!setup_status.ok()) { PW_LOG_DEBUG("Failed to setup"); // TODO(pwbug/398): `tls_status_` may be set, but the session object will // be released. Map `tls_stauts_` to string and print out here so that // the information can be catched. delete sess; return setup_status; } return sess; } Status Session::Open() { // TODO(pwbug/398): To implement return Status::Unimplemented(); } Status Session::Close() { // TODO(pwbug/398): To implement return Status::Unimplemented(); } StatusWithSize Session::DoRead(ByteSpan) { // TODO(pwbug/398): To implement return StatusWithSize(Status::Unimplemented(), 0); } Status Session::DoWrite(ConstByteSpan) { // TODO(pwbug/398): To implement return Status::Unimplemented(); } TLSStatus Session::GetLastTLSStatus() { return session_impl_.GetTlsStatus(); } } // namespace pw::tls_client