mirror of
https://github.com/zerotier/ZeroTierOne.git
synced 2025-07-22 10:22:50 +02:00
472 lines
15 KiB
Rust
472 lines
15 KiB
Rust
// (c) 2020-2022 ZeroTier, Inc. -- currently propritery pending actual release and licensing. See LICENSE.md.
|
|
|
|
// AES-GMAC-SIV implemented using MacOS/iOS CommonCrypto (MacOS 10.13 or newer required).
|
|
|
|
use std::os::raw::{c_int, c_void};
|
|
use std::ptr::{null, null_mut};
|
|
|
|
#[allow(non_upper_case_globals)]
|
|
const kCCModeECB: i32 = 1;
|
|
#[allow(non_upper_case_globals)]
|
|
const kCCModeCTR: i32 = 4;
|
|
#[allow(non_upper_case_globals)]
|
|
const kCCModeGCM: i32 = 11;
|
|
#[allow(non_upper_case_globals)]
|
|
const kCCEncrypt: i32 = 0;
|
|
#[allow(non_upper_case_globals)]
|
|
const kCCDecrypt: i32 = 1;
|
|
#[allow(non_upper_case_globals)]
|
|
const kCCAlgorithmAES: i32 = 0;
|
|
#[allow(non_upper_case_globals)]
|
|
const kCCOptionECBMode: i32 = 2;
|
|
|
|
extern "C" {
|
|
fn CCCryptorCreateWithMode(
|
|
op: i32,
|
|
mode: i32,
|
|
alg: i32,
|
|
padding: i32,
|
|
iv: *const c_void,
|
|
key: *const c_void,
|
|
key_len: usize,
|
|
tweak: *const c_void,
|
|
tweak_len: usize,
|
|
num_rounds: c_int,
|
|
options: i32,
|
|
cryyptor_ref: *mut *mut c_void,
|
|
) -> i32;
|
|
fn CCCryptorUpdate(
|
|
cryptor_ref: *mut c_void,
|
|
data_in: *const c_void,
|
|
data_in_len: usize,
|
|
data_out: *mut c_void,
|
|
data_out_len: usize,
|
|
data_out_written: *mut usize,
|
|
) -> i32;
|
|
fn CCCryptorReset(cryptor_ref: *mut c_void, iv: *const c_void) -> i32;
|
|
fn CCCryptorRelease(cryptor_ref: *mut c_void) -> i32;
|
|
fn CCCryptorGCMSetIV(cryptor_ref: *mut c_void, iv: *const c_void, iv_len: usize) -> i32;
|
|
fn CCCryptorGCMAddAAD(cryptor_ref: *mut c_void, aad: *const c_void, len: usize) -> i32;
|
|
fn CCCryptorGCMFinalize(cryptor_ref: *mut c_void, tag: *mut c_void, tag_len: usize) -> i32;
|
|
fn CCCryptorGCMReset(cryptor_ref: *mut c_void) -> i32;
|
|
}
|
|
|
|
pub struct AesCtr(*mut c_void);
|
|
|
|
impl Drop for AesCtr {
|
|
fn drop(&mut self) {
|
|
if !self.0.is_null() {
|
|
unsafe {
|
|
CCCryptorRelease(self.0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AesCtr {
|
|
/// Construct a new AES-CTR cipher.
|
|
/// Key must be 16, 24, or 32 bytes in length or a panic will occur.
|
|
pub fn new(k: &[u8]) -> Self {
|
|
if k.len() != 32 && k.len() != 24 && k.len() != 16 {
|
|
panic!("AES supports 128, 192, or 256 bits keys");
|
|
}
|
|
unsafe {
|
|
let mut ptr: *mut c_void = null_mut();
|
|
let result = CCCryptorCreateWithMode(
|
|
kCCEncrypt,
|
|
kCCModeCTR,
|
|
kCCAlgorithmAES,
|
|
0,
|
|
crate::ZEROES.as_ptr().cast(),
|
|
k.as_ptr().cast(),
|
|
k.len(),
|
|
null(),
|
|
0,
|
|
0,
|
|
0,
|
|
&mut ptr,
|
|
);
|
|
if result != 0 {
|
|
panic!("CCCryptorCreateWithMode for CTR mode returned {}", result);
|
|
}
|
|
AesCtr(ptr)
|
|
}
|
|
}
|
|
|
|
/// Initialize AES-CTR for encryption or decryption with the given IV.
|
|
/// If it's already been used, this also resets the cipher. There is no separate reset.
|
|
pub fn init(&mut self, iv: &[u8]) {
|
|
unsafe {
|
|
if iv.len() == 16 {
|
|
if CCCryptorReset(self.0, iv.as_ptr().cast()) != 0 {
|
|
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
|
}
|
|
} else if iv.len() < 16 {
|
|
let mut iv2 = [0_u8; 16];
|
|
iv2[0..iv.len()].copy_from_slice(iv);
|
|
if CCCryptorReset(self.0, iv2.as_ptr().cast()) != 0 {
|
|
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
|
}
|
|
} else {
|
|
panic!("CTR IV must be less than or equal to 16 bytes in length");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Encrypt or decrypt (same operation with CTR mode)
|
|
#[inline(always)]
|
|
pub fn crypt(&mut self, input: &[u8], output: &mut [u8]) {
|
|
unsafe {
|
|
assert!(output.len() >= input.len());
|
|
let mut data_out_written: usize = 0;
|
|
CCCryptorUpdate(
|
|
self.0,
|
|
input.as_ptr().cast(),
|
|
input.len(),
|
|
output.as_mut_ptr().cast(),
|
|
output.len(),
|
|
&mut data_out_written,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Encrypt or decrypt in place (same operation with CTR mode)
|
|
#[inline(always)]
|
|
pub fn crypt_in_place(&mut self, data: &mut [u8]) {
|
|
unsafe {
|
|
let mut data_out_written: usize = 0;
|
|
CCCryptorUpdate(
|
|
self.0,
|
|
data.as_ptr().cast(),
|
|
data.len(),
|
|
data.as_mut_ptr().cast(),
|
|
data.len(),
|
|
&mut data_out_written,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for AesCtr {}
|
|
|
|
#[repr(align(8))]
|
|
pub struct AesGmacSiv {
|
|
tag: [u8; 16],
|
|
tmp: [u8; 16],
|
|
ctr: *mut c_void,
|
|
ecb_enc: *mut c_void,
|
|
ecb_dec: *mut c_void,
|
|
gmac: *mut c_void,
|
|
}
|
|
|
|
impl Drop for AesGmacSiv {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
if !self.ctr.is_null() {
|
|
CCCryptorRelease(self.ctr);
|
|
}
|
|
if !self.ecb_enc.is_null() {
|
|
CCCryptorRelease(self.ecb_enc);
|
|
}
|
|
if !self.ecb_dec.is_null() {
|
|
CCCryptorRelease(self.ecb_dec);
|
|
}
|
|
if !self.gmac.is_null() {
|
|
CCCryptorRelease(self.gmac);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl AesGmacSiv {
|
|
/// Create a new keyed instance of AES-GMAC-SIV
|
|
/// The key may be of size 16, 24, or 32 bytes (128, 192, or 256 bits). Any other size will panic.
|
|
/// Two keys are required: one for GMAC and one for AES-CTR.
|
|
pub fn new(k0: &[u8], k1: &[u8]) -> Self {
|
|
if k0.len() != 32 && k0.len() != 24 && k0.len() != 16 {
|
|
panic!("AES supports 128, 192, or 256 bits keys");
|
|
}
|
|
if k1.len() != k0.len() {
|
|
panic!("k0 and k1 must be of the same size");
|
|
}
|
|
let mut c: AesGmacSiv = AesGmacSiv {
|
|
tag: [0_u8; 16],
|
|
tmp: [0_u8; 16],
|
|
ctr: null_mut(),
|
|
ecb_enc: null_mut(),
|
|
ecb_dec: null_mut(),
|
|
gmac: null_mut(),
|
|
};
|
|
unsafe {
|
|
let result = CCCryptorCreateWithMode(
|
|
kCCEncrypt,
|
|
kCCModeCTR,
|
|
kCCAlgorithmAES,
|
|
0,
|
|
crate::ZEROES.as_ptr().cast(),
|
|
k1.as_ptr().cast(),
|
|
k1.len(),
|
|
null(),
|
|
0,
|
|
0,
|
|
0,
|
|
&mut c.ctr,
|
|
);
|
|
if result != 0 {
|
|
panic!("CCCryptorCreateWithMode for CTR mode returned {}", result);
|
|
}
|
|
let result = CCCryptorCreateWithMode(
|
|
kCCEncrypt,
|
|
kCCModeECB,
|
|
kCCAlgorithmAES,
|
|
0,
|
|
crate::ZEROES.as_ptr().cast(),
|
|
k1.as_ptr().cast(),
|
|
k1.len(),
|
|
null(),
|
|
0,
|
|
0,
|
|
kCCOptionECBMode,
|
|
&mut c.ecb_enc,
|
|
);
|
|
if result != 0 {
|
|
panic!("CCCryptorCreateWithMode for ECB encrypt mode returned {}", result);
|
|
}
|
|
let result = CCCryptorCreateWithMode(
|
|
kCCDecrypt,
|
|
kCCModeECB,
|
|
kCCAlgorithmAES,
|
|
0,
|
|
crate::ZEROES.as_ptr().cast(),
|
|
k1.as_ptr().cast(),
|
|
k1.len(),
|
|
null(),
|
|
0,
|
|
0,
|
|
kCCOptionECBMode,
|
|
&mut c.ecb_dec,
|
|
);
|
|
if result != 0 {
|
|
panic!("CCCryptorCreateWithMode for ECB decrypt mode returned {}", result);
|
|
}
|
|
let result = CCCryptorCreateWithMode(
|
|
kCCEncrypt,
|
|
kCCModeGCM,
|
|
kCCAlgorithmAES,
|
|
0,
|
|
crate::ZEROES.as_ptr().cast(),
|
|
k0.as_ptr().cast(),
|
|
k0.len(),
|
|
null(),
|
|
0,
|
|
0,
|
|
0,
|
|
&mut c.gmac,
|
|
);
|
|
if result != 0 {
|
|
panic!("CCCryptorCreateWithMode for GCM (GMAC) mode returned {}", result);
|
|
}
|
|
}
|
|
c
|
|
}
|
|
|
|
/// Reset to prepare for another encrypt or decrypt operation.
|
|
#[inline(always)]
|
|
pub fn reset(&mut self) {
|
|
unsafe {
|
|
CCCryptorGCMReset(self.gmac);
|
|
}
|
|
}
|
|
|
|
/// Initialize for encryption.
|
|
#[inline(always)]
|
|
pub fn encrypt_init(&mut self, iv: &[u8]) {
|
|
self.tag[0..8].copy_from_slice(iv);
|
|
self.tag[8..12].fill(0);
|
|
unsafe {
|
|
CCCryptorGCMSetIV(self.gmac, self.tag.as_ptr().cast(), 12);
|
|
}
|
|
}
|
|
|
|
/// Set additional authenticated data (data to be authenticated but not encrypted).
|
|
/// This can currently only be called once. Multiple calls will result in corrupt data.
|
|
#[inline(always)]
|
|
pub fn encrypt_set_aad(&mut self, data: &[u8]) {
|
|
unsafe {
|
|
CCCryptorGCMAddAAD(self.gmac, data.as_ptr().cast(), data.len());
|
|
}
|
|
let pad = data.len() & 0xf;
|
|
if pad != 0 {
|
|
unsafe {
|
|
CCCryptorGCMAddAAD(self.gmac, crate::ZEROES.as_ptr().cast(), 16 - pad);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Feed plaintext in for the first encryption pass.
|
|
/// This may be called more than once.
|
|
#[inline(always)]
|
|
pub fn encrypt_first_pass(&mut self, plaintext: &[u8]) {
|
|
unsafe {
|
|
CCCryptorGCMAddAAD(self.gmac, plaintext.as_ptr().cast(), plaintext.len());
|
|
}
|
|
}
|
|
|
|
/// Finish first pass and begin second pass.
|
|
#[inline(always)]
|
|
pub fn encrypt_first_pass_finish(&mut self) {
|
|
unsafe {
|
|
CCCryptorGCMFinalize(self.gmac, self.tmp.as_mut_ptr().cast(), 16);
|
|
let tmp = self.tmp.as_mut_ptr().cast::<u64>();
|
|
*self.tag.as_mut_ptr().cast::<u64>().offset(1) = *tmp ^ *tmp.offset(1);
|
|
let mut data_out_written: usize = 0;
|
|
CCCryptorUpdate(
|
|
self.ecb_enc,
|
|
self.tag.as_ptr().cast(),
|
|
16,
|
|
self.tag.as_mut_ptr().cast(),
|
|
16,
|
|
&mut data_out_written,
|
|
);
|
|
}
|
|
self.tmp.copy_from_slice(&self.tag);
|
|
self.tmp[12] &= 0x7f;
|
|
unsafe {
|
|
if CCCryptorReset(self.ctr, self.tmp.as_ptr().cast()) != 0 {
|
|
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Feed plaintext for second pass and write ciphertext to supplied buffer.
|
|
/// This may be called more than once.
|
|
#[inline(always)]
|
|
pub fn encrypt_second_pass(&mut self, plaintext: &[u8], ciphertext: &mut [u8]) {
|
|
unsafe {
|
|
assert!(ciphertext.len() >= plaintext.len());
|
|
let mut data_out_written: usize = 0;
|
|
CCCryptorUpdate(
|
|
self.ctr,
|
|
plaintext.as_ptr().cast(),
|
|
plaintext.len(),
|
|
ciphertext.as_mut_ptr().cast(),
|
|
ciphertext.len(),
|
|
&mut data_out_written,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Encrypt plaintext in place.
|
|
/// This may be called more than once.
|
|
#[inline(always)]
|
|
pub fn encrypt_second_pass_in_place(&mut self, plaintext_to_ciphertext: &mut [u8]) {
|
|
unsafe {
|
|
let mut data_out_written: usize = 0;
|
|
CCCryptorUpdate(
|
|
self.ctr,
|
|
plaintext_to_ciphertext.as_ptr().cast(),
|
|
plaintext_to_ciphertext.len(),
|
|
plaintext_to_ciphertext.as_mut_ptr().cast(),
|
|
plaintext_to_ciphertext.len(),
|
|
&mut data_out_written,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Finish second pass and return a reference to the tag for this message.
|
|
/// The tag returned remains valid until reset() is called.
|
|
#[inline(always)]
|
|
pub fn encrypt_second_pass_finish(&mut self) -> &[u8; 16] {
|
|
return &self.tag;
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn decrypt_init_internal(&mut self) {
|
|
self.tmp[12] &= 0x7f;
|
|
unsafe {
|
|
if CCCryptorReset(self.ctr, self.tmp.as_ptr().cast()) != 0 {
|
|
panic!("CCCryptorReset for CTR mode failed (old MacOS bug)");
|
|
}
|
|
let mut data_out_written = 0;
|
|
CCCryptorUpdate(
|
|
self.ecb_dec,
|
|
self.tag.as_ptr().cast(),
|
|
16,
|
|
self.tag.as_mut_ptr().cast(),
|
|
16,
|
|
&mut data_out_written,
|
|
);
|
|
let tmp = self.tmp.as_mut_ptr().cast::<u64>();
|
|
*tmp = *self.tag.as_mut_ptr().cast::<u64>();
|
|
*tmp.add(1) = 0;
|
|
CCCryptorGCMSetIV(self.gmac, self.tmp.as_ptr().cast(), 12);
|
|
}
|
|
}
|
|
|
|
/// Initialize this cipher for decryption.
|
|
/// The supplied tag must be 16 bytes in length. Any other length will panic.
|
|
#[inline(always)]
|
|
pub fn decrypt_init(&mut self, tag: &[u8]) {
|
|
self.tmp.copy_from_slice(tag);
|
|
self.tag.copy_from_slice(tag);
|
|
self.decrypt_init_internal();
|
|
}
|
|
|
|
/// Set additional authenticated data to be checked.
|
|
#[inline(always)]
|
|
pub fn decrypt_set_aad(&mut self, data: &[u8]) {
|
|
self.encrypt_set_aad(data);
|
|
}
|
|
|
|
/// Decrypt ciphertext and write to plaintext.
|
|
/// This may be called more than once.
|
|
#[inline(always)]
|
|
pub fn decrypt(&mut self, ciphertext: &[u8], plaintext: &mut [u8]) {
|
|
unsafe {
|
|
let mut data_out_written = 0;
|
|
CCCryptorUpdate(
|
|
self.ctr,
|
|
ciphertext.as_ptr().cast(),
|
|
ciphertext.len(),
|
|
plaintext.as_mut_ptr().cast(),
|
|
plaintext.len(),
|
|
&mut data_out_written,
|
|
);
|
|
CCCryptorGCMAddAAD(self.gmac, plaintext.as_ptr().cast(), plaintext.len());
|
|
}
|
|
}
|
|
|
|
/// Decrypt ciphertext in place.
|
|
/// This may be called more than once.
|
|
#[inline(always)]
|
|
pub fn decrypt_in_place(&mut self, ciphertext_to_plaintext: &mut [u8]) {
|
|
unsafe {
|
|
let mut data_out_written = 0;
|
|
CCCryptorUpdate(
|
|
self.ctr,
|
|
ciphertext_to_plaintext.as_ptr().cast(),
|
|
ciphertext_to_plaintext.len(),
|
|
ciphertext_to_plaintext.as_mut_ptr().cast(),
|
|
ciphertext_to_plaintext.len(),
|
|
&mut data_out_written,
|
|
);
|
|
CCCryptorGCMAddAAD(self.gmac, ciphertext_to_plaintext.as_ptr().cast(), ciphertext_to_plaintext.len());
|
|
}
|
|
}
|
|
|
|
/// Finish decryption and returns the decrypted tag if the message appears valid.
|
|
#[inline(always)]
|
|
pub fn decrypt_finish(&mut self) -> Option<&[u8; 16]> {
|
|
unsafe {
|
|
CCCryptorGCMFinalize(self.gmac, self.tmp.as_mut_ptr().cast(), 16);
|
|
let tmp = self.tmp.as_mut_ptr().cast::<u64>();
|
|
if *self.tag.as_mut_ptr().cast::<u64>().offset(1) == *tmp ^ *tmp.offset(1) {
|
|
Some(&self.tag)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for AesGmacSiv {}
|