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