1 // Copyright 2016 Pierre-Étienne Meunier
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 //
15 
16 // http://cvsweb.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD
17 
18 use super::super::Error;
19 use byteorder::{BigEndian, ByteOrder};
20 use sodium::chacha20::*;
21 
22 pub struct OpeningKey {
23     k1: Key,
24     k2: Key,
25 }
26 pub struct SealingKey {
27     k1: Key,
28     k2: Key,
29 }
30 
31 const TAG_LEN: usize = 16;
32 
33 pub static CIPHER: super::Cipher = super::Cipher {
34     name: NAME,
35     key_len: 64,
36     make_sealing_cipher,
37     make_opening_cipher,
38 };
39 
40 pub const NAME: super::Name = super::Name("chacha20-poly1305@openssh.com");
41 
make_sealing_cipher(k: &[u8]) -> super::SealingCipher42 fn make_sealing_cipher(k: &[u8]) -> super::SealingCipher {
43     let mut k1 = Key([0; KEY_BYTES]);
44     let mut k2 = Key([0; KEY_BYTES]);
45     k1.0.clone_from_slice(&k[KEY_BYTES..]);
46     k2.0.clone_from_slice(&k[..KEY_BYTES]);
47     super::SealingCipher::Chacha20Poly1305(SealingKey { k1, k2 })
48 }
49 
make_opening_cipher(k: &[u8]) -> super::OpeningCipher50 fn make_opening_cipher(k: &[u8]) -> super::OpeningCipher {
51     let mut k1 = Key([0; KEY_BYTES]);
52     let mut k2 = Key([0; KEY_BYTES]);
53     k1.0.clone_from_slice(&k[KEY_BYTES..]);
54     k2.0.clone_from_slice(&k[..KEY_BYTES]);
55     super::OpeningCipher::Chacha20Poly1305(OpeningKey { k1, k2 })
56 }
57 
make_counter(sequence_number: u32) -> Nonce58 fn make_counter(sequence_number: u32) -> Nonce {
59     let mut nonce = Nonce([0; NONCE_BYTES]);
60     let i0 = NONCE_BYTES - 4;
61     BigEndian::write_u32(&mut nonce.0[i0..], sequence_number);
62     nonce
63 }
64 
65 impl super::OpeningKey for OpeningKey {
decrypt_packet_length( &self, sequence_number: u32, mut encrypted_packet_length: [u8; 4], ) -> [u8; 4]66     fn decrypt_packet_length(
67         &self,
68         sequence_number: u32,
69         mut encrypted_packet_length: [u8; 4],
70     ) -> [u8; 4] {
71         let nonce = make_counter(sequence_number);
72         chacha20_xor(&mut encrypted_packet_length, &nonce, &self.k1);
73         encrypted_packet_length
74     }
75 
tag_len(&self) -> usize76     fn tag_len(&self) -> usize {
77         TAG_LEN
78     }
79 
open<'a>( &self, sequence_number: u32, ciphertext_in_plaintext_out: &'a mut [u8], tag: &[u8], ) -> Result<&'a [u8], Error>80     fn open<'a>(
81         &self,
82         sequence_number: u32,
83         ciphertext_in_plaintext_out: &'a mut [u8],
84         tag: &[u8],
85     ) -> Result<&'a [u8], Error> {
86         let nonce = make_counter(sequence_number);
87         {
88             use sodium::poly1305::*;
89             let mut poly_key = Key([0; 32]);
90             chacha20_xor(&mut poly_key.0, &nonce, &self.k2);
91             // let mut tag_ = Tag([0; 16]);
92             // tag_.0.clone_from_slice(tag);
93             if !poly1305_verify(&tag, ciphertext_in_plaintext_out, &poly_key) {
94                 return Err(Error::PacketAuth);
95             }
96         }
97         chacha20_xor_ic(&mut ciphertext_in_plaintext_out[4..], &nonce, 1, &self.k2);
98         Ok(&ciphertext_in_plaintext_out[4..])
99     }
100 }
101 
102 impl super::SealingKey for SealingKey {
padding_length(&self, payload: &[u8]) -> usize103     fn padding_length(&self, payload: &[u8]) -> usize {
104         let block_size = 8;
105         let extra_len = super::PACKET_LENGTH_LEN + super::PADDING_LENGTH_LEN;
106         let padding_len = if payload.len() + extra_len <= super::MINIMUM_PACKET_LEN {
107             super::MINIMUM_PACKET_LEN - payload.len() - super::PADDING_LENGTH_LEN
108         } else {
109             block_size - ((super::PADDING_LENGTH_LEN + payload.len()) % block_size)
110         };
111         if padding_len < super::PACKET_LENGTH_LEN {
112             padding_len + block_size
113         } else {
114             padding_len
115         }
116     }
117 
118     // As explained in "SSH via CTR mode with stateful decryption" in
119     // https://openvpn.net/papers/ssh-security.pdf, the padding doesn't need to
120     // be random because we're doing stateful counter-mode encryption. Use
121     // fixed padding to avoid PRNG overhead.
fill_padding(&self, padding_out: &mut [u8])122     fn fill_padding(&self, padding_out: &mut [u8]) {
123         for padding_byte in padding_out {
124             *padding_byte = 0;
125         }
126     }
127 
tag_len(&self) -> usize128     fn tag_len(&self) -> usize {
129         TAG_LEN
130     }
131 
132     /// Append an encrypted packet with contents `packet_content` at the end of `buffer`.
seal( &self, sequence_number: u32, plaintext_in_ciphertext_out: &mut [u8], tag_out: &mut [u8], )133     fn seal(
134         &self,
135         sequence_number: u32,
136         plaintext_in_ciphertext_out: &mut [u8],
137         tag_out: &mut [u8],
138     ) {
139         let mut nonce = make_counter(sequence_number);
140         {
141             let (a, b) = plaintext_in_ciphertext_out.split_at_mut(4);
142             chacha20_xor(a, &nonce, &self.k1);
143             chacha20_xor_ic(b, &nonce, 1, &self.k2);
144         }
145         nonce.0[0] = 0;
146         use sodium::poly1305::*;
147         let mut poly_key = Key([0; 32]);
148         chacha20_xor(&mut poly_key.0, &nonce, &self.k2);
149         let tag = poly1305_auth(plaintext_in_ciphertext_out, &poly_key);
150         tag_out.clone_from_slice(&tag.0);
151     }
152 }
153