Skip to content

Commit

Permalink
Windows roots (#2088)
Browse files Browse the repository at this point in the history
  • Loading branch information
Erik Corry authored Feb 15, 2024
1 parent c9bda64 commit 724fcf4
Show file tree
Hide file tree
Showing 7 changed files with 223 additions and 3 deletions.
14 changes: 14 additions & 0 deletions lib/tls/certificate.toit
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,17 @@ Returns the hash of the added certificate, which can be used to add it more
*/
add-global-root-certificate_ cert hash/int?=null -> int:
#primitive.tls.add-global-root-certificate

/**
Adds the trusted root certificates that are installed on the system.
This is only supported on Windows. On other platforms it currently does
nothing.
This need only be called once, then it is available for all TLS connections.
Like $RootCertificate.install, this function is an alternative to adding
root certificates to individual TLS sockets.
*/
use-system-trusted-root-certificates -> none:
#primitive.tls.use-system-trusted-root-certificates
4 changes: 2 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ else()
endif()

if ("${CMAKE_SYSTEM_NAME}" MATCHES "MSYS")
set(TOIT_WINDOWS_LIBS ws2_32 rpcrt4 shlwapi)
set(TOIT_WINDOWS_LIBS ws2_32 rpcrt4 shlwapi crypt32)
elseif ("${CMAKE_SYSTEM_NAME}" MATCHES "Windows")
set(TOIT_WINDOWS_LIBS rpcrt4 shlwapi)
set(TOIT_WINDOWS_LIBS rpcrt4 shlwapi crypt32)
else()
set(TOIT_WINDOWS_LIBS )
endif()
Expand Down
1 change: 1 addition & 0 deletions src/compiler/propagation/type_primitive_tls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ TYPE_PRIMITIVE_ANY(read)
TYPE_PRIMITIVE_ANY(write)
TYPE_PRIMITIVE_ANY(add_root_certificate)
TYPE_PRIMITIVE_INT(add_global_root_certificate)
TYPE_PRIMITIVE_NULL(use_system_trusted_root_certificates)
TYPE_PRIMITIVE_ANY(add_certificate)
TYPE_PRIMITIVE_ANY(error)
TYPE_PRIMITIVE_ANY(get_internals)
Expand Down
1 change: 1 addition & 0 deletions src/primitive.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ namespace toit {
PRIMITIVE(write, 4) \
PRIMITIVE(add_root_certificate, 2) \
PRIMITIVE(add_global_root_certificate, 2) \
PRIMITIVE(use_system_trusted_root_certificates, 0) \
PRIMITIVE(add_certificate, 4) \
PRIMITIVE(error, 2) \
PRIMITIVE(get_internals, 1) \
Expand Down
76 changes: 75 additions & 1 deletion src/resources/tls.cc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
#include <mbedtls/ssl_internal.h>
#endif

#ifdef TOIT_WINDOWS
#include <windows.h>
#include <wincrypt.h>
#endif

#include "../entropy_mixer.h"
#include "../heap_report.h"
#include "../primitive.h"
Expand Down Expand Up @@ -645,6 +650,60 @@ PRIMITIVE(close) {
return process->null_object();
}

static const int NEEDS_DELETE = 1;
static const int IN_FLASH = 2;
static const int IGNORE_UNSUPPORTED_HASH = 4;

static Object* add_global_root(const uint8* data, size_t length, Object* hash, Process* process, int flags);

#ifdef TOIT_WINDOWS
static Object* add_roots_from_store(const HCERTSTORE store, Process* process) {
if (!store) return process->null_object();
const CERT_CONTEXT* cert_context = CertEnumCertificatesInStore(store, null);
while (cert_context) {
if (cert_context->dwCertEncodingType == X509_ASN_ENCODING) {
// The certificate is in DER format.
const uint8* data = cert_context->pbCertEncoded;
size_t size = cert_context->cbCertEncoded;
Object* result = add_global_root(data, size, process->null_object(), process, IGNORE_UNSUPPORTED_HASH);
// Normally the result is a hash, but we don't need that here, so just
// check for errors.
if (Primitive::is_error(result)) return result;
}
cert_context = CertEnumCertificatesInStore(store, cert_context);
}
return process->null_object();
}

static Object* load_system_trusted_roots(Process* process) {
const HCERTSTORE root_store = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"ROOT");
Object* result = add_roots_from_store(root_store, process);
if (Primitive::is_error(result)) return result;

const HCERTSTORE ca_store = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"CA");
return add_roots_from_store(ca_store, process);
}
#endif

PRIMITIVE(use_system_trusted_root_certificates) {
#ifdef TOIT_WINDOWS
static bool loaded_system_trusted_roots = false;
bool load = false;
{ Locker locker(OS::tls_mutex());
load = !loaded_system_trusted_roots;
}
if (load) {
Object* result = load_system_trusted_roots(process);
if (Primitive::is_error(result)) return result;
loaded_system_trusted_roots = true;
}
{ Locker locker(OS::tls_mutex());
loaded_system_trusted_roots = true;
}
#endif
return process->null_object();
}

