11// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22// SPDX-License-Identifier: Apache-2.0
33
4+ use crate :: connection:: Connection ;
45use core:: { convert:: TryInto , fmt, ptr:: NonNull , task:: Poll } ;
56use errno:: { errno, Errno } ;
67use 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+
50129enum 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+
76165impl 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) ]
400517mod 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