Skip to content

Commit

Permalink
Merge pull request #119 from JKRhb/certificate
Browse files Browse the repository at this point in the history
Add support for root certificates in PEM format
  • Loading branch information
JKRhb authored Dec 11, 2024
2 parents 2f806a1 + 01c9eb3 commit 1ce833c
Show file tree
Hide file tree
Showing 8 changed files with 1,568 additions and 20 deletions.
1 change: 1 addition & 0 deletions lib/dtls2.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/// A DTLS library for Dart, implemented via FFI bindings to OpenSSL.
library dtls2;

export "src/certificate.dart";
export "src/dtls_client.dart";
export "src/dtls_connection.dart";
export "src/dtls_exception.dart";
Expand Down
38 changes: 38 additions & 0 deletions lib/src/certificate.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2024 Jan Romann
// SPDX-License-Identifier: MIT

import "dart:convert";
import "dart:typed_data";

import "package:meta/meta.dart";

/// Base class for certificates usable with OpenSSL.
@immutable
sealed class Certificate {
const Certificate();

/// The certificate in serialized form.
Uint8List get bytes;
}

/// A certificate in PEM format.
final class PemCertificate extends Certificate {
/// Creates a new [PemCertificate] from a [_certificateString].
const PemCertificate(this._certificateString);

final String _certificateString;

@override
Uint8List get bytes => utf8.encode(_certificateString);
}

/// A certificate in DER format.
final class DerCertificate extends Certificate {
/// Creates a new [DerCertificate] from a list of [_bytes].
const DerCertificate(this._bytes);

final Uint8List _bytes;

@override
Uint8List get bytes => _bytes;
}
36 changes: 26 additions & 10 deletions lib/src/dtls_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "dart:math";
import "dart:typed_data";

