1 | // Copyright 2016 Brian Smith. |
2 | // |
3 | // Permission to use, copy, modify, and/or distribute this software for any |
4 | // purpose with or without fee is hereby granted, provided that the above |
5 | // copyright notice and this permission notice appear in all copies. |
6 | // |
7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY |
10 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION |
12 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
13 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
14 | |
15 | //! The [chacha20-poly1305@openssh.com] AEAD-ish construct. |
16 | //! |
17 | //! This should only be used by SSH implementations. It has a similar, but |
18 | //! different API from `ring::aead` because the construct cannot use the same |
19 | //! API as `ring::aead` due to the way the construct handles the encrypted |
20 | //! packet length. |
21 | //! |
22 | //! The concatenation of a and b is denoted `a||b`. `K_1` and `K_2` are defined |
23 | //! in the [chacha20-poly1305@openssh.com] specification. `packet_length`, |
24 | //! `padding_length`, `payload`, and `random padding` are defined in |
25 | //! [RFC 4253]. The term `plaintext` is used as a shorthand for |
26 | //! `padding_length||payload||random padding`. |
27 | //! |
28 | //! [chacha20-poly1305@openssh.com]: |
29 | //! http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD |
30 | //! [RFC 4253]: https://tools.ietf.org/html/rfc4253 |
31 | |
32 | use super::{ |
33 | chacha::{self, *}, |
34 | chacha20_poly1305, cpu, poly1305, Aad, Nonce, Tag, |
35 | }; |
36 | use crate::{ |
37 | constant_time, |
38 | error::{self, InputTooLongError}, |
39 | polyfill::slice, |
40 | }; |
41 | |
42 | /// A key for sealing packets. |
43 | pub struct SealingKey { |
44 | key: Key, |
45 | } |
46 | |
47 | impl SealingKey { |
48 | /// Constructs a new `SealingKey`. |
49 | pub fn new(key_material: &[u8; KEY_LEN]) -> Self { |
50 | Self { |
51 | key: Key::new(key_material), |
52 | } |
53 | } |
54 | |
55 | /// Seals (encrypts and signs) a packet. |
56 | /// |
57 | /// On input, `plaintext_in_ciphertext_out` must contain the unencrypted |
58 | /// `packet_length||plaintext` where `plaintext` is the |
59 | /// `padding_length||payload||random padding`. It will be overwritten by |
60 | /// `encrypted_packet_length||ciphertext`, where `encrypted_packet_length` |
61 | /// is encrypted with `K_1` and `ciphertext` is encrypted by `K_2`. |
62 | /// |
63 | /// # Panics |
64 | /// |
65 | /// Panics if `plaintext_in_ciphertext_out.len() < PACKET_LENGTH_LEN`. |
66 | /// |
67 | /// Panics if `plaintext_in_ciphertext_out` is longer than the maximum |
68 | /// input size for ChaCha20-Poly1305. Note that this limit is much, |
69 | /// much larger than SSH's 256KB maximum record size. |
70 | pub fn seal_in_place( |
71 | &self, |
72 | sequence_number: u32, |
73 | plaintext_in_ciphertext_out: &mut [u8], |
74 | tag_out: &mut [u8; TAG_LEN], |
75 | ) { |
76 | // XXX/TODO(SemVer): Refactor API to return an error. |
77 | let (len_in_out, data_and_padding_in_out): (&mut [u8; PACKET_LENGTH_LEN], _) = |
78 | slice::split_first_chunk_mut(plaintext_in_ciphertext_out).unwrap(); |
79 | |
80 | let cpu = cpu::features(); |
81 | // XXX/TODO(SemVer): Refactor API to return an error. |
82 | let (counter, poly_key) = chacha20_poly1305::begin( |
83 | &self.key.k_2, |
84 | make_nonce(sequence_number), |
85 | Aad::from(len_in_out), |
86 | data_and_padding_in_out, |
87 | cpu, |
88 | ) |
89 | .map_err(error::erase::<InputTooLongError>) |
90 | .unwrap(); |
91 | |
92 | let _: Counter = self.key.k_1.encrypt_single_block_with_ctr_0( |
93 | make_nonce(sequence_number), |
94 | len_in_out, |
95 | cpu, |
96 | ); |
97 | self.key |
98 | .k_2 |
99 | .encrypt(counter, data_and_padding_in_out.into(), cpu); |
100 | |
101 | let Tag(tag) = poly1305::sign(poly_key, plaintext_in_ciphertext_out, cpu); |
102 | *tag_out = tag; |
103 | } |
104 | } |
105 | |
106 | /// A key for opening packets. |
107 | pub struct OpeningKey { |
108 | key: Key, |
109 | } |
110 | |
111 | impl OpeningKey { |
112 | /// Constructs a new `OpeningKey`. |
113 | pub fn new(key_material: &[u8; KEY_LEN]) -> Self { |
114 | Self { |
115 | key: Key::new(key_material), |
116 | } |
117 | } |
118 | |
119 | /// Returns the decrypted, but unauthenticated, packet length. |
120 | /// |
121 | /// Importantly, the result won't be authenticated until `open_in_place` is |
122 | /// called. |
123 | pub fn decrypt_packet_length( |
124 | &self, |
125 | sequence_number: u32, |
126 | encrypted_packet_length: [u8; PACKET_LENGTH_LEN], |
127 | ) -> [u8; PACKET_LENGTH_LEN] { |
128 | let cpu = cpu::features(); |
129 | let mut packet_length = encrypted_packet_length; |
130 | let _: Counter = self.key.k_1.encrypt_single_block_with_ctr_0( |
131 | make_nonce(sequence_number), |
132 | &mut packet_length, |
133 | cpu, |
134 | ); |
135 | packet_length |
136 | } |
137 | |
138 | /// Opens (authenticates and decrypts) a packet. |
139 | /// |
140 | /// `ciphertext_in_plaintext_out` must be of the form |
141 | /// `encrypted_packet_length||ciphertext` where `ciphertext` is the |
142 | /// encrypted `plaintext`. When the function succeeds the ciphertext is |
143 | /// replaced by the plaintext and the result is `Ok(plaintext)`, where |
144 | /// `plaintext` is `&ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]`; |
145 | /// otherwise the contents of `ciphertext_in_plaintext_out` are unspecified |
146 | /// and must not be used. |
147 | pub fn open_in_place<'a>( |
148 | &self, |
149 | sequence_number: u32, |
150 | ciphertext_in_plaintext_out: &'a mut [u8], |
151 | tag: &[u8; TAG_LEN], |
152 | ) -> Result<&'a [u8], error::Unspecified> { |
153 | let (packet_length, after_packet_length): (&mut [u8; PACKET_LENGTH_LEN], _) = |
154 | slice::split_first_chunk_mut(ciphertext_in_plaintext_out).ok_or(error::Unspecified)?; |
155 | |
156 | let cpu = cpu::features(); |
157 | let (counter, poly_key) = chacha20_poly1305::begin( |
158 | &self.key.k_2, |
159 | make_nonce(sequence_number), |
160 | Aad::from(packet_length), |
161 | after_packet_length, |
162 | cpu, |
163 | ) |
164 | .map_err(error::erase::<InputTooLongError>)?; |
165 | |
166 | // We must verify the tag before decrypting so that |
167 | // `ciphertext_in_plaintext_out` is unmodified if verification fails. |
168 | // This is beyond what we guarantee. |
169 | let calculated_tag = poly1305::sign(poly_key, ciphertext_in_plaintext_out, cpu); |
170 | constant_time::verify_slices_are_equal(calculated_tag.as_ref(), tag)?; |
171 | |
172 | // Won't panic because the length was checked above. |
173 | let after_packet_length = &mut ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]; |
174 | |
175 | self.key |
176 | .k_2 |
177 | .encrypt(counter, after_packet_length.into(), cpu); |
178 | |
179 | Ok(after_packet_length) |
180 | } |
181 | } |
182 | |
183 | struct Key { |
184 | k_1: chacha::Key, |
185 | k_2: chacha::Key, |
186 | } |
187 | |
188 | impl Key { |
189 | fn new(key_material: &[u8; KEY_LEN]) -> Self { |
190 | // The first half becomes K_2 and the second half becomes K_1. |
191 | let (k_2: &[u8], k_1: &[u8]) = key_material.split_at(mid:chacha::KEY_LEN); |
192 | Self { |
193 | k_1: chacha::Key::new(k_1.try_into().unwrap()), |
194 | k_2: chacha::Key::new(k_2.try_into().unwrap()), |
195 | } |
196 | } |
197 | } |
198 | |
199 | fn make_nonce(sequence_number: u32) -> Nonce { |
200 | let [s0: u8, s1: u8, s2: u8, s3: u8] = sequence_number.to_be_bytes(); |
201 | let nonce: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0, s0, s1, s2, s3]; |
202 | Nonce::assume_unique_for_key(nonce) |
203 | } |
204 | |
205 | /// The length of key. |
206 | pub const KEY_LEN: usize = chacha::KEY_LEN * 2; |
207 | |
208 | /// The length in bytes of the `packet_length` field in a SSH packet. |
209 | pub const PACKET_LENGTH_LEN: usize = 4; // 32 bits |
210 | |
211 | /// The length in bytes of an authentication tag. |
212 | pub const TAG_LEN: usize = super::TAG_LEN; |
213 | |