brass_aphid_wire_decryption/decryption/
mod.rs

1use std::{ffi::c_void, io::ErrorKind};
2
3use crate::decryption::{
4    key_manager::KeyManager,
5    s2n_tls_intercept::{generic_recv_cb, generic_send_cb, ArchaicCPipe, PeerIntoS2ntlsInsides},
6    stream_decrypter::StreamDecrypter,
7};
8
9pub mod key_manager;
10pub mod key_space;
11pub mod s2n_tls_intercept;
12pub mod stream_decrypter;
13pub mod tls_stream;
14pub mod transcript;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum Mode {
18    Client,
19    Server,
20}
21
22impl Mode {
23    pub fn peer(&self) -> Mode {
24        match self {
25            Mode::Client => Mode::Server,
26            Mode::Server => Mode::Client,
27        }
28    }
29}
30
31// basic test -> 1 message, 1 record,
32// harder test -> 2 messages, 1 record,
33// hardest test -> 1 message, 2 records,
34
35// how funky can the message framing get?
36// would this be allowed? I certainly hope not.
37// but it seems like a simple thing that would make maintainers lives easier, so
38// it probably is allowed
39// |         record          |        record     |
40// |  message   |    message        |  message   |
41
42/// This makes it easy to decrypt the traffic from a TLS implementation which operates
43/// over some generic type implementing Read + Write. E.g. `openssl::SslStream`
44pub struct DecryptingPipe<T> {
45    pub pipe: T,
46    // currently the mutex is a really ugly way for us to maintain access to this.
47    // need something better.
48    pub decrypter: StreamDecrypter,
49    pub identity: Option<Mode>,
50}
51
52impl DecryptingPipe<ArchaicCPipe> {
53    /// configure a decrypting pipe for an s2n-tls connection.
54    /// 
55    /// This will steal the currently configure send/recv callbacks and wrap them
56    /// in a pipe that the decrypter will use to populate the data
57    pub fn s2n_tls_decrypter(
58        key_manager: KeyManager,
59        connection: &mut s2n_tls::connection::Connection,
60    ) -> Box<Self> {
61        let original_send = connection.steal_send_cb();
62        let original_recv = connection.steal_recv_cb();
63        let original_pipe = ArchaicCPipe::new(original_send, original_recv);
64        let decrypter = Self::new(key_manager, original_pipe);
65        let decrypter = Box::new(decrypter);
66
67        connection
68            .set_send_callback(Some(generic_send_cb::<Self>))
69            .unwrap();
70        connection
71            .set_receive_callback(Some(generic_recv_cb::<Self>))
72            .unwrap();
73        unsafe {
74            connection
75                .set_send_context(decrypter.as_ref() as *const Self as *mut c_void)
76                .unwrap();
77            connection
78                .set_receive_context(decrypter.as_ref() as *const Self as *mut c_void)
79                .unwrap();
80        }
81        decrypter
82    }
83}
84
85impl<T: std::io::Read + std::io::Write> DecryptingPipe<T> {
86    pub fn new(key_manager: KeyManager, pipe: T) -> Self {
87        Self {
88            pipe,
89            decrypter: StreamDecrypter::new(key_manager),
90            identity: None,
91        }
92    }
93
94    /// Used to decrypt s2n-tls connections
95    ///
96    /// First the underlying "real" connection IO stuff needs to be retrieved and
97    /// packaged into an `ArchaicCPipe`. At that point this can be used to set the
98    /// appropriate callbacks on s2n-tls.
99    pub fn enable_s2n_tls_decryption(
100        decrypter: &Box<Self>,
101        connection: &mut s2n_tls::connection::Connection,
102    ) {
103        connection
104            .set_send_callback(Some(generic_send_cb::<Self>))
105            .unwrap();
106        connection
107            .set_receive_callback(Some(generic_recv_cb::<Self>))
108            .unwrap();
109        unsafe {
110            connection
111                .set_send_context(decrypter.as_ref() as *const Self as *mut c_void)
112                .unwrap();
113            connection
114                .set_receive_context(decrypter.as_ref() as *const Self as *mut c_void)
115                .unwrap();
116        }
117    }
118}
119
120// designed to work with an IO callback based pattern, such as that used by s2n-tls
121// and OpenSSL
122impl<T: std::io::Read> std::io::Read for DecryptingPipe<T> {
123    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
124        if self.identity.is_none() {
125            // reading first is server behavior
126            self.identity = Some(Mode::Server);
127        }
128
129        let read = self.pipe.read(buf)?;
130        tracing::trace!("DecryptingPipe read {read} bytes");
131
132        let peer = self.identity.unwrap().peer();
133
134        self.decrypter.record_tx(&buf[..read], peer);
135        self.decrypter.assemble_records(peer);
136        //self.decrypter.decrypt_records(peer).unwrap();
137        if let Err(e) = self.decrypter.decrypt_records(peer) {
138            if e.kind() != ErrorKind::UnexpectedEof {
139                panic!("unexpected error: {e}");
140            }
141        }
142
143        Ok(read)
144    }
145}
146
147impl<T: std::io::Write> std::io::Write for DecryptingPipe<T> {
148    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
149        if self.identity.is_none() {
150            // writing first is client behavior
151            self.identity = Some(Mode::Client);
152        }
153
154        let written = self.pipe.write(buf)?;
155        tracing::trace!("DecryptingPipe wrote {written} bytes");
156
157        let identity = self.identity.unwrap();
158
159        self.decrypter.record_tx(&buf[..written], identity);
160        self.decrypter.assemble_records(self.identity.unwrap());
161        if let Err(e) = self.decrypter.decrypt_records(self.identity.unwrap()) {
162            if e.kind() != ErrorKind::UnexpectedEof {
163                panic!("unexpected error: {e}");
164            }
165        }
166
167        Ok(written)
168    }
169
170    fn flush(&mut self) -> std::io::Result<()> {
171        /* no op */
172        Ok(())
173    }
174}