import "package:dtls2/src/buffer.dart";
import "package:dtls2/src/certificate.dart";
import "package:dtls2/src/dtls_alert.dart";
import "package:dtls2/src/dtls_connection.dart";
import "package:dtls2/src/dtls_exception.dart";
Expand Down Expand Up @@ -158,7 +159,7 @@ class DtlsClient {
hostname,
address,
port,
context._generateSslContext(_libSsl),
context._generateSslContext(_libSsl, _libCrypto),
context._pskCredentialsCallback,
_libCrypto,
_libSsl,
Expand Down Expand Up @@ -648,7 +649,7 @@ class DtlsClientContext {
DtlsClientContext({
bool verify = true,
bool withTrustedRoots = false,
List<Uint8List> rootCertificates = const [],
List<Certificate> rootCertificates = const [],
String? ciphers,
PskCredentialsCallback? pskCredentialsCallback,
this.securityLevel,
Expand All @@ -664,7 +665,7 @@ class DtlsClientContext {

final PskCredentialsCallback? _pskCredentialsCallback;

final List<Uint8List> _rootCertificates;
final List<Certificate> _rootCertificates;

final String? _ciphers;

Expand All @@ -677,21 +678,33 @@ class DtlsClientContext {
final int? securityLevel;

void _addRoots(
List<Uint8List> certs,
List<Certificate> certs,
Pointer<SSL_CTX> ctx,
OpenSsl libSsl,
OpenSsl libCrypto,
) {
if (certs.isEmpty) return;
final bufLen = certs.map((c) => c.length).reduce(max);
final bufLen = certs.map((c) => c.bytes.length).reduce(max);
final buf = malloc.call<UnsignedChar>(bufLen);
final data = malloc.call<Pointer<UnsignedChar>>();
final store = libSsl.SSL_CTX_get_cert_store(ctx);

for (final cert in certs) {
buf.cast<Uint8>().asTypedList(bufLen).setAll(0, cert);
buf.cast<Uint8>().asTypedList(bufLen).setAll(0, cert.bytes);
data.value = buf;
final opensslCert = libSsl.d2i_X509(nullptr, data, cert.length);
libSsl
final Pointer<X509> opensslCert;
switch (cert) {
case DerCertificate(:final bytes):
opensslCert = libCrypto.d2i_X509(nullptr, data, bytes.length);
case PemCertificate(:final bytes):
final bio = libCrypto.BIO_new(libCrypto.BIO_s_mem());
libCrypto.BIO_write(bio, buf.cast(), bytes.length);
opensslCert =
libCrypto.PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
libCrypto.BIO_free(bio);
}

libCrypto
..X509_STORE_add_cert(store, opensslCert)
..X509_free(opensslCert);
}
Expand All @@ -701,14 +714,17 @@ class DtlsClientContext {
..free(buf);
}

Pointer<SSL_CTX> _generateSslContext(OpenSsl libSsl) {
Pointer<SSL_CTX> _generateSslContext(
OpenSsl libSsl,
OpenSsl libCrypto,
) {
final ctx = libSsl.SSL_CTX_new(libSsl.DTLS_client_method());

if (_withTrustedRoots) {
libSsl.SSL_CTX_set_default_verify_paths(ctx);
}

_addRoots(_rootCertificates, ctx, libSsl);
_addRoots(_rootCertificates, ctx, libSsl, libCrypto);
libSsl.SSL_CTX_set_verify(
ctx,
_verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
Expand Down
36 changes: 26 additions & 10 deletions lib/src/dtls_server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import "dart:typed_data";

import "package:collection/collection.dart";
import "package:dtls2/src/buffer.dart";
import "package:dtls2/src/certificate.dart";
import "package:dtls2/src/dtls_alert.dart";
import "package:dtls2/src/dtls_connection.dart";
import "package:dtls2/src/dtls_exception.dart";
Expand All @@ -34,7 +35,10 @@ class DtlsServer extends Stream<DtlsConnection> {
this._context, {
DynamicLibrary? libSsl,
DynamicLibrary? libCrypto,
}) : _sslContext = _context._generateSslContext(loadLibSsl(libSsl)),
}) : _sslContext = _context._generateSslContext(
loadLibSsl(libSsl),
loadLibCrypto(libCrypto),
),
_libCrypto = loadLibCrypto(libCrypto),
_libSsl = loadLibSsl(libSsl) {
_setCallbacks();
Expand Down Expand Up @@ -456,7 +460,7 @@ class DtlsServerContext {
DtlsServerContext({
bool verify = true,
bool withTrustedRoots = false,
List<Uint8List> rootCertificates = const [],
List<Certificate> rootCertificates = const [],
String? ciphers,
PskKeyStoreCallback? pskKeyStoreCallback,
String? identityHint,
Expand All @@ -474,7 +478,7 @@ class DtlsServerContext {

final PskKeyStoreCallback? _pskKeyStoreCallback;

final List<Uint8List> _rootCertificates;
final List<Certificate> _rootCertificates;

final String? _ciphers;

Expand All @@ -489,21 +493,33 @@ class DtlsServerContext {
final int? securityLevel;

void _addRoots(
List<Uint8List> certs,
List<Certificate> certs,
Pointer<SSL_CTX> ctx,
OpenSsl libSsl,
OpenSsl libCrypto,
) {
if (certs.isEmpty) return;
final bufLen = certs.map((c) => c.length).reduce(max);
final bufLen = certs.map((c) => c.bytes.length).reduce(max);
final buf = malloc.call<UnsignedChar>(bufLen);
final data = malloc.call<Pointer<UnsignedChar>>();
final store = libSsl.SSL_CTX_get_cert_store(ctx);

for (final cert in certs) {
buf.cast<Uint8>().asTypedList(bufLen).setAll(0, cert);
buf.cast<Uint8>().asTypedList(bufLen).setAll(0, cert.bytes);
data.value = buf;
final opensslCert = libSsl.d2i_X509(nullptr, data, cert.length);
libSsl
final Pointer<X509> opensslCert;
switch (cert) {
case DerCertificate(:final bytes):
opensslCert = libCrypto.d2i_X509(nullptr, data, bytes.length);
case PemCertificate(:final bytes):
final bio = libCrypto.BIO_new(libCrypto.BIO_s_mem());
libCrypto.BIO_write(bio, buf.cast(), bytes.length);
opensslCert =
libCrypto.PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
libCrypto.BIO_free(bio);
}

libCrypto
..X509_STORE_add_cert(store, opensslCert)
..X509_free(opensslCert);
}
Expand All @@ -513,14 +529,14 @@ class DtlsServerContext {
..free(buf);
}

Pointer<SSL_CTX> _generateSslContext(OpenSsl libSsl) {
Pointer<SSL_CTX> _generateSslContext(OpenSsl libSsl, OpenSsl libCrypto) {
final ctx = libSsl.SSL_CTX_new(libSsl.DTLS_server_method());

if (_withTrustedRoots) {
libSsl.SSL_CTX_set_default_verify_paths(ctx);
}

_addRoots(_rootCertificates, ctx, libSsl);
_addRoots(_rootCertificates, ctx, libSsl, libCrypto);
libSsl.SSL_CTX_set_verify(
ctx,
_verify ? SSL_VERIFY_PEER : SSL_VERIFY_NONE,
Expand Down
31 changes: 31 additions & 0 deletions lib/src/generated/ffi.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,34 @@ class OpenSsl {
ffi.Pointer<X509> Function(ffi.Pointer<ffi.Pointer<X509>>,
ffi.Pointer<ffi.Pointer<ffi.UnsignedChar>>, int)>();

ffi.Pointer<X509> PEM_read_bio_X509(
ffi.Pointer<BIO> out,
ffi.Pointer<ffi.Pointer<X509>> x,
ffi.Pointer<pem_password_cb> cb,
ffi.Pointer<ffi.Void> u,
) {
return _PEM_read_bio_X509(
out,
x,
cb,
u,
);
}

late final _PEM_read_bio_X509Ptr = _lookup<
ffi.NativeFunction<
ffi.Pointer<X509> Function(
ffi.Pointer<BIO>,
ffi.Pointer<ffi.Pointer<X509>>,
ffi.Pointer<pem_password_cb>,
ffi.Pointer<ffi.Void>)>>('PEM_read_bio_X509');
late final _PEM_read_bio_X509 = _PEM_read_bio_X509Ptr.asFunction<
ffi.Pointer<X509> Function(
ffi.Pointer<BIO>,
ffi.Pointer<ffi.Pointer<X509>>,
ffi.Pointer<pem_password_cb>,
ffi.Pointer<ffi.Void>)>();

void SSL_CTX_set_info_callback(
ffi.Pointer<SSL_CTX> ctx,
ffi.Pointer<
Expand Down Expand Up @@ -824,6 +852,9 @@ typedef X509_VERIFY_PARAM = X509_VERIFY_PARAM_st;

final class X509_VERIFY_PARAM_st extends ffi.Opaque {}

typedef pem_password_cb = ffi.NativeFunction<
ffi.Int Function(ffi.Pointer<ffi.Char> buf, ffi.Int size, ffi.Int rwflag,
ffi.Pointer<ffi.Void> userdata)>;
typedef SSL_CTX = ssl_ctx_st;

final class ssl_ctx_st extends ffi.Opaque {}
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ environment:
dependencies:
collection: ^1.18.0
ffi: ^2.1.2
meta: ^1.16.0

dev_dependencies:
ffigen: ^12.0.0
Expand Down Expand Up @@ -81,6 +82,7 @@ ffigen:
- OPENSSL_version_minor
- OPENSSL_version_patch
- SSL_CTX_set_security_level
- PEM_read_bio_X509
macros:
include:
- BIO_C_SET_BUF_MEM_EOF_RETURN
Expand Down
Loading

0 comments on commit 1ce833c

Please sign in to comment.