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