| #include <sframe/sframe.h> |
| |
| #include "crypto.h" |
| #include "header.h" |
| |
| #include <array> |
| #include <iomanip> |
| #include <iostream> |
| #include <stdexcept> |
| #include <tuple> |
| |
| namespace sframe { |
| |
| std::ostream& |
| operator<<(std::ostream& str, const input_bytes data) |
| { |
| str.flags(std::ios::hex); |
| for (const auto& byte : data) { |
| str << std::setw(2) << std::setfill('0') << int(byte); |
| } |
| return str; |
| } |
| |
| /// |
| /// Errors |
| /// |
| |
| unsupported_ciphersuite_error::unsupported_ciphersuite_error() |
| : std::runtime_error("Unsupported ciphersuite") |
| {} |
| |
| authentication_error::authentication_error() |
| : std::runtime_error("AEAD authentication failure") |
| {} |
| |
| /// |
| /// Context |
| /// |
| |
| Context::Context(CipherSuite suite_in) |
| : SFrame(suite_in) |
| {} |
| |
| static const bytes sframe_label{ |
| 0x53, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x31, 0x30 // "SFrame10" |
| }; |
| static const bytes sframe_key_label{ 0x6b, 0x65, 0x79 }; // "key" |
| static const bytes sframe_salt_label{ 0x73, 0x61, 0x6c, 0x74 }; // "salt" |
| |
| static const bytes sframe_ctr_label{ |
| // "SFrame10 AES CM AEAD" |
| 0x53, 0x46, 0x72, 0x61, 0x6d, 0x65, 0x31, 0x30, 0x20, 0x41, |
| 0x45, 0x53, 0x20, 0x43, 0x4d, 0x20, 0x41, 0x45, 0x41, 0x44, |
| }; |
| static const bytes sframe_enc_label{ 0x65, 0x6e, 0x63 }; // "enc" |
| static const bytes sframe_auth_label{ 0x61, 0x75, 0x74, 0x68 }; // "auth" |
| |
| SFrame::KeyState |
| SFrame::KeyState::from_base_key(CipherSuite suite, const bytes& base_key) |
| { |
| auto key_size = cipher_key_size(suite); |
| auto nonce_size = cipher_nonce_size(suite); |
| auto hash_size = cipher_digest_size(suite); |
| |
| auto secret = hkdf_extract(suite, sframe_label, base_key); |
| auto key = hkdf_expand(suite, secret, sframe_key_label, key_size); |
| auto salt = hkdf_expand(suite, secret, sframe_salt_label, nonce_size); |
| |
| // If using CTR+HMAC, set key = enc_key || auth_key |
| if (suite == CipherSuite::AES_CM_128_HMAC_SHA256_4 || |
| suite == CipherSuite::AES_CM_128_HMAC_SHA256_8) { |
| secret = hkdf_extract(suite, sframe_ctr_label, key); |
| |
| auto main_key = key; |
| auto enc_key = hkdf_expand(suite, secret, sframe_enc_label, key_size); |
| auto auth_key = hkdf_expand(suite, secret, sframe_auth_label, hash_size); |
| |
| key = enc_key; |
| key.insert(key.end(), auth_key.begin(), auth_key.end()); |
| } |
| |
| return KeyState{ key, salt, 0 }; |
| } |
| |
| void |
| Context::add_key(KeyID key_id, const bytes& base_key) |
| { |
| auto key_state = KeyState::from_base_key(suite, base_key); |
| state[key_id] = std::move(key_state); |
| } |
| |
| static bytes |
| form_nonce(Counter ctr, const bytes& salt) |
| { |
| auto nonce = salt; |
| for (size_t i = 0; i < sizeof(ctr); i++) { |
| nonce[nonce.size() - i - 1] ^= uint8_t(ctr >> (8 * i)); |
| } |
| |
| return nonce; |
| } |
| |
| SFrame::SFrame(CipherSuite suite_in) |
| : suite(suite_in) |
| {} |
| |
| SFrame::~SFrame() = default; |
| |
| output_bytes |
| SFrame::_protect(KeyID key_id, output_bytes ciphertext, input_bytes plaintext) |
| { |
| auto& state = get_state(key_id); |
| const auto ctr = state.counter; |
| state.counter += 1; |
| |
| auto hdr_size = Header{ key_id, ctr }.encode(ciphertext); |
| auto header = ciphertext.subspan(0, hdr_size); |
| auto inner_ciphertext = ciphertext.subspan(hdr_size); |
| |
| const auto nonce = form_nonce(ctr, state.salt); |
| auto final_ciphertext = |
| seal(suite, state.key, nonce, inner_ciphertext, header, plaintext); |
| return ciphertext.subspan(0, hdr_size + final_ciphertext.size()); |
| } |
| |
| output_bytes |
| SFrame::_unprotect(output_bytes plaintext, input_bytes ciphertext) |
| { |
| const auto header_aad = Header::decode(ciphertext); |
| const auto& header = std::get<0>(header_aad); |
| const auto& aad = std::get<1>(header_aad); |
| |
| auto inner_ciphertext = ciphertext.subspan(aad.size()); |
| |
| auto& state = get_state(header.key_id); |
| const auto nonce = form_nonce(header.counter, state.salt); |
| return open(suite, state.key, nonce, plaintext, aad, inner_ciphertext); |
| } |
| |
| output_bytes |
| Context::protect(KeyID key_id, output_bytes ciphertext, input_bytes plaintext) |
| { |
| return _protect(key_id, ciphertext, plaintext); |
| } |
| |
| output_bytes |
| Context::unprotect(output_bytes plaintext, input_bytes ciphertext) |
| { |
| return _unprotect(plaintext, ciphertext); |
| } |
| |
| SFrame::KeyState& |
| Context::get_state(KeyID key_id) |
| { |
| auto it = state.find(key_id); |
| if (it == state.end()) { |
| throw invalid_parameter_error("Unknown key"); |
| } |
| |
| return it->second; |
| } |
| |
| /// |
| /// MLSContext |
| /// |
| |
| MLSContext::MLSContext(CipherSuite suite_in, size_t epoch_bits_in) |
| : SFrame(suite_in) |
| , epoch_bits(epoch_bits_in) |
| , epoch_mask((size_t(1) << epoch_bits_in) - 1) |
| , epoch_cache(size_t(1) << epoch_bits_in) |
| { |
| std::for_each(epoch_cache.begin(), |
| epoch_cache.end(), |
| [&](std::unique_ptr<EpochKeys>& ptr) { ptr.reset(nullptr); }); |
| } |
| |
| void |
| MLSContext::add_epoch(EpochID epoch_id, const bytes& sframe_epoch_secret) |
| { |
| auto epoch_index = epoch_id & epoch_mask; |
| epoch_cache.at(epoch_index).reset(new EpochKeys(sframe_epoch_secret)); |
| } |
| |
| output_bytes |
| MLSContext::protect(EpochID epoch_id, |
| SenderID sender_id, |
| output_bytes ciphertext, |
| input_bytes plaintext) |
| { |
| auto epoch_index = epoch_id & epoch_mask; |
| auto key_id = KeyID((uint64_t(sender_id) << epoch_bits) | epoch_index); |
| return _protect(key_id, ciphertext, plaintext); |
| } |
| |
| output_bytes |
| MLSContext::unprotect(output_bytes plaintext, input_bytes ciphertext) |
| { |
| return _unprotect(plaintext, ciphertext); |
| } |
| |
| MLSContext::EpochKeys::EpochKeys(bytes sframe_epoch_secret_in) |
| : sframe_epoch_secret(std::move(sframe_epoch_secret_in)) |
| {} |
| |
| SFrame::KeyState& |
| MLSContext::EpochKeys::get(CipherSuite ciphersuite, SenderID sender_id) |
| { |
| auto it = sender_keys.find(sender_id); |
| if (it != sender_keys.end()) { |
| return it->second; |
| } |
| |
| auto hash_size = cipher_digest_size(ciphersuite); |
| auto enc_sender_id = bytes(4); |
| encode_uint(sender_id, enc_sender_id); |
| |
| auto sender_base_key = |
| hkdf_expand(ciphersuite, sframe_epoch_secret, enc_sender_id, hash_size); |
| auto key_state = KeyState::from_base_key(ciphersuite, sender_base_key); |
| sender_keys.insert({ sender_id, std::move(key_state) }); |
| |
| return sender_keys.at(sender_id); |
| } |
| |
| SFrame::KeyState& |
| MLSContext::get_state(KeyID key_id) |
| { |
| const auto epoch_index = EpochID(key_id & epoch_mask); |
| const auto sender_id = SenderID(key_id >> epoch_bits); |
| |
| auto& epoch = epoch_cache.at(epoch_index); |
| if (!epoch) { |
| throw invalid_parameter_error("Unknown epoch"); |
| } |
| |
| return epoch->get(suite, sender_id); |
| } |
| |
| } // namespace sframe |