Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ default = ["runtime-smol"]

vendored = ["native-tls/vendored"]

# Expose ALPN configuration on server-side `TlsAcceptor` (no-op on Apple platforms).
alpn-accept = ["native-tls/alpn-accept"]

# Runtime
runtime-smol = ["futures-util"]
runtime-tokio = ["tokio"]
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,14 @@ $ cargo add async-native-tls

* `runtime-tokio`: Use the `tokio` runtime. This is mutually exclusive with `runtime-smol`.

* `alpn-accept`: Enable server-side ALPN configuration via
`TlsAcceptor::new_with_alpn`. Note that the underlying `native-tls` crate
does not implement server-side ALPN on Apple platforms (Secure Transport
exposes no public API for it), so the configured protocols are silently
ignored there. Client-side ALPN (`TlsConnector::request_alpns` and
`TlsStream::negotiated_alpn`) is always available and not gated by this
feature.

## Example

#### smol
Expand Down
74 changes: 74 additions & 0 deletions src/acceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,41 @@ impl TlsAcceptor {
Ok(TlsAcceptor(native_tls::TlsAcceptor::new(identity)?))
}

/// Create a new TlsAcceptor that advertises the given ALPN protocols to clients.
///
/// `protocols` is a server-preference-ordered list of ALPN identifiers (for
/// example `&["h2", "http/1.1"]`).
///
/// # Platform support
///
/// On Apple platforms, the underlying `native-tls` crate is built on
/// Secure Transport, which does not expose a server-side ALPN selection
/// API. The supplied protocols are silently ignored there — the handshake
/// proceeds without advertising an ALPN list and
/// [`TlsStream::negotiated_alpn`] will return `Ok(None)`. See
/// <https://github.com/rust-native-tls/rust-native-tls/issues/49>.
#[cfg(feature = "alpn-accept")]
#[cfg_attr(docsrs, doc(cfg(feature = "alpn-accept")))]
pub async fn new_with_alpn<R, S, P>(
mut file: R,
password: S,
protocols: &[P],
) -> Result<Self, Error>
where
R: AsyncRead + Unpin,
S: AsRef<str>,
P: AsRef<str>,
{
let mut identity = vec![];
file.read_to_end(&mut identity).await?;

let identity = native_tls::Identity::from_pkcs12(&identity, password.as_ref())?;
let acceptor = native_tls::TlsAcceptor::builder(identity)
.accept_alpn(protocols)
.build()?;
Ok(TlsAcceptor(acceptor))
}

/// Accepts a new client connection with the provided stream.
///
/// This function will internally call `TlsAcceptor::accept` to connect
Expand Down Expand Up @@ -135,4 +170,43 @@ mod tests {
assert_eq!(res, b"hello");
})
}

#[cfg(all(feature = "alpn-accept", not(target_vendor = "apple")))]
#[test]
fn test_acceptor_alpn() {
smol::block_on(async {
let key = File::open("tests/identity.pfx").await.unwrap();
let acceptor = TlsAcceptor::new_with_alpn(key, "hello", &["h2", "http/1.1"])
.await
.unwrap();
let listener = TcpListener::bind("127.0.0.1:8443").await.unwrap();
smol::spawn(async move {
let mut incoming = listener.incoming();
while let Some(stream) = incoming.next().await {
let acceptor = acceptor.clone();
let stream = stream.unwrap();
smol::spawn(async move {
let mut stream = acceptor.accept(stream).await.unwrap();
stream.write_all(b"hello").await.unwrap();
})
.detach();
}
})
.detach();

let stream = TcpStream::connect("127.0.0.1:8443").await.unwrap();
let connector = TlsConnector::new()
.danger_accept_invalid_certs(true)
.request_alpns(&["h2"]);

let mut stream = connector.connect("127.0.0.1", stream).await.unwrap();
assert_eq!(
stream.negotiated_alpn().unwrap().as_deref(),
Some(&b"h2"[..])
);
let mut res = Vec::new();
stream.read_to_end(&mut res).await.unwrap();
assert_eq!(res, b"hello");
})
}
}
14 changes: 14 additions & 0 deletions src/tls_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,20 @@ impl<S> TlsStream<S> {
{
self.0.tls_server_end_point()
}

/// Returns the protocol negotiated via ALPN, if any.
///
/// Returns `Ok(None)` if no ALPN protocol was negotiated, including when
/// neither peer advertised a list. On Apple platforms this also returns
/// `Ok(None)` for server-side acceptors configured via
/// [`TlsAcceptor::new_with_alpn`](crate::TlsAcceptor::new_with_alpn),
/// since Secure Transport does not implement server-side ALPN.
pub fn negotiated_alpn(&self) -> crate::Result<Option<Vec<u8>>>
where
S: AsyncRead + AsyncWrite + Unpin,
{
self.0.negotiated_alpn()
}
}

#[cfg(feature = "runtime-smol")]
Expand Down