PRIMITIVE(add_global_root_certificate) {
ARGS(Object, unparsed_cert, Object, hash);
bool needs_delete = false;
Expand All @@ -656,7 +715,15 @@ PRIMITIVE(add_global_root_certificate) {

bool in_flash = reinterpret_cast<const HeapObject*>(data)->on_program_heap(process);
ASSERT(!(in_flash && needs_delete)); // We can't free something in flash.
int flags = 0;
if (needs_delete) flags |= NEEDS_DELETE;
if (in_flash) flags |= IN_FLASH;
return add_global_root(data, length, hash, process, flags);
}

static Object* add_global_root(const uint8* data, size_t length, Object* hash, Process* process, int flags) {
bool needs_delete = (flags & NEEDS_DELETE) != 0;
bool in_flash = (flags & IN_FLASH) != 0;
if (!needs_delete && !in_flash) {
// The raw cert data will not survive the end of this primitive, so we need a copy.
uint8* new_data = _new uint8[length];
Expand Down Expand Up @@ -691,7 +758,14 @@ PRIMITIVE(add_global_root_certificate) {
}
if (ret != 0) {
mbedtls_x509_crt_free(&cert);
return tls_error(null, process, ret);
int major_error = (-ret & 0xff80);
if ((flags & IGNORE_UNSUPPORTED_HASH) != 0 &&
(-major_error == MBEDTLS_ERR_X509_UNKNOWN_SIG_ALG ||
-major_error == MBEDTLS_ERR_ASN1_UNEXPECTED_TAG)) {
return process->null_object();
} else {
return tls_error(null, process, ret);
}
}

uint8 subject_buffer[MAX_SUBJECT];
Expand Down
1 change: 1 addition & 0 deletions tests/fail.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ list(APPEND TOIT_FLAKY_TESTS
tests/tls-test-slow.toit
tests/tls-ubuntu-test.toit
tests/tls-global-cert-simple-test.toit
tests/tls-simple-cert.toit
)

if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" OR "${CMAKE_SYSTEM_NAME}" STREQUAL "MSYS")
Expand Down
129 changes: 129 additions & 0 deletions tests/tls-system-cert.toit
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (C) 2023 Toitware ApS.
// Use of this source code is governed by a Zero-Clause BSD license that can
// be found in the tests/LICENSE file.
import expect show *
import writer
import tls
import net
import net.x509
import system
import system show platform

import .tls-global-common

expect-error name [code]:
error := catch code
expect: error.contains name

monitor LimitLoad:
current := 0
has-test-failure := null
// FreeRTOS does not have enough memory to run 10 in parallel.
concurrent-processes ::= 4

inc:
await: current < concurrent-processes
current++

flush:
await: current == 0

test-failures:
await: current == 0
return has-test-failure

log-test-failure message:
has-test-failure = message

dec:
current--

load-limiter := LimitLoad

network/net.Client? := null

main:
add-global-certs
if system.platform != "Windows": return

network = net.open
try:
run-tests
finally:
network.close

run-tests:
working := [
"amazon.com",
"adafruit.com",
"dkhostmaster.dk",
"dmi.dk",
"pravda.ru",
"elpriser.nu",
"coinbase.com",
"helsinki.fi",
"lund.se",
"web.whatsapp.com",
"digimedia.com",
]
working.do: | site |
test-site site
if load-limiter.has-test-failure: throw load-limiter.has-test-failure // End early if we have a test failure.
if load-limiter.test-failures:
throw load-limiter.has-test-failure

test-site url:
host := url
extra-info := null
if (host.index-of "/") != -1:
parts := host.split "/"
host = parts[0]
extra-info = parts[1]
port := 443
if (url.index-of ":") != -1:
array := url.split ":"
host = array[0]
port = int.parse array[1]
load-limiter.inc
working-site host port extra-info

working-site host port expected-certificate-name:
error := true
try:
connect-to-site host port expected-certificate-name
error = false
finally:
if error:
load-limiter.log-test-failure "*** Incorrectly failed to connect to $host ***"
load-limiter.dec

connect-to-site host port expected-certificate-name:
bytes := 0
connection := null

tcp-socket := network.tcp-connect host port
try:
socket := tls.Socket.client tcp-socket
--server-name=expected-certificate-name or host

try:
writer := writer.Writer socket
writer.write """GET / HTTP/1.1\r\nHost: $host\r\nConnection: close\r\n\r\n"""
print "$host: $((socket as any).session_.mode == tls.SESSION-MODE-TOIT ? "Toit mode" : "MbedTLS mode")"

while data := socket.read:
bytes += data.size

finally:
socket.close
finally:
tcp-socket.close
if connection: connection.close

print "Read $bytes bytes from https://$host$(port == 443 ? "" : ":$port")/"

add-global-certs -> none:
// Test that the built-in root certs in the Windows
// installation are sufficient.
tls.use-system-trusted-root-certificates

0 comments on commit 724fcf4

Please sign in to comment.