Skip to content

Commit 2f0cbe5

Browse files
committed
Add alerts to alert errors
1 parent 57f7c10 commit 2f0cbe5

2 files changed

Lines changed: 171 additions & 24 deletions

File tree

bindings/rust/extended/s2n-tls/src/connection.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,8 @@ impl Connection {
637637
pub fn poll_negotiate(&mut self) -> Poll<Result<&mut Self, Error>> {
638638
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
639639
self.poll_negotiate_method(|conn| unsafe {
640-
s2n_negotiate(conn.as_ptr(), &mut blocked).into_poll()
640+
let result = s2n_negotiate(conn.as_ptr(), &mut blocked);
641+
(conn, result).into_poll()
641642
})
642643
.map_ok(|_| self)
643644
}
@@ -653,7 +654,10 @@ impl Connection {
653654
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
654655
let buf_len: isize = buf.len().try_into().map_err(|_| Error::INVALID_INPUT)?;
655656
let buf_ptr = buf.as_ptr() as *const ::libc::c_void;
656-
unsafe { s2n_send(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked).into_poll() }
657+
unsafe {
658+
let result = s2n_send(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked);
659+
(self, result).into_poll()
660+
}
657661
}
658662

659663
#[cfg(not(feature = "unstable-renegotiate"))]
@@ -663,7 +667,10 @@ impl Connection {
663667
buf_len: isize,
664668
) -> Poll<Result<usize, Error>> {
665669
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
666-
unsafe { s2n_recv(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked).into_poll() }
670+
unsafe {
671+
let result = s2n_recv(self.connection.as_ptr(), buf_ptr, buf_len, &mut blocked);
672+
(self, result).into_poll()
673+
}
667674
}
668675

669676
/// Reads and decrypts data from a connection where
@@ -718,9 +725,8 @@ impl Connection {
718725
pub fn poll_flush(&mut self) -> Poll<Result<&mut Self, Error>> {
719726
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
720727
unsafe {
721-
s2n_flush(self.connection.as_ptr(), &mut blocked)
722-
.into_poll()
723-
.map_ok(|_| self)
728+
let result = s2n_flush(self.connection.as_ptr(), &mut blocked);
729+
(&self, result).into_poll().map_ok(|_| self)
724730
}
725731
}
726732

@@ -744,9 +750,8 @@ impl Connection {
744750
}
745751
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
746752
unsafe {
747-
s2n_shutdown(self.connection.as_ptr(), &mut blocked)
748-
.into_poll()
749-
.map_ok(|_| self)
753+
let result = s2n_shutdown(self.connection.as_ptr(), &mut blocked);
754+
(&self, result).into_poll().map_ok(|_| self)
750755
}
751756
}
752757

@@ -762,9 +767,8 @@ impl Connection {
762767
}
763768
let mut blocked = s2n_blocked_status::NOT_BLOCKED;
764769
unsafe {
765-
s2n_shutdown_send(self.connection.as_ptr(), &mut blocked)
766-
.into_poll()
767-
.map_ok(|_| self)
770+
let result = s2n_shutdown_send(self.connection.as_ptr(), &mut blocked);
771+
(&self, result).into_poll().map_ok(|_| self)
768772
}
769773
}
770774

bindings/rust/extended/s2n-tls/src/error.rs

Lines changed: 155 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
use crate::connection::Connection;
45
use core::{convert::TryInto, fmt, ptr::NonNull, task::Poll};
56
use errno::{errno, Errno};
67
use libc::c_char;
@@ -47,9 +48,87 @@ impl From<libc::c_int> for ErrorType {
4748
}
4849
}
4950

