1use crate::{Conn, Pristine, startup::StartupMessage};
7use bytes::{BufMut as _, Bytes, BytesMut};
8
9const SSL_REQUEST_CODE: u32 = 80_877_103;
10const GSSENC_REQUEST_CODE: u32 = 80_877_104;
11const CANCEL_REQUEST_CODE: u32 = 80_877_102;
12
13pub const DEFAULT_MAX_PRE_STARTUP_PACKET_LEN: usize = 10_000;
15
16#[derive(Debug)]
18pub enum PreStartup {}
19
20#[derive(Debug)]
22pub enum Startup {}
23
24#[derive(Debug)]
26pub enum AwaitingSslReply {}
27
28#[derive(Debug)]
30pub enum AwaitingGssReply {}
31
32#[derive(Debug)]
34pub enum TlsHandshake {}
35
36#[derive(Debug)]
38pub enum GssHandshake {}
39
40#[derive(Debug)]
42pub enum Terminated {}
43
44#[derive(Debug)]
46pub enum ServerSslDecision {}
47
48#[derive(Debug)]
50pub enum ServerGssDecision {}
51
52#[derive(Debug)]
54pub enum PreStartupOffer<S, C = Pristine> {
55 Ssl(Conn<S, ServerSslDecision, C>),
57 Gss(Conn<S, ServerGssDecision, C>),
59 Cancel {
61 conn: Conn<S, Terminated, C>,
63 process_id: u32,
65 secret_key: Bytes,
67 },
68 Startup {
70 conn: Conn<S, Startup, C>,
72 message: StartupMessage,
74 },
75}
76
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
79pub enum EncryptionReply {
80 Accepted,
82 Rejected,
84 LegacyError,
86}
87
88impl EncryptionReply {
89 #[must_use]
91 pub const fn as_byte(self) -> u8 {
92 match self {
93 Self::Accepted => b'S',
94 Self::Rejected => b'N',
95 Self::LegacyError => b'E',
96 }
97 }
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
102pub enum PreStartupMessage {
103 SslRequest,
105 GssEncRequest,
107 CancelRequest {
109 process_id: u32,
111 secret_key: Bytes,
113 },
114 Startup(StartupMessage),
116}
117
118impl PreStartupMessage {
119 pub fn to_packet(&self) -> std::io::Result<Bytes> {
125 match self {
126 Self::SslRequest => Ok(Bytes::copy_from_slice(&request_packet(SSL_REQUEST_CODE))),
127 Self::GssEncRequest => Ok(Bytes::copy_from_slice(&request_packet(GSSENC_REQUEST_CODE))),
128 Self::CancelRequest {
129 process_id,
130 secret_key,
131 } => cancel_packet(*process_id, secret_key),
132 Self::Startup(message) => message.encode(),
133 }
134 }
135}
136
137pub fn decode_pre_startup(input: &mut BytesMut) -> std::io::Result<Option<PreStartupMessage>> {
143 decode_pre_startup_with_limit(input, DEFAULT_MAX_PRE_STARTUP_PACKET_LEN)
144}
145
146pub fn decode_pre_startup_with_limit(
156 input: &mut BytesMut,
157 max_packet_len: usize,
158) -> std::io::Result<Option<PreStartupMessage>> {
159 if !(8..=i32::MAX as usize).contains(&max_packet_len) {
160 return Err(invalid(
161 "pre-startup packet limit must be between 8 and i32::MAX bytes",
162 ));
163 }
164 if input.len() < 4 {
165 input.reserve(4 - input.len());
166 return Ok(None);
167 }
168 let length = usize::try_from(u32::from_be_bytes([input[0], input[1], input[2], input[3]]))
169 .map_err(|_| invalid("pre-startup packet length overflow"))?;
170 if length < 8 {
171 return Err(invalid("pre-startup packet is shorter than 8 bytes"));
172 }
173 if length > i32::MAX as usize {
174 return Err(invalid("pre-startup packet length exceeds i32::MAX"));
175 }
176 if length > max_packet_len {
177 return Err(invalid("pre-startup packet exceeds configured limit"));
178 }
179 if input.len() < length {
180 input.reserve(length - input.len());
181 return Ok(None);
182 }
183 let packet = input.split_to(length).freeze();
184 let code = u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]);
185 match code {
186 SSL_REQUEST_CODE if length == 8 => Ok(Some(PreStartupMessage::SslRequest)),
187 GSSENC_REQUEST_CODE if length == 8 => Ok(Some(PreStartupMessage::GssEncRequest)),
188 CANCEL_REQUEST_CODE => {
189 if !(16..=268).contains(&length) {
190 return Err(invalid("invalid CancelRequest length"));
191 }
192 let process_id = u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]);
193 let secret_key = packet.slice(12..);
194 Ok(Some(PreStartupMessage::CancelRequest {
195 process_id,
196 secret_key,
197 }))
198 }
199 _ => StartupMessage::decode(packet)
200 .map(PreStartupMessage::Startup)
201 .map(Some),
202 }
203}
204
205impl TryFrom<u8> for EncryptionReply {
206 type Error = InvalidEncryptionReply;
207
208 fn try_from(value: u8) -> Result<Self, Self::Error> {
209 match value {
210 b'S' => Ok(Self::Accepted),
211 b'N' => Ok(Self::Rejected),
212 b'E' => Ok(Self::LegacyError),
213 byte => Err(InvalidEncryptionReply(byte)),
214 }
215 }
216}
217
218#[derive(Clone, Copy, Debug, Eq, PartialEq)]
220pub struct InvalidEncryptionReply(
221 pub u8,
223);
224
225#[derive(Clone, Copy, Debug, Eq, PartialEq)]
227pub enum SslMode {
228 Disable,
230 Allow,
232 Prefer,
234 Require,
236 VerifyCa,
238 VerifyFull,
240}
241
242#[derive(Clone, Copy, Debug, Eq, PartialEq)]
244pub enum CertificateVerification {
245 None,
247 CertificateAuthority,
249 CertificateAuthorityAndHost,
251}
252
253#[derive(Clone, Copy, Debug, Eq, PartialEq)]
255pub struct SslStrategy {
256 pub request_on_first_connection: bool,
258 pub retry_with_ssl_after_plaintext_failure: bool,
260 pub allow_server_rejection: bool,
262 pub verification: CertificateVerification,
264}
265
266impl SslMode {
267 #[must_use]
269 pub const fn strategy(self) -> SslStrategy {
270 match self {
271 Self::Disable => SslStrategy {
272 request_on_first_connection: false,
273 retry_with_ssl_after_plaintext_failure: false,
274 allow_server_rejection: true,
275 verification: CertificateVerification::None,
276 },
277 Self::Allow => SslStrategy {
278 request_on_first_connection: false,
279 retry_with_ssl_after_plaintext_failure: true,
280 allow_server_rejection: true,
281 verification: CertificateVerification::None,
282 },
283 Self::Prefer => SslStrategy {
284 request_on_first_connection: true,
285 retry_with_ssl_after_plaintext_failure: false,
286 allow_server_rejection: true,
287 verification: CertificateVerification::None,
288 },
289 Self::Require => SslStrategy {
290 request_on_first_connection: true,
291 retry_with_ssl_after_plaintext_failure: false,
292 allow_server_rejection: false,
293 verification: CertificateVerification::None,
294 },
295 Self::VerifyCa => SslStrategy {
296 request_on_first_connection: true,
297 retry_with_ssl_after_plaintext_failure: false,
298 allow_server_rejection: false,
299 verification: CertificateVerification::CertificateAuthority,
300 },
301 Self::VerifyFull => SslStrategy {
302 request_on_first_connection: true,
303 retry_with_ssl_after_plaintext_failure: false,
304 allow_server_rejection: false,
305 verification: CertificateVerification::CertificateAuthorityAndHost,
306 },
307 }
308 }
309}
310
311#[derive(Debug)]
313pub enum Negotiation<S, Handshake, C = Pristine> {
314 Accepted(Conn<S, Handshake, C>),
316 Rejected(Conn<S, PreStartup, C>),
318 LegacyError(Conn<S, Terminated, C>),
320}
321
322#[derive(Debug)]
324pub enum SslModeNegotiation<S, C = Pristine> {
325 Accepted(Conn<S, TlsHandshake, C>),
327 Plaintext(Conn<S, PreStartup, C>),
329 RequiredRejected {
331 conn: Conn<S, Terminated, C>,
333 mode: SslMode,
335 },
336 LegacyError(Conn<S, Terminated, C>),
338}
339
340impl<S> Conn<S, PreStartup, Pristine> {
341 pub fn ssl_request(self) -> (Conn<S, AwaitingSslReply>, [u8; 8]) {
343 (self.transition(), ssl_request_packet())
344 }
345
346 pub fn gssenc_request(self) -> (Conn<S, AwaitingGssReply>, [u8; 8]) {
348 (self.transition(), gssenc_request_packet())
349 }
350
351 pub fn startup(
357 self,
358 message: &StartupMessage,
359 ) -> std::io::Result<(Conn<S, Startup>, bytes::Bytes)> {
360 Ok((self.transition(), message.encode()?))
361 }
362
363 pub fn cancel_request(
369 self,
370 process_id: u32,
371 secret_key: &[u8],
372 ) -> std::io::Result<(Conn<S, Terminated>, bytes::Bytes)> {
373 if !(4..=256).contains(&secret_key.len()) {
374 return Err(std::io::Error::new(
375 std::io::ErrorKind::InvalidInput,
376 "cancellation key length is outside 4..=256",
377 ));
378 }
379 Ok((self.transition(), cancel_packet(process_id, secret_key)?))
380 }
381}
382
383impl<S, C> Conn<S, PreStartup, C> {
384 pub fn offer_pre_startup(self, message: PreStartupMessage) -> PreStartupOffer<S, C> {
386 match message {
387 PreStartupMessage::SslRequest => PreStartupOffer::Ssl(self.transition()),
388 PreStartupMessage::GssEncRequest => PreStartupOffer::Gss(self.transition()),
389 PreStartupMessage::CancelRequest {
390 process_id,
391 secret_key,
392 } => PreStartupOffer::Cancel {
393 conn: self.transition(),
394 process_id,
395 secret_key,
396 },
397 PreStartupMessage::Startup(message) => PreStartupOffer::Startup {
398 conn: self.transition(),
399 message,
400 },
401 }
402 }
403}
404
405impl<S, C> Conn<S, ServerSslDecision, C> {
406 pub fn reject_ssl(self) -> (Conn<S, PreStartup, C>, u8) {
408 (self.transition(), EncryptionReply::Rejected.as_byte())
409 }
410
411 pub fn accept_ssl(self) -> (Conn<S, TlsHandshake, C>, u8) {
413 (self.transition(), EncryptionReply::Accepted.as_byte())
414 }
415
416 pub fn legacy_ssl_error(self) -> (Conn<S, Terminated, C>, u8) {
418 (self.transition(), EncryptionReply::LegacyError.as_byte())
419 }
420}
421
422impl<S, C> Conn<S, ServerGssDecision, C> {
423 pub fn reject_gss(self) -> (Conn<S, PreStartup, C>, u8) {
425 (self.transition(), EncryptionReply::Rejected.as_byte())
426 }
427
428 pub fn accept_gss(self) -> (Conn<S, GssHandshake, C>, u8) {
430 (self.transition(), EncryptionReply::Accepted.as_byte())
431 }
432
433 pub fn legacy_gss_error(self) -> (Conn<S, Terminated, C>, u8) {
435 (self.transition(), EncryptionReply::LegacyError.as_byte())
436 }
437}
438
439impl<S, C> Conn<S, AwaitingSslReply, C> {
440 pub fn receive_reply(self, reply: EncryptionReply) -> Negotiation<S, TlsHandshake, C> {
450 match reply {
451 EncryptionReply::Accepted => Negotiation::Accepted(self.transition()),
452 EncryptionReply::Rejected => Negotiation::Rejected(self.transition()),
453 EncryptionReply::LegacyError => Negotiation::LegacyError(self.transition()),
454 }
455 }
456
457 pub fn apply_ssl_reply(
459 self,
460 reply: EncryptionReply,
461 mode: SslMode,
462 ) -> SslModeNegotiation<S, C> {
463 match reply {
464 EncryptionReply::Accepted => SslModeNegotiation::Accepted(self.transition()),
465 EncryptionReply::Rejected if mode.strategy().allow_server_rejection => {
466 SslModeNegotiation::Plaintext(self.transition())
467 }
468 EncryptionReply::Rejected => SslModeNegotiation::RequiredRejected {
469 conn: self.transition(),
470 mode,
471 },
472 EncryptionReply::LegacyError => SslModeNegotiation::LegacyError(self.transition()),
473 }
474 }
475}
476
477impl<S, C> Conn<S, AwaitingGssReply, C> {
478 pub fn receive_reply(self, reply: EncryptionReply) -> Negotiation<S, GssHandshake, C> {
480 match reply {
481 EncryptionReply::Accepted => Negotiation::Accepted(self.transition()),
482 EncryptionReply::Rejected => Negotiation::Rejected(self.transition()),
483 EncryptionReply::LegacyError => Negotiation::LegacyError(self.transition()),
484 }
485 }
486}
487
488impl<S, C> Conn<S, TlsHandshake, C> {
489 pub fn finish_tls<Tls>(self, upgrade: impl FnOnce(S) -> Tls) -> Conn<Tls, PreStartup, C> {
491 self.map_transport(upgrade).transition()
492 }
493}
494
495impl<S, C> Conn<S, TlsHandshake, C> {
496 pub fn finish_server_tls<Tls>(
498 self,
499 upgrade: impl FnOnce(S) -> Tls,
500 ) -> Conn<Tls, PreStartup, C> {
501 self.map_transport(upgrade).transition()
502 }
503}
504
505impl<S> Conn<S, GssHandshake, Pristine> {
506 pub fn finish_gss<Gss>(self, upgrade: impl FnOnce(S) -> Gss) -> Conn<Gss, PreStartup> {
508 Conn::new(upgrade(self.into_transport()))
509 }
510}
511
512impl<S, C> Conn<S, GssHandshake, C> {
513 pub fn finish_server_gss<Gss>(
515 self,
516 upgrade: impl FnOnce(S) -> Gss,
517 ) -> Conn<Gss, PreStartup, C> {
518 self.map_transport(upgrade).transition()
519 }
520}
521
522pub(crate) const fn ssl_request_packet() -> [u8; 8] {
523 request_packet(SSL_REQUEST_CODE)
524}
525
526pub(crate) const fn gssenc_request_packet() -> [u8; 8] {
527 request_packet(GSSENC_REQUEST_CODE)
528}
529
530const fn request_packet(code: u32) -> [u8; 8] {
531 let length = 8_u32.to_be_bytes();
532 let code = code.to_be_bytes();
533 [
534 length[0], length[1], length[2], length[3], code[0], code[1], code[2], code[3],
535 ]
536}
537
538fn cancel_packet(process_id: u32, secret_key: &[u8]) -> std::io::Result<Bytes> {
539 if !(4..=256).contains(&secret_key.len()) {
540 return Err(std::io::Error::new(
541 std::io::ErrorKind::InvalidInput,
542 "cancellation key length is outside 4..=256",
543 ));
544 }
545 let key_length =
546 u32::try_from(secret_key.len()).map_err(|_| invalid("cancellation key length overflow"))?;
547 let length = 12 + key_length;
548 let mut packet = BytesMut::with_capacity(12 + secret_key.len());
549 packet.put_u32(length);
550 packet.put_u32(CANCEL_REQUEST_CODE);
551 packet.put_u32(process_id);
552 packet.extend_from_slice(secret_key);
553 Ok(packet.freeze())
554}
555
556fn invalid(message: &'static str) -> std::io::Error {
557 std::io::Error::new(std::io::ErrorKind::InvalidData, message)
558}
559
560#[cfg(test)]
561mod tests {
562 use super::*;
563
564 #[test]
565 fn encodes_special_requests_in_network_byte_order() {
566 let (pending, ssl) = Conn::new(()).ssl_request();
567 assert_eq!(ssl, [0, 0, 0, 8, 4, 210, 22, 47]);
568 pending.into_transport();
569
570 let (terminated, cancel) = Conn::new(())
571 .cancel_request(0x0102_0304, &[5, 6, 7, 8])
572 .expect("valid protocol 3.0 cancellation key");
573 assert_eq!(
574 &cancel[..],
575 [0, 0, 0, 16, 4, 210, 22, 46, 1, 2, 3, 4, 5, 6, 7, 8]
576 );
577 terminated.into_transport();
578 }
579
580 #[test]
581 fn tls_upgrade_changes_the_transport_type() {
582 struct Tcp;
583 struct Tls;
584
585 let (pending, _) = Conn::new(Tcp).ssl_request();
586 let Negotiation::Accepted(handshake) = pending.receive_reply(EncryptionReply::Accepted)
587 else {
588 panic!("unexpected negotiation branch")
589 };
590 let upgraded: Conn<Tls, PreStartup> = handshake.finish_tls(|Tcp| Tls);
591 let message = StartupMessage {
592 version: crate::startup::ProtocolVersion::V3_0,
593 parameters: std::collections::BTreeMap::new(),
594 };
595 let (startup, _) = upgraded.startup(&message).expect("valid startup message");
596 let _transport = startup.into_transport();
597 }
598
599 #[test]
600 fn sslmode_allow_starts_plaintext_then_reconnects_with_tls() {
601 assert_eq!(
602 SslMode::Allow.strategy(),
603 SslStrategy {
604 request_on_first_connection: false,
605 retry_with_ssl_after_plaintext_failure: true,
606 allow_server_rejection: true,
607 verification: CertificateVerification::None,
608 }
609 );
610 }
611
612 #[test]
613 fn verify_full_requires_tls_ca_and_hostname() {
614 assert_eq!(
615 SslMode::VerifyFull.strategy(),
616 SslStrategy {
617 request_on_first_connection: true,
618 retry_with_ssl_after_plaintext_failure: false,
619 allow_server_rejection: false,
620 verification: CertificateVerification::CertificateAuthorityAndHost,
621 }
622 );
623 }
624
625 #[test]
626 fn sslmode_rejection_is_plaintext_only_when_policy_allows_it() {
627 let (pending, _) = Conn::new(()).ssl_request();
628 let SslModeNegotiation::Plaintext(plaintext) =
629 pending.apply_ssl_reply(EncryptionReply::Rejected, SslMode::Prefer)
630 else {
631 panic!("prefer should permit a plaintext fallback")
632 };
633 plaintext.into_transport();
634
635 let (pending, _) = Conn::new(()).ssl_request();
636 let SslModeNegotiation::RequiredRejected { conn, mode } =
637 pending.apply_ssl_reply(EncryptionReply::Rejected, SslMode::VerifyFull)
638 else {
639 panic!("verify-full must reject a server without TLS")
640 };
641 assert_eq!(mode, SslMode::VerifyFull);
642 conn.into_transport();
643 }
644
645 #[test]
646 fn encryption_negotiation_and_upgrade_preserve_cleanliness() {
647 fn require_dirty<S>(conn: Conn<S, PreStartup, crate::Dirty>) {
648 conn.into_transport();
649 }
650
651 let pending: Conn<(), AwaitingSslReply, crate::Dirty> = Conn::new(()).transition();
652 let Negotiation::Accepted(handshake) = pending.receive_reply(EncryptionReply::Accepted)
653 else {
654 panic!("expected the TLS handshake branch")
655 };
656 let upgraded = handshake.finish_tls(|()| 42_u8);
657
658 require_dirty(upgraded);
659 }
660
661 #[test]
662 fn incrementally_decodes_each_pre_startup_branch() {
663 let messages = [
664 PreStartupMessage::SslRequest,
665 PreStartupMessage::GssEncRequest,
666 PreStartupMessage::CancelRequest {
667 process_id: 42,
668 secret_key: Bytes::from_static(&[7; 32]),
669 },
670 PreStartupMessage::Startup(StartupMessage {
671 version: crate::startup::ProtocolVersion::V3_2,
672 parameters: std::collections::BTreeMap::from([(
673 Bytes::from_static(b"user"),
674 Bytes::from_static(b"postgres"),
675 )]),
676 }),
677 ];
678
679 for message in messages {
680 let packet = message.to_packet().expect("encodable pre-startup message");
681 let mut input = BytesMut::from(&packet[..3]);
682 assert_eq!(
683 decode_pre_startup(&mut input).expect("partial input is valid"),
684 None
685 );
686 input.extend_from_slice(&packet[3..]);
687 assert_eq!(
688 decode_pre_startup(&mut input).expect("complete packet is valid"),
689 Some(message)
690 );
691 assert!(input.is_empty());
692 }
693 }
694
695 #[test]
696 fn rejects_oversized_pre_startup_before_reserving_body() {
697 let mut input = BytesMut::from(&10_001_u32.to_be_bytes()[..]);
698 let capacity = input.capacity();
699
700 let error = decode_pre_startup(&mut input).expect_err("packet exceeds the default limit");
701
702 assert_eq!(error.kind(), std::io::ErrorKind::InvalidData);
703 assert_eq!(input.len(), 4);
704 assert_eq!(input.capacity(), capacity);
705 }
706
707 #[test]
708 fn validates_custom_pre_startup_limit() {
709 let packet = PreStartupMessage::SslRequest
710 .to_packet()
711 .expect("SSL request is encodable");
712
713 assert!(decode_pre_startup_with_limit(&mut BytesMut::from(&packet[..]), 7).is_err());
714 assert!(
715 decode_pre_startup_with_limit(&mut BytesMut::from(&packet[..]), i32::MAX as usize + 1)
716 .is_err()
717 );
718 }
719}