1 // Copyright 2025 The Pigweed Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 // use this file except in compliance with the License. You may obtain a copy of 5 // the License at 6 // 7 // https://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, WITHOUT 11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 // License for the specific language governing permissions and limitations under 13 // the License. 14 15 #pragma once 16 17 #include "pw_crypto/aes.h" 18 19 namespace pw::crypto::aes_cmac { 20 21 /// Computes the message authentication code (MAC) of a message using AES-CMAC. 22 /// 23 /// The interface specifically allows computing the MAC of potentially long, 24 /// non-contiguous messages. A MAC is similar to a message digest in that it can 25 /// be used to verify integrity, but since it also takes a secret `key` as input 26 /// it can also be used to verify authenticity, as the other party must also 27 /// know the secret key to compute the same MAC. 28 /// 29 /// Usage: 30 /// 31 /// @code{.cpp} 32 /// if (!Cmac(key).Update(part1).Update(part2).Final(out_mac).ok()) { 33 /// // Error handling. 34 /// } 35 /// @endcode 36 class Cmac { 37 private: 38 enum class State { 39 /// Initialized and accepting input (via `Update()`). 40 kReady = 1, 41 /// Finalized by `Final()`. Any additional requests to `Update()` or 42 /// `Final()` will trigger a transition to `kError`. 43 kFinalized = 2, 44 /// In an unrecoverable error state. 45 kError = 3, 46 }; 47 48 public: 49 /// Initialize a `Cmac` instance using the specified `key`. 50 /// 51 /// @note Any error during initialization will be reflected in the return 52 /// value of `Final()`. 53 /// 54 /// @param[in] key A byte string containing the key to used in the CMAC 55 /// operation. 56 template <size_t key_size> Cmac(span<const std::byte,key_size> key)57 explicit Cmac(span<const std::byte, key_size> key) { 58 constexpr auto this_op = pw::crypto::aes::backend::AesOperation::kCmac; 59 static_assert(pw::crypto::aes::internal::BackendSupports<this_op>(key_size), 60 "Usupported key size for CMAC for backend."); 61 62 if (!pw::crypto::aes::backend::DoInit(native_ctx_, key).ok()) { 63 PW_LOG_DEBUG("backend::DoInit() failed"); 64 state_ = State::kError; 65 return; 66 } 67 68 state_ = State::kReady; 69 } 70 Cmac(span<const std::byte,dynamic_extent> key)71 explicit Cmac(span<const std::byte, dynamic_extent> key) { 72 constexpr auto this_op = pw::crypto::aes::backend::AesOperation::kCmac; 73 PW_ASSERT(pw::crypto::aes::internal::BackendSupports<this_op>(key.size())); 74 75 if (!pw::crypto::aes::backend::DoInit(native_ctx_, key).ok()) { 76 PW_LOG_DEBUG("backend::DoInit() failed"); 77 state_ = State::kError; 78 return; 79 } 80 81 state_ = State::kReady; 82 } 83 84 // Overload to enable implicit conversions to span if `T` is implicitly 85 // convertible to `span`. 86 template <typename T, 87 typename = std::enable_if_t< 88 std::is_convertible_v<T, decltype(span(std::declval<T>()))>>> Cmac(const T & key)89 explicit Cmac(const T& key) : Cmac(span(key)) {} 90 91 /// Feeds `data` to the running AES-CMAC operation. 92 /// 93 /// The feeding can involve zero or more `Update()` calls and the order 94 /// matters. 95 /// 96 /// @note Any error during update will be reflected in the return value of 97 /// `Final()`. 98 /// 99 /// @param[in] data A byte string of any length to be fed to the running 100 /// AES-CMAC operation. 101 /// 102 /// @returns This same `Cmac` instance to allow chaining calls. Update(ConstByteSpan data)103 Cmac& Update(ConstByteSpan data) { 104 if (data.empty()) { 105 return *this; 106 } 107 108 if (state_ != State::kReady) { 109 PW_LOG_DEBUG("The backend is not ready/initialized"); 110 return *this; 111 } 112 113 if (!pw::crypto::aes::backend::DoUpdate(native_ctx_, data).ok()) { 114 PW_LOG_DEBUG("backend::DoUpdate() failed"); 115 state_ = State::kError; 116 return *this; 117 } 118 119 return *this; 120 } 121 122 /// Finishes the AES-CMAC operations and outputs the final MAC. 123 /// 124 /// Additionally, `Final()` locks down the `Cmac` instance from any additional 125 /// use. 126 /// 127 /// @note Any error during initialization or update will be reflected in the 128 /// return value of `Final()`. 129 /// 130 /// @param[in] out_mac A byte span with a size of at least `kBlockSizeBytes` 131 /// where the final MAC will be written. If the span is larger than 132 /// `kBlockSizeBytes` only the first `kBlockSizeBytes` will be modified. 133 /// 134 /// @returns @pw_status{OK} if the AES-CMAC operation was successful and the 135 /// MAC was written to `out_mac`, @pw_status{RESOURCE_EXHAUSTED} if `out_mac` 136 /// is too small to write the MAC to, or @pw_status{INTERNAL} if an error was 137 /// encountered during the operation. Final(aes::BlockSpan out_mac)138 Status Final(aes::BlockSpan out_mac) { 139 if (state_ != State::kReady) { 140 PW_LOG_DEBUG("The backend is not ready/initialized"); 141 return Status::FailedPrecondition(); 142 } 143 144 auto status = pw::crypto::aes::backend::DoFinal(native_ctx_, out_mac); 145 if (!status.ok()) { 146 PW_LOG_DEBUG("backend::DoFinal() failed"); 147 state_ = State::kError; 148 return status; 149 } 150 151 state_ = State::kFinalized; 152 return OkStatus(); 153 } 154 155 private: 156 // Common state. Tracked by the front-end. 157 State state_; 158 // Backend-specific context. 159 pw::crypto::aes::backend::NativeCmacContext native_ctx_; 160 }; 161 162 } // namespace pw::crypto::aes_cmac 163