51+
#[non_exhaustive]
52+
#[derive(Clone, Copy, Debug, PartialEq)]
53+
pub enum AlertDescription {
54+
CloseNotify,
55+
UnexpectedMessage,
56+
BadRecordMac,
57+
RecordOverflow,
58+
HandshakeFailure,
59+
BadCertificate,
60+
UnsupportedCertificate,
61+
CertificateRevoked,
62+
CertificateExpired,
63+
CertificateUnknown,
64+
IllegalParameter,
65+
UnknownCa,
66+
AccessDenied,
67+
DecodeError,
68+
DecryptError,
69+
ProtocolVersion,
70+
InsufficientSecurity,
71+
InternalError,
72+
InappropriateFallback,
73+
UserCanceled,
74+
NoRenegotiation,
75+
MissingExtension,
76+
UnsupportedExtension,
77+
UnrecognizedName,
78+
BadCertificateStatusResponse,
79+
UnknownPskIdentity,
80+
CertificateRequired,
81+
NoApplicationProtocol,
82+
Unknown(u8),
83+
}
84+
85+
impl From<u8> for AlertDescription {
86+
fn from(code: u8) -> Self {
87+
match code {
88+
0 => AlertDescription::CloseNotify,
89+
10 => AlertDescription::UnexpectedMessage,
90+
20 => AlertDescription::BadRecordMac,
91+
22 => AlertDescription::RecordOverflow,
92+
40 => AlertDescription::HandshakeFailure,
93+
42 => AlertDescription::BadCertificate,
94+
43 => AlertDescription::UnsupportedCertificate,
95+
44 => AlertDescription::CertificateRevoked,
96+
45 => AlertDescription::CertificateExpired,
97+
46 => AlertDescription::CertificateUnknown,
98+
47 => AlertDescription::IllegalParameter,
99+
48 => AlertDescription::UnknownCa,
100+
49 => AlertDescription::AccessDenied,
101+
50 => AlertDescription::DecodeError,
102+
51 => AlertDescription::DecryptError,
103+
70 => AlertDescription::ProtocolVersion,
104+
71 => AlertDescription::InsufficientSecurity,
105+
80 => AlertDescription::InternalError,
106+
86 => AlertDescription::InappropriateFallback,
107+
90 => AlertDescription::UserCanceled,
108+
100 => AlertDescription::NoRenegotiation,
109+
109 => AlertDescription::MissingExtension,
110+
110 => AlertDescription::UnsupportedExtension,
111+
112 => AlertDescription::UnrecognizedName,
112+
113 => AlertDescription::BadCertificateStatusResponse,
113+
115 => AlertDescription::UnknownPskIdentity,
114+
116 => AlertDescription::CertificateRequired,
115+
120 => AlertDescription::NoApplicationProtocol,
116+
code => AlertDescription::Unknown(code),
117+
}
118+
}
119+
}
120+
121+
/// Errors are not necessarily tied to connections.
122+
/// However, some errors are difficult to debug without additional information
123+
/// from a connection. Store any helpful connection context here.
124+
#[derive(Clone, Debug, Default, PartialEq)]
125+
struct ConnContext {
126+
alert: Option<AlertDescription>,
127+
}
128+
50129
enum Context {
51130
Bindings(ErrorType, &'static str, &'static str),
52-
Code(s2n_status_code::Type, Errno),
131+
Code(s2n_status_code::Type, Errno, ConnContext),
53132
Application(Box<dyn std::error::Error + Send + Sync + 'static>),
54133
}
55134

@@ -73,6 +152,16 @@ impl Fallible for s2n_status_code::Type {
73152
}
74153
}
75154

