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::derive_poly1305_key, |
35 | cpu, poly1305, Nonce, Tag, |
36 | }; |
37 | use crate::{constant_time, error}; |
38 | |
39 | /// A key for sealing packets. |
40 | pub struct SealingKey { |
41 | key: Key, |
42 | } |
43 | |
44 | impl SealingKey { |
45 | /// Constructs a new `SealingKey`. |
46 | pub fn new(key_material: &[u8; KEY_LEN]) -> Self { |
47 | Self { |
48 | key: Key::new(key_material), |
49 | } |
50 | } |
51 | |
52 | /// Seals (encrypts and signs) a packet. |
53 | /// |
54 | /// On input, `plaintext_in_ciphertext_out` must contain the unencrypted |
55 | /// `packet_length||plaintext` where `plaintext` is the |
56 | /// `padding_length||payload||random padding`. It will be overwritten by |
57 | /// `encrypted_packet_length||ciphertext`, where `encrypted_packet_length` |
58 | /// is encrypted with `K_1` and `ciphertext` is encrypted by `K_2`. |
59 | pub fn seal_in_place( |
60 | &self, |
61 | sequence_number: u32, |
62 | plaintext_in_ciphertext_out: &mut [u8], |
63 | tag_out: &mut [u8; TAG_LEN], |
64 | ) { |
65 | let cpu_features = cpu::features(); |
66 | let mut counter = make_counter(sequence_number); |
67 | let poly_key = derive_poly1305_key(&self.key.k_2, counter.increment()); |
68 | |
69 | { |
70 | let (len_in_out, data_and_padding_in_out) = |
71 | plaintext_in_ciphertext_out.split_at_mut(PACKET_LENGTH_LEN); |
72 | |
73 | self.key |
74 | .k_1 |
75 | .encrypt_in_place(make_counter(sequence_number), len_in_out); |
76 | self.key |
77 | .k_2 |
78 | .encrypt_in_place(counter, data_and_padding_in_out); |
79 | } |
80 | |
81 | let Tag(tag) = poly1305::sign(poly_key, plaintext_in_ciphertext_out, cpu_features); |
82 | tag_out.copy_from_slice(tag.as_ref()); |
83 | } |
84 | } |
85 | |
86 | /// A key for opening packets. |
87 | pub struct OpeningKey { |
88 | key: Key, |
89 | } |
90 | |
91 | impl OpeningKey { |
92 | /// Constructs a new `OpeningKey`. |
93 | pub fn new(key_material: &[u8; KEY_LEN]) -> Self { |
94 | Self { |
95 | key: Key::new(key_material), |
96 | } |
97 | } |
98 | |
99 | /// Returns the decrypted, but unauthenticated, packet length. |
100 | /// |
101 | /// Importantly, the result won't be authenticated until `open_in_place` is |
102 | /// called. |
103 | pub fn decrypt_packet_length( |
104 | &self, |
105 | sequence_number: u32, |
106 | encrypted_packet_length: [u8; PACKET_LENGTH_LEN], |
107 | ) -> [u8; PACKET_LENGTH_LEN] { |
108 | let mut packet_length = encrypted_packet_length; |
109 | let counter = make_counter(sequence_number); |
110 | self.key.k_1.encrypt_in_place(counter, &mut packet_length); |
111 | packet_length |
112 | } |
113 | |
114 | /// Opens (authenticates and decrypts) a packet. |
115 | /// |
116 | /// `ciphertext_in_plaintext_out` must be of the form |
117 | /// `encrypted_packet_length||ciphertext` where `ciphertext` is the |
118 | /// encrypted `plaintext`. When the function succeeds the ciphertext is |
119 | /// replaced by the plaintext and the result is `Ok(plaintext)`, where |
120 | /// `plaintext` is `&ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]`; |
121 | /// otherwise the contents of `ciphertext_in_plaintext_out` are unspecified |
122 | /// and must not be used. |
123 | pub fn open_in_place<'a>( |
124 | &self, |
125 | sequence_number: u32, |
126 | ciphertext_in_plaintext_out: &'a mut [u8], |
127 | tag: &[u8; TAG_LEN], |
128 | ) -> Result<&'a [u8], error::Unspecified> { |
129 | let mut counter = make_counter(sequence_number); |
130 | |
131 | // We must verify the tag before decrypting so that |
132 | // `ciphertext_in_plaintext_out` is unmodified if verification fails. |
133 | // This is beyond what we guarantee. |
134 | let poly_key = derive_poly1305_key(&self.key.k_2, counter.increment()); |
135 | verify(poly_key, ciphertext_in_plaintext_out, tag)?; |
136 | |
137 | let plaintext_in_ciphertext_out = &mut ciphertext_in_plaintext_out[PACKET_LENGTH_LEN..]; |
138 | self.key |
139 | .k_2 |
140 | .encrypt_in_place(counter, plaintext_in_ciphertext_out); |
141 | |
142 | Ok(plaintext_in_ciphertext_out) |
143 | } |
144 | } |
145 | |
146 | struct Key { |
147 | k_1: chacha::Key, |
148 | k_2: chacha::Key, |
149 | } |
150 | |
151 | impl Key { |
152 | fn new(key_material: &[u8; KEY_LEN]) -> Self { |
153 | // The first half becomes K_2 and the second half becomes K_1. |
154 | let (k_2: &[u8], k_1: &[u8]) = key_material.split_at(mid:chacha::KEY_LEN); |
155 | Self { |
156 | k_1: chacha::Key::new(k_1.try_into().unwrap()), |
157 | k_2: chacha::Key::new(k_2.try_into().unwrap()), |
158 | } |
159 | } |
160 | } |
161 | |
162 | fn make_counter(sequence_number: u32) -> Counter { |
163 | let [s0: u8, s1: u8, s2: u8, s3: u8] = sequence_number.to_be_bytes(); |
164 | let nonce: [u8; 12] = [0, 0, 0, 0, 0, 0, 0, 0, s0, s1, s2, s3]; |
165 | Counter::zero(Nonce::assume_unique_for_key(nonce)) |
166 | } |
167 | |
168 | /// The length of key. |
169 | pub const KEY_LEN: usize = chacha::KEY_LEN * 2; |
170 | |
171 | /// The length in bytes of the `packet_length` field in a SSH packet. |
172 | pub const PACKET_LENGTH_LEN: usize = 4; // 32 bits |
173 | |
174 | /// The length in bytes of an authentication tag. |
175 | pub const TAG_LEN: usize = super::TAG_LEN; |
176 | |
177 | fn verify(key: poly1305::Key, msg: &[u8], tag: &[u8; TAG_LEN]) -> Result<(), error::Unspecified> { |
178 | let Tag(calculated_tag: [u8; 16]) = poly1305::sign(key, input:msg, cpu_features:cpu::features()); |
179 | constant_time::verify_slices_are_equal(a:calculated_tag.as_ref(), b:tag) |
180 | } |
181 | |