| 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 | |