155+
impl<C: AsRef<Connection>, R: Fallible> Fallible for (C, R) {
156+
type Output = R::Output;
157+
158+
fn into_result(self) -> Result<Self::Output, Error> {
159+
self.1
160+
.into_result()
161+
.map_err(|e| e.capture_context(self.0.as_ref()))
162+
}
163+
}
164+
76165
impl Fallible for isize {
77166
type Output = usize;
78167

@@ -186,6 +275,7 @@ impl Error {
186275

187276
fn capture() -> Self {
188277
unsafe {
278+
let errno = errno();
189279
let s2n_errno = s2n_errno_location();
190280

191281
let code = *s2n_errno;
@@ -195,16 +285,32 @@ impl Error {
195285
//# an error: s2n_errno = S2N_ERR_T_OK
196286
*s2n_errno = s2n_error_type::OK as _;
197287

198-
Self(Context::Code(code, errno()))
288+
Self(Context::Code(code, errno, ConnContext::default()))
199289
}
200290
}
201291

292+
fn capture_context(mut self, conn: &Connection) -> Self {
293+
let kind = self.kind();
294+
match &mut self.0 {
295+
Context::Code(_, _, debug) => {
296+
match kind {
297+
ErrorType::Alert => {
298+
debug.alert = conn.alert().map(|a| a.into());
299+
}
300+
_ => {}
301+
};
302+
}
303+
_ => {}
304+
};
305+
self
306+
}
307+
202308
/// Corresponds to [s2n_strerror_name] for ErrorSource::Library errors.
203309
pub fn name(&self) -> &'static str {
204310
match self.0 {
205311
Context::Bindings(_, name, _) => name,
206312
Context::Application(_) => "ApplicationError",
207-
Context::Code(code, _) => unsafe {
313+
Context::Code(code, _, _) => unsafe {
208314
// Safety: we assume the string has a valid encoding coming from s2n
209315
cstr_to_str(s2n_strerror_name(code))
210316
},
@@ -216,7 +322,7 @@ impl Error {
216322
match self.0 {
217323
Context::Bindings(_, _, msg) => msg,
218324
Context::Application(_) => "An error occurred while executing application code",
219-
Context::Code(code, _) => unsafe {
325+
Context::Code(code, _, _) => unsafe {
220326
// Safety: we assume the string has a valid encoding coming from s2n
221327
cstr_to_str(s2n_strerror(code, core::ptr::null()))
222328
},
@@ -227,7 +333,7 @@ impl Error {
227333
pub fn debug(&self) -> Option<&'static str> {
228334
match self.0 {
229335
Context::Bindings(_, _, _) | Context::Application(_) => None,
230-
Context::Code(code, _) => unsafe {
336+
Context::Code(code, _, _) => unsafe {
231337
let debug_info = s2n_strerror_debug(code, core::ptr::null());
232338

233339
// The debug string should be set to a constant static string
@@ -249,15 +355,15 @@ impl Error {
249355
match self.0 {
250356
Context::Bindings(error_type, _, _) => error_type,
251357
Context::Application(_) => ErrorType::Application,
252-
Context::Code(code, _) => unsafe { ErrorType::from(s2n_error_get_type(code)) },
358+
Context::Code(code, _, _) => unsafe { ErrorType::from(s2n_error_get_type(code)) },
253359
}
254360
}
255361

256362
pub fn source(&self) -> ErrorSource {
257363
match self.0 {
258364
Context::Bindings(_, _, _) => ErrorSource::Bindings,
259365
Context::Application(_) => ErrorSource::Application,
260-
Context::Code(_, _) => ErrorSource::Library,
366+
Context::Code(_, _, _) => ErrorSource::Library,
261367
}
262368
}
263369

@@ -272,6 +378,13 @@ impl Error {
272378
}
273379
}
274380

381+
pub fn alert_description(&self) -> Option<AlertDescription> {
382+
match &self.0 {
383+
Context::Code(_, _, debug) => debug.alert,
384+
_ => None,
385+
}
386+
}
387+
275388
pub fn is_retryable(&self) -> bool {
276389
matches!(self.kind(), ErrorType::Blocked)
277390
}
@@ -290,7 +403,7 @@ impl Error {
290403
pub fn alert(&self) -> Option<u8> {
291404
match self.0 {
292405
Context::Bindings(_, _, _) | Context::Application(_) => None,
293-
Context::Code(code, _) => {
406+
Context::Code(code, _, _) => {
294407
let mut alert = 0;
295408
let r = unsafe { s2n_error_get_alert(code, &mut alert) };
296409
match r.into_result() {
@@ -327,7 +440,7 @@ impl From<Error> for std::io::Error {
327440
fn from(input: Error) -> Self {
328441
let kind = match input.kind() {
329442
ErrorType::IOError => {
330-
if let Context::Code(_, errno) = input.0 {
443+
if let Context::Code(_, errno, _) = input.0 {
331444
let bare = std::io::Error::from_raw_os_error(errno.0);
332445
bare.kind()
333446
} else {
@@ -350,7 +463,7 @@ impl fmt::Debug for Error {
350463
}
351464

352465
let mut s = f.debug_struct("Error");
353-
if let Context::Code(code, _) = self.0 {
466+
if let Context::Code(code, _, _) = self.0 {
354467
s.field("code", &code);
355468
}
356469

@@ -363,10 +476,14 @@ impl fmt::Debug for Error {
363476
s.field("debug", &debug);
364477
}
365478

479+
if let Some(alert) = self.alert_description() {
480+
s.field("alert", &alert);
481+
}
482+
366483
// "errno" is only known to be meaningful for IOErrors.
367484
// However, it has occasionally proved useful for debugging
368485
// other errors, so include it for all errors.
369-
if let Context::Code(_, errno) = self.0 {
486+
if let Context::Code(_, errno, _) = self.0 {
370487
s.field("errno", &errno.to_string());
371488
}
372489

@@ -399,8 +516,11 @@ impl std::error::Error for Error {
399516
#[cfg(test)]
400517
mod tests {
401518
use super::*;
402-
use crate::{enums::Version, testing::client_hello::CustomError};
519+
use crate::{
520+
enums::Version, security, testing, testing::client_hello::CustomError, testing::TestPair,
521+
};
403522
use errno::set_errno;
523+
use std::io::Write;
404524

405525
const FAILURE: isize = -1;
406526

@@ -508,4 +628,27 @@ mod tests {
508628
assert_eq!(error.debug(), None);
509629
assert_eq!(error.source(), ErrorSource::Bindings);
510630
}
631+
632+
#[test]
633+
fn handshake_error_includes_alert() -> Result<(), Box<dyn std::error::Error>> {
634+
let mut builder = testing::config_builder(&security::DEFAULT_TLS13)?;
635+
builder.set_max_blinding_delay(0)?;
636+
let config = builder.build()?;
637+
let mut pair = TestPair::from_config(&config);
638+
639+
const ALERT_CODE: u8 = 45;
640+
const ALERT: &[u8] = &[21, 3, 3, 0, 2, 1, ALERT_CODE];
641+
642+
// buffer the alert so that it's the first data read by the client
643+
pair.io.server_tx_stream.borrow_mut().write(ALERT)?;
644+
645+
let result = pair.handshake();
646+
assert!(result.is_err());
647+
let error = result.unwrap_err();
648+
assert!(error.kind() == ErrorType::Alert);
649+
assert!(error.alert_description().unwrap() == ALERT_CODE.into());
650+
assert!(format!("{:?}", error).contains("alert: CertificateExpired"));
651+
652+
Ok(())
653+
}
511654
}

0 commit comments

Comments
 (0)