88namespace OpenTelemetry . Exporter . OpenTelemetryProtocol . Implementation ;
99
1010/// <summary>
11- /// Factory for creating HttpClient instances configured with mTLS settings.
11+ /// Factory for creating HttpClient instances configured with TLS settings.
1212/// </summary>
1313internal static class OtlpSecureHttpClientFactory
1414{
1515 /// <summary>
16- /// Creates an HttpClient configured with mTLS settings.
16+ /// Creates an HttpClient configured with TLS settings based on the provided options .
1717 /// </summary>
18- /// <param name="mtlsOptions ">The mTLS configuration options.</param>
18+ /// <param name="tlsOptions ">The TLS configuration options.</param>
1919 /// <param name="configureClient">Optional action to configure the client.</param>
20- /// <returns>An HttpClient configured for mTLS .</returns>
21- /// <exception cref="ArgumentNullException">Thrown when <paramref name="mtlsOptions "/> is null.</exception>
22- /// <exception cref="InvalidOperationException">Thrown when mTLS is not enabled.</exception>
20+ /// <returns>An HttpClient configured for secure communication .</returns>
21+ /// <exception cref="ArgumentNullException">Thrown when <paramref name="tlsOptions "/> is null.</exception>
22+ /// <exception cref="InvalidOperationException">Thrown when TLS is not enabled.</exception>
2323 public static HttpClient CreateSecureHttpClient (
24- OtlpMtlsOptions mtlsOptions ,
24+ OtlpTlsOptions tlsOptions ,
2525 Action < HttpClient > ? configureClient = null )
2626 {
27- ArgumentNullException . ThrowIfNull ( mtlsOptions ) ;
27+ ArgumentNullException . ThrowIfNull ( tlsOptions ) ;
2828
29- if ( ! mtlsOptions . IsEnabled )
29+ if ( ! tlsOptions . IsTlsEnabled && ! tlsOptions . IsMtlsEnabled )
3030 {
31- throw new InvalidOperationException ( "mTLS options must include a client or CA certificate path." ) ;
31+ throw new InvalidOperationException (
32+ "TLS options must include at least a CA path or client certificate path." ) ;
3233 }
3334
34- HttpClientHandler ? handler = null ;
3535 X509Certificate2 ? caCertificate = null ;
3636 X509Certificate2 ? clientCertificate = null ;
37+ TlsHttpClientHandler ? handler = null ;
3738
3839 try
3940 {
40- // Load certificates
41- if ( ! string . IsNullOrEmpty ( mtlsOptions . CaCertificatePath ) )
41+ if ( ! string . IsNullOrEmpty ( tlsOptions . CaCertificatePath ) )
4242 {
4343 caCertificate = OtlpCertificateManager . LoadCaCertificate (
44- mtlsOptions . CaCertificatePath ) ;
44+ tlsOptions . CaCertificatePath ) ;
4545
46- if ( mtlsOptions . EnableCertificateChainValidation )
46+ if ( tlsOptions . EnableCertificateChainValidation )
4747 {
4848 OtlpCertificateManager . ValidateCertificateChain (
4949 caCertificate ,
5050 OtlpCertificateManager . CaCertificateType ) ;
5151 }
5252 }
5353
54- if ( ! string . IsNullOrEmpty ( mtlsOptions . ClientCertificatePath ) )
54+ if ( tlsOptions is OtlpMtlsOptions mtlsOptions && mtlsOptions . IsMtlsEnabled )
5555 {
56- if ( string . IsNullOrEmpty ( mtlsOptions . ClientKeyPath ) )
57- {
58- // Load certificate without separate key file (e.g., PKCS#12 format)
59- clientCertificate = OtlpCertificateManager . LoadClientCertificate (
60- mtlsOptions . ClientCertificatePath ,
61- null ) ;
62- }
63- else
64- {
65- clientCertificate = OtlpCertificateManager . LoadClientCertificate (
66- mtlsOptions . ClientCertificatePath ,
56+ clientCertificate = string . IsNullOrEmpty ( mtlsOptions . ClientKeyPath )
57+ ? OtlpCertificateManager . LoadClientCertificate (
58+ mtlsOptions . ClientCertificatePath ! ,
59+ null )
60+ : OtlpCertificateManager . LoadClientCertificate (
61+ mtlsOptions . ClientCertificatePath ! ,
6762 mtlsOptions . ClientKeyPath ) ;
68- }
6963
70- if ( mtlsOptions . EnableCertificateChainValidation )
64+ if ( tlsOptions . EnableCertificateChainValidation )
7165 {
7266 OtlpCertificateManager . ValidateCertificateChain (
7367 clientCertificate ,
@@ -77,89 +71,133 @@ public static HttpClient CreateSecureHttpClient(
7771 OpenTelemetryProtocolExporterEventSource . Log . MtlsConfigurationEnabled (
7872 clientCertificate . Subject ) ;
7973 }
74+ else if ( caCertificate != null )
75+ {
76+ OpenTelemetryProtocolExporterEventSource . Log . CaCertificateConfigured (
77+ caCertificate . Subject ) ;
78+ }
8079
81- // Create HttpClientHandler with mTLS configuration
80+ // Create HttpClientHandler and apply TLS configuration
8281#pragma warning disable CA2000 // Dispose objects before losing scope - HttpClientHandler is disposed by HttpClient
83- handler = new MtlsHttpClientHandler ( clientCertificate , caCertificate ) ;
82+ handler = new TlsHttpClientHandler ( caCertificate , clientCertificate ) ;
8483#pragma warning restore CA2000
85- handler . CheckCertificateRevocationList = true ;
8684
87- // Handler now owns the certificates and will dispose them when disposed.
85+ // Handler now owns certificates
8886 caCertificate = null ;
8987 clientCertificate = null ;
9088
89+ #pragma warning disable CA5399 // CheckCertificateRevocationList is set in ConfigureTls.
9190 var client = new HttpClient ( handler , disposeHandler : true ) ;
91+ #pragma warning restore CA5399
9292
9393 configureClient ? . Invoke ( client ) ;
9494
9595 return client ;
9696 }
9797 catch ( Exception ex )
9898 {
99- // Dispose handler if something went wrong
99+ // Clean up on failure
100100 handler ? . Dispose ( ) ;
101-
102- OpenTelemetryProtocolExporterEventSource . Log . MtlsHttpClientCreationFailed ( ex ) ;
103- throw ;
104- }
105- finally
106- {
107- // Dispose certificates as they are no longer needed after being added to the handler
108101 caCertificate ? . Dispose ( ) ;
109102 clientCertificate ? . Dispose ( ) ;
103+
104+ OpenTelemetryProtocolExporterEventSource . Log . SecureHttpClientCreationFailed ( ex ) ;
105+ throw ;
110106 }
111107 }
112108
113- private sealed class MtlsHttpClientHandler : HttpClientHandler
109+ /// <summary>
110+ /// Creates an HttpClient configured with mTLS settings.
111+ /// </summary>
112+ /// <param name="mtlsOptions">The mTLS configuration options.</param>
113+ /// <param name="configureClient">Optional action to configure the client.</param>
114+ /// <returns>An HttpClient configured for mTLS.</returns>
115+ /// <remarks>
116+ /// This method exists for backward compatibility. New code should use
117+ /// <see cref="CreateSecureHttpClient(OtlpTlsOptions, Action{HttpClient}?)"/>.
118+ /// </remarks>
119+ public static HttpClient CreateMtlsHttpClient (
120+ OtlpMtlsOptions mtlsOptions ,
121+ Action < HttpClient > ? configureClient = null )
122+ {
123+ return CreateSecureHttpClient ( mtlsOptions , configureClient ) ;
124+ }
125+
126+ /// <summary>
127+ /// HttpClientHandler that applies TLS configuration based on loaded certificates.
128+ /// </summary>
129+ private sealed class TlsHttpClientHandler : HttpClientHandler
114130 {
115131 private readonly X509Certificate2 ? caCertificate ;
116132 private readonly X509Certificate2 ? clientCertificate ;
133+ private bool disposed ;
117134
118- internal MtlsHttpClientHandler (
119- X509Certificate2 ? clientCertificate ,
120- X509Certificate2 ? caCertificate )
135+ internal TlsHttpClientHandler (
136+ X509Certificate2 ? caCertificate ,
137+ X509Certificate2 ? clientCertificate )
121138 {
122- this . clientCertificate = clientCertificate ;
123139 this . caCertificate = caCertificate ;
124- this . CheckCertificateRevocationList = true ;
140+ this . clientCertificate = clientCertificate ;
141+
142+ this . ConfigureTls ( ) ;
143+ }
125144
126- if ( clientCertificate != null )
145+ protected override void Dispose ( bool disposing )
146+ {
147+ if ( disposing && ! this . disposed )
127148 {
128- this . ClientCertificates . Add ( clientCertificate ) ;
129- this . ClientCertificateOptions = ClientCertificateOption . Manual ;
149+ this . clientCertificate ? . Dispose ( ) ;
150+ this . caCertificate ? . Dispose ( ) ;
151+ this . disposed = true ;
130152 }
131153
132- if ( caCertificate != null )
154+ base . Dispose ( disposing ) ;
155+ }
156+
157+ private void ConfigureTls ( )
158+ {
159+ this . CheckCertificateRevocationList = true ;
160+
161+ this . ConfigureClientCertificate ( ) ;
162+ this . ConfigureCaCertificateValidation ( ) ;
163+ }
164+
165+ private void ConfigureClientCertificate ( )
166+ {
167+ if ( this . clientCertificate == null )
133168 {
134- this . ServerCertificateCustomValidationCallback = (
135- httpRequestMessage ,
136- cert ,
137- chain ,
138- sslPolicyErrors ) =>
139- {
140- if ( cert == null || chain == null )
141- {
142- return false ;
143- }
144-
145- return OtlpCertificateManager . ValidateServerCertificate (
146- cert ,
147- chain ,
148- sslPolicyErrors ,
149- caCertificate ) ;
150- } ;
169+ return ;
151170 }
171+
172+ this . ClientCertificates . Add ( this . clientCertificate ) ;
173+ this . ClientCertificateOptions = ClientCertificateOption . Manual ;
152174 }
153175
154- protected override void Dispose ( bool disposing )
176+ private void ConfigureCaCertificateValidation ( )
155177 {
156- if ( disposing )
178+ if ( this . caCertificate == null )
157179 {
158- this . caCertificate ? . Dispose ( ) ;
159- this . clientCertificate ? . Dispose ( ) ;
180+ return ;
160181 }
161182
162- base . Dispose ( disposing ) ;
183+ var caCert = this . caCertificate ;
184+ this . ServerCertificateCustomValidationCallback = (
185+ httpRequestMessage ,
186+ cert ,
187+ chain ,
188+ sslPolicyErrors ) =>
189+ {
190+ if ( cert == null || chain == null )
191+ {
192+ return false ;
193+ }
194+
195+ return OtlpCertificateManager . ValidateServerCertificate (
196+ cert ,
197+ chain ,
198+ sslPolicyErrors ,
199+ caCert ) ;
200+ } ;
163201 }
164202 }
165203}
0 commit comments