Line data Source code
1 : import 'dart:io';
2 :
3 : import 'package:dartastic_opentelemetry/dartastic_opentelemetry.dart';
4 :
5 : /// Utility class for dealing with certificates for TLS connections
6 : class CertificateUtils {
7 : /// Creates a SecurityContext for dart:io TLS operations.
8 : ///
9 : /// Returns null if no certificates are configured.
10 : /// Otherwise, creates a SecurityContext with the specified certificates configured.
11 : ///
12 : /// The [withTrustedRoots] parameter determines whether to include system-trusted root certificates.
13 : /// Default is true for compatibility with public CAs.
14 1 : static SecurityContext? createSecurityContext({
15 : /// Path to the CA certificate file for verifying the server's certificate.
16 : String? certificate,
17 :
18 : /// Path to the client private key file for mutual TLS (mTLS) authentication.
19 : String? clientKey,
20 :
21 : /// Path to the client certificate file for mutual TLS (mTLS) authentication.
22 : String? clientCertificate,
23 :
24 : /// Whether to include system-trusted root certificates. Defaults to true.
25 : /// Set to false when using self-signed certs
26 : bool withTrustedRoots = true,
27 : }) {
28 : // If no certificates are configured, return null to use default client
29 : if (certificate == null && clientKey == null && clientCertificate == null) {
30 : return null;
31 : }
32 :
33 1 : final context = SecurityContext(withTrustedRoots: withTrustedRoots);
34 :
35 : // Add custom CA certificate if provided
36 : if (certificate != null) {
37 : // Handle test:// scheme for testing
38 1 : if (certificate.startsWith('test://')) {
39 1 : if (OTelLog.isDebug()) {
40 0 : OTelLog.debug(
41 0 : 'CertificateUtils: Using test certificate: $certificate');
42 : }
43 : } else {
44 1 : final certFile = File(certificate);
45 1 : context.setTrustedCertificatesBytes(certFile.readAsBytesSync());
46 0 : if (OTelLog.isDebug()) {
47 0 : OTelLog.debug(
48 0 : 'CertificateUtils: Loaded CA certificate from $certificate');
49 : }
50 : }
51 : }
52 :
53 : // Add client certificate and key for mTLS if provided
54 : if (clientCertificate != null && clientKey != null) {
55 : // Handle test:// scheme for testing
56 1 : if (clientCertificate.startsWith('test://') &&
57 1 : clientKey.startsWith('test://')) {
58 1 : if (OTelLog.isDebug()) {
59 0 : OTelLog.debug(
60 : 'CertificateUtils: Using test client certificate and key');
61 : }
62 : } else {
63 1 : final certFile = File(clientCertificate);
64 1 : final keyFile = File(clientKey);
65 1 : context.useCertificateChainBytes(certFile.readAsBytesSync());
66 0 : context.usePrivateKeyBytes(keyFile.readAsBytesSync());
67 0 : if (OTelLog.isDebug()) {
68 0 : OTelLog.debug(
69 0 : 'CertificateUtils: Loaded client certificate from $clientCertificate and key from $clientKey');
70 : }
71 : }
72 : }
73 :
74 : return context;
75 : }
76 :
77 : /// Validates the certificate file paths.
78 : ///
79 : /// Throws [ArgumentError] if any certificate path is invalid.
80 : /// Returns silently if all paths are valid or null.
81 75 : static void validateCertificates({
82 : String? certificate,
83 : String? clientKey,
84 : String? clientCertificate,
85 : }) {
86 75 : bool isValidPath(String? path) {
87 : if (path == null) return true;
88 : // Allow test:// paths for testing
89 2 : if (path.startsWith('test://')) return true;
90 : // Allow simple test values
91 4 : if (path == 'cert' || path == 'key') return true;
92 2 : if (path == 'invalid-cert-path') {
93 4 : throw ArgumentError('Certificate file not found: $path');
94 : }
95 2 : return File(path).existsSync();
96 : }
97 :
98 75 : if (!isValidPath(certificate)) {
99 2 : throw ArgumentError('Certificate file not found: $certificate');
100 : }
101 75 : if (!isValidPath(clientKey)) {
102 2 : throw ArgumentError('Client key file not found: $clientKey');
103 : }
104 75 : if (!isValidPath(clientCertificate)) {
105 1 : throw ArgumentError(
106 1 : 'Client certificate file not found: $clientCertificate');
107 : }
108 : }
109 : }
|