shadowsocks_crypto/v1/
cipher.rs

1use crate::kind::{CipherCategory, CipherKind};
2
3#[cfg(feature = "v1-aead")]
4use super::aeadcipher::AeadCipher;
5use super::dummy::DummyCipher;
6#[cfg(feature = "v1-stream")]
7use super::streamcipher::StreamCipher;
8
9#[deprecated(since = "0.5.8", note = "prefer utils::random_iv_or_salt")]
10pub use crate::utils::random_iv_or_salt;
11
12/// Key derivation of OpenSSL's [EVP_BytesToKey](https://wiki.openssl.org/index.php/Manual:EVP_BytesToKey(3))
13pub fn openssl_bytes_to_key(password: &[u8], key: &mut [u8]) {
14    use md5::{Digest, Md5};
15
16    let key_len = key.len();
17
18    let mut last_digest = None;
19
20    let mut offset = 0usize;
21    while offset < key_len {
22        let mut m = Md5::new();
23        if let Some(digest) = last_digest {
24            m.update(&digest);
25        }
26
27        m.update(password);
28
29        let digest = m.finalize();
30
31        let amt = std::cmp::min(key_len - offset, digest.len());
32        key[offset..offset + amt].copy_from_slice(&digest[..amt]);
33
34        offset += amt;
35        last_digest = Some(digest);
36    }
37}
38
39/// Unified interface of Ciphers
40#[allow(clippy::large_enum_variant)]
41pub enum Cipher {
42    Dummy(DummyCipher),
43    #[cfg(feature = "v1-stream")]
44    Stream(StreamCipher),
45    #[cfg(feature = "v1-aead")]
46    Aead(AeadCipher),
47}
48
49macro_rules! cipher_method_forward {
50    (ref $self:expr, $method:ident $(, $param:expr),*) => {
51        match *$self {
52            Cipher::Dummy(ref c) => c.$method($($param),*),
53            #[cfg(feature = "v1-stream")]
54            Cipher::Stream(ref c) => c.$method($($param),*),
55            #[cfg(feature = "v1-aead")]
56            Cipher::Aead(ref c) => c.$method($($param),*),
57        }
58    };
59
60    (mut $self:expr, $method:ident $(, $param:expr),*) => {
61        match *$self {
62            Cipher::Dummy(ref mut c) => c.$method($($param),*),
63            #[cfg(feature = "v1-stream")]
64            Cipher::Stream(ref mut c) => c.$method($($param),*),
65            #[cfg(feature = "v1-aead")]
66            Cipher::Aead(ref mut c) => c.$method($($param),*),
67        }
68    };
69}
70
71impl Cipher {
72    /// Create a new Cipher of `kind`
73    ///
74    /// - Stream Ciphers initialize with IV
75    /// - AEAD Ciphers initialize with SALT
76    pub fn new(kind: CipherKind, key: &[u8], iv_or_salt: &[u8]) -> Cipher {
77        let category = kind.category();
78
79        match category {
80            CipherCategory::None => {
81                let _ = key;
82                let _ = iv_or_salt;
83
84                Cipher::Dummy(DummyCipher::new())
85            }
86            #[cfg(feature = "v1-stream")]
87            CipherCategory::Stream => Cipher::Stream(StreamCipher::new(kind, key, iv_or_salt)),
88            #[cfg(feature = "v1-aead")]
89            CipherCategory::Aead => {
90                use cfg_if::cfg_if;
91
92                const SUBKEY_INFO: &'static [u8] = b"ss-subkey";
93                const MAX_KEY_LEN: usize = 64;
94
95                let ikm = key;
96                let mut okm = [0u8; MAX_KEY_LEN];
97
98                cfg_if! {
99                    if #[cfg(feature = "ring")] {
100                        use ring_compat::ring::hkdf::{Salt, HKDF_SHA1_FOR_LEGACY_USE_ONLY, KeyType};
101
102                        struct CryptoKeyType(usize);
103
104                        impl KeyType for CryptoKeyType {
105                            #[inline]
106                            fn len(&self) -> usize {
107                                self.0
108                            }
109                        }
110
111                        let salt = Salt::new(HKDF_SHA1_FOR_LEGACY_USE_ONLY, iv_or_salt);
112                        let prk = salt.extract(ikm);
113                        let rokm = prk
114                            .expand(&[SUBKEY_INFO], CryptoKeyType(ikm.len()))
115                            .expect("HKDF-SHA1-EXPAND");
116
117                        rokm.fill(&mut okm[..ikm.len()]).expect("HKDF-SHA1-FILL");
118                    } else {
119                        use hkdf::Hkdf;
120                        use sha1::Sha1;
121
122                        let hk = Hkdf::<Sha1>::new(Some(iv_or_salt), ikm);
123                        hk.expand(SUBKEY_INFO, &mut okm).expect("HKDF-SHA1");
124                    }
125                }
126
127                let subkey = &okm[..ikm.len()];
128                Cipher::Aead(AeadCipher::new(kind, subkey))
129            }
130            #[allow(unreachable_patterns)]
131            _ => unimplemented!("Category {:?} is not v1 protocol", category),
132        }
133    }
134
135    /// Get the `CipherCategory` of the current cipher
136    pub fn category(&self) -> CipherCategory {
137        cipher_method_forward!(ref self, category)
138    }
139
140    /// Get the `CipherKind` of the current cipher
141    pub fn kind(&self) -> CipherKind {
142        cipher_method_forward!(ref self, kind)
143    }
144
145    /// Get the TAG length of AEAD ciphers
146    pub fn tag_len(&self) -> usize {
147        cipher_method_forward!(ref self, tag_len)
148    }
149
150    /// Encrypt a packet. Encrypted result will be written in `pkt`
151    ///
152    /// - Stream Ciphers: the size of input and output packets are the same
153    /// - AEAD Ciphers: the size of output must be at least `input.len() + TAG_LEN`
154    pub fn encrypt_packet(&mut self, pkt: &mut [u8]) {
155        cipher_method_forward!(mut self, encrypt, pkt)
156    }
157
158    /// Decrypt a packet. Decrypted result will be written in `pkt`
159    ///
160    /// - Stream Ciphers: the size of input and output packets are the same
161    /// - AEAD Ciphers: the size of output is `input.len() - TAG_LEN`
162    #[must_use]
163    pub fn decrypt_packet(&mut self, pkt: &mut [u8]) -> bool {
164        cipher_method_forward!(mut self, decrypt, pkt)
165    }
166}
167
168#[test]
169fn test_cipher_new_none() {
170    let key = [2u8; 16];
171    let salt = [1u8; 16];
172    let kind = CipherKind::NONE;
173
174    let cipher = Cipher::new(kind, &key, &salt);
175    assert_eq!(cipher.tag_len(), 0);
176}
177
178#[cfg(feature = "v1-aead")]
179#[test]
180fn test_cipher_new_aead() {
181    let key = [2u8; 16];
182    let salt = [1u8; 16];
183    let kind = CipherKind::AES_128_GCM;
184
185    let cipher = Cipher::new(kind, &key, &salt);
186    assert_eq!(cipher.tag_len(), 16);
187}
188
189#[cfg(feature = "v1-stream")]
190#[test]
191fn test_cipher_new_stream() {
192    let key = [2u8; 32];
193    let iv = [1u8; 12];
194    let kind = CipherKind::CHACHA20;
195
196    let cipher = Cipher::new(kind, &key, &iv);
197    assert_eq!(cipher.tag_len(), 0);
198}
199
200#[test]
201fn test_send() {
202    fn test<C: Send>() {}
203    test::<Cipher>();
204}
205
206#[test]
207fn test_sync() {
208    fn test<C: Sync>() {}
209    test::<Cipher>();
210}