use crate::{
client::{
config::{ClientCertSource, ClientCertificate, Config},
TrustConfig,
},
error::{Error, IoErrorKind},
};
use futures_util::io::{AsyncRead, AsyncWrite};
pub(crate) use opentls::async_io::{TlsConnector, TlsStream};
use opentls::{Certificate, Identity};
use std::fs;
use tracing::{event, Level};
fn load_identity(cert: &ClientCertificate) -> crate::Result<Identity> {
match &cert.source {
ClientCertSource::Pkcs12 { path, password } => {
let buf = fs::read(path).map_err(|e| Error::Io {
kind: IoErrorKind::InvalidData,
message: format!(
"Could not read PKCS#12 identity {}: {e}",
path.to_string_lossy()
),
})?;
Ok(Identity::from_pkcs12(&buf, password)?)
}
ClientCertSource::CertAndKey { .. } => Err(Error::Tls(
"The vendored-openssl (opentls) backend does not support separate \
certificate/key files for client authentication; supply a PKCS#12 \
bundle via `Config::client_certificate_pkcs12` instead."
.to_string(),
)),
}
}
pub(crate) const TDS80_ALPN_PROTOCOL: &str = "tds/8.0";
pub(crate) const fn supports_alpn() -> bool {
false
}
pub(crate) async fn create_tls_stream<S: AsyncRead + AsyncWrite + Unpin + Send>(
config: &Config,
stream: S,
) -> crate::Result<TlsStream<S>> {
if !supports_alpn() {
event!(
Level::WARN,
"The `vendored-openssl` (opentls) TLS backend cannot advertise the \
`{TDS80_ALPN_PROTOCOL}` ALPN protocol; TDS 8.0 strict-encryption \
negotiation is unavailable. Use the `native-tls` or `rustls` \
backend if TDS 8.0 ALPN negotiation is required."
);
}
let mut builder = TlsConnector::new();
if matches!(config.encryption, crate::EncryptionLevel::Strict) {
event!(
Level::WARN,
"OpenTLS does not support ALPN, so the TDS 8.0 ALPN protocol will not be requested. SQL Server will assume TDS 8.0."
);
}
if let Some(cert) = config.get_client_certificate() {
event!(
Level::DEBUG,
"Presenting a client certificate for mutual TLS."
);
builder = builder.identity(load_identity(cert)?);
}
match &config.trust {
TrustConfig::CaCertificateLocation(path) => {
if let Ok(buf) = fs::read(path) {
let cert = match path.extension() {
Some(ext)
if ext.to_ascii_lowercase() == "pem"
|| ext.to_ascii_lowercase() == "crt" =>
{
Some(Certificate::from_pem(&buf)?)
}
Some(ext) if ext.to_ascii_lowercase() == "der" => {
Some(Certificate::from_der(&buf)?)
}
Some(_) | None => return Err(Error::Io {
kind: IoErrorKind::InvalidInput,
message: "Provided CA certificate with unsupported file-extension! Supported types are pem, crt and der.".to_string()}),
};
if let Some(c) = cert {
builder = builder.add_root_certificate(c);
}
} else {
return Err(Error::Io {
kind: IoErrorKind::InvalidData,
message: "Could not read provided CA certificate!".to_string(),
});
}
}
TrustConfig::TrustAll => {
event!(
Level::WARN,
"Trusting the server certificate without validation."
);
builder = builder.danger_accept_invalid_certs(true);
builder = builder.danger_accept_invalid_hostnames(true);
builder = builder.use_sni(false);
}
TrustConfig::Default => {
event!(Level::DEBUG, "Using default trust configuration.");
}
}
Ok(builder
.connect(config.get_hostname_in_certificate(), stream)
.await?)
}
#[cfg(test)]
mod tests {
use super::{supports_alpn, TDS80_ALPN_PROTOCOL};
#[test]
fn tds80_alpn_identifier_is_stable() {
assert_eq!(TDS80_ALPN_PROTOCOL, "tds/8.0");
}
#[test]
fn opentls_backend_cannot_advertise_alpn() {
assert!(!supports_alpn());
}
}