spetz commented on code in PR #4615: URL: https://github.com/apache/iggy/pull/4615#discussion_r4338667859
########## core/common/src/utils/stream_key.rs: ########## @@ -0,0 +1,1042 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use crate::IggyError; +use crate::utils::crypto::{Aes256GcmEncryptor, EncryptorKind}; +use crate::utils::text; +use aes_gcm::aead::{Aead, Generate, Payload}; +use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; +use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use ring::rand::{SecureRandom, SystemRandom}; +use ring::{digest, hkdf}; +use secrecy::zeroize::Zeroizing; +use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox}; +use std::fmt::{Debug, Formatter}; +use std::str::FromStr; +use std::sync::Arc; + +pub use iggy_binary_protocol::requests::streams::{ + MAX_WRAP_SET_ENVELOPES, WRAPPED_STREAM_KEY_LENGTH, +}; + +pub const STREAM_KEY_LENGTH: usize = 32; +/// Most retired config keys a node keeps for unwrapping during a rotation. +pub const MAX_PREVIOUS_KEYS: usize = MAX_WRAP_SET_ENVELOPES - 1; + +const WRAPPED_STREAM_KEY_VERSION: u8 = 1; +const FINGERPRINT_LENGTH: usize = 8; +const NONCE_LENGTH: usize = 12; +const WRAPPED_KEY_CIPHERTEXT_LENGTH: usize = STREAM_KEY_LENGTH + 16; +const ORIGIN_OFFSET: usize = 1; +const KEK_FINGERPRINT_OFFSET: usize = ORIGIN_OFFSET + 1; +const NONCE_OFFSET: usize = KEK_FINGERPRINT_OFFSET + FINGERPRINT_LENGTH; +const CIPHERTEXT_OFFSET: usize = NONCE_OFFSET + NONCE_LENGTH; +const KEY_FINGERPRINT_OFFSET: usize = CIPHERTEXT_OFFSET + WRAPPED_KEY_CIPHERTEXT_LENGTH; +const STREAM_KEY_WRAP_INFO: &[u8] = b"iggy/stream-key-wrap/v1"; +const STREAM_KEY_AAD_LABEL: &[u8] = b"iggy/stream-key/v1"; +const STREAM_KEY_FINGERPRINT_LABEL: &[u8] = b"iggy/stream-key-fingerprint/v1"; +const KEK_FINGERPRINT_LABEL: &[u8] = b"iggy/stream-kek-fingerprint/v1"; + +/// A stream's own data encryption key: 32 random bytes. +/// +/// Never printed and never serialized. The bytes leave this type only to be +/// wrapped, encoded into a create request, or turned into an encryptor. +pub struct StreamEncryptionKey(SecretBox<[u8; STREAM_KEY_LENGTH]>); + +/// Who chose a stream's key, recorded and authenticated in its envelope. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum StreamKeyOrigin { + /// The client sent the key with the create. + Client = 1, + /// The server generated the key because the client sent none. + Server = 2, +} + +/// A stream key wrapped under one key-encryption key. +/// +/// Layout: `[version:1][origin:1][kek_fingerprint:8][nonce:12][ciphertext:32][tag:16][key_fingerprint:8]`. +/// The version, the origin and both fingerprints are authenticated as +/// associated data. +#[derive(Clone, PartialEq, Eq)] +pub struct WrappedStreamKey([u8; WRAPPED_STREAM_KEY_LENGTH]); + +/// One stream key wrapped under one or more key-encryption keys: one envelope +/// outside a rotation, one per configured key during it. All envelopes hold +/// the same key from the same origin, sorted by KEK fingerprint, none twice. +#[derive(Clone, PartialEq, Eq)] +pub struct WrapSet { + envelopes: Vec<WrappedStreamKey>, + bytes: Vec<u8>, +} + +/// The key-encryption keys derived from the server config: the current one, +/// which wraps, and the previous ones a rotation still unwraps with. +#[derive(Clone)] +pub struct ServerKeyContext { + current: Option<KeyEncryptionKey>, + previous: Vec<KeyEncryptionKey>, + generates: bool, +} + +#[derive(Clone)] +struct KeyEncryptionKey { + cipher: Aes256Gcm, + fingerprint: [u8; FINGERPRINT_LENGTH], +} + +struct DerivedKeyLength; + +impl StreamEncryptionKey { + /// A new random key from the operating system's secure generator. + /// + /// # Errors + /// + /// `CannotEncryptData` when the generator fails. + pub fn generate() -> Result<Self, IggyError> { + let mut key = SecretBox::new(Box::new([0u8; STREAM_KEY_LENGTH])); + SystemRandom::new() + .fill(key.expose_secret_mut()) + .map_err(|_| IggyError::CannotEncryptData)?; + Ok(Self(key)) + } + + /// 16 lowercase hex characters identifying this key without revealing it. + #[must_use] + pub fn fingerprint(&self) -> String { + hex(&self.fingerprint_bytes()) + } + + /// The raw key, for the request encoder and the stream encryptor. + #[must_use] + pub fn as_bytes(&self) -> &[u8; STREAM_KEY_LENGTH] { + self.0.expose_secret() + } + + /// The encryptor for this stream's message payloads and user headers. + /// + /// # Errors + /// + /// `InvalidEncryptionKey` when the cipher refuses the key. + pub fn encryptor(&self) -> Result<Arc<EncryptorKind>, IggyError> { Review Comment: Fixed. Server stream payloads and user headers now use AES-256-GCM-SIV at `core/common/src/utils/crypto.rs:123`, while client payload encryption stays AES-256-GCM. `given_siv_ciphertext_when_decrypting_should_authenticate_every_byte` and `given_stream_key_when_encrypting_should_use_a_distinct_cipher_from_client_payloads` pin the change. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
