Skip to content

Commit

Permalink
pw_crypto: Add AES facade
Browse files Browse the repository at this point in the history
Add an initial facade for AES that includes raw::EncryptBlock. No
backends are implemented yet, so it is configured as a header library
rather than a facade, but this will be updated when the first backend
is implemented.

Change-Id: Id152a22c52f44e7abc8fa9298e5f4bbeb7203fc3
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/231911
Presubmit-Verified: CQ Bot Account <[email protected]>
Commit-Queue: Jason Graffius <[email protected]>
Lint: Lint 🤖 <[email protected]>
Reviewed-by: Ali Zhang <[email protected]>
  • Loading branch information
jasongraffius authored and CQ Bot Account committed Dec 6, 2024
1 parent 92c01bf commit 23cc90c
Show file tree
Hide file tree
Showing 8 changed files with 451 additions and 0 deletions.
1 change: 1 addition & 0 deletions docs/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ _doxygen_input_files = [ # keep-sorted: start
"$dir_pw_containers/public/pw_containers/intrusive_multimap.h",
"$dir_pw_containers/public/pw_containers/intrusive_multiset.h",
"$dir_pw_containers/public/pw_containers/intrusive_set.h",
"$dir_pw_crypto/public/pw_crypto/aes.h",
"$dir_pw_crypto/public/pw_crypto/ecdsa.h",
"$dir_pw_crypto/public/pw_crypto/sha256.h",
"$dir_pw_digital_io/public/pw_digital_io/digital_io.h",
Expand Down
26 changes: 26 additions & 0 deletions pw_crypto/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,32 @@ pw_cc_test(
],
)

cc_library(
name = "aes",
hdrs = [
"public/pw_crypto/aes.h",
"public/pw_crypto/aes_backend.h",
"public/pw_crypto/aes_backend_defs.h",
],
includes = ["public"],
deps = [
"//pw_bytes",
"//pw_status",
],
)

pw_cc_test(
name = "aes_test",
srcs = [
"aes_test.cc",
],
deps = [
":aes",
"//pw_containers:vector",
"//pw_unit_test",
],
)

filegroup(
name = "doxygen",
srcs = [
Expand Down
22 changes: 22 additions & 0 deletions pw_crypto/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pw_size_diff("size_report") {

pw_test_group("tests") {
tests = [
":aes_test",
":sha256_test",
":sha256_mock_test",
":ecdsa_test",
Expand Down Expand Up @@ -219,3 +220,24 @@ pw_test("ecdsa_test") {
deps = [ ":ecdsa" ]
sources = [ "ecdsa_test.cc" ]
}

source_set("aes") {
public_configs = [ ":default_config" ]
public = [
"public/pw_crypto/aes.h",
"public/pw_crypto/aes_backend.h",
"public/pw_crypto/aes_backend_defs.h",
]
public_deps = [
"$dir_pw_bytes",
"$dir_pw_status",
]
}

pw_test("aes_test") {
deps = [
":aes",
"$dir_pw_containers:vector",
]
sources = [ "aes_test.cc" ]
}
127 changes: 127 additions & 0 deletions pw_crypto/aes_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// Copyright 2024 The Pigweed Authors
//
// Licensed 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
//
// https://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.

#include "pw_crypto/aes.h"

#include <algorithm>
#include <iterator>

#include "pw_assert/assert.h"
#include "pw_containers/vector.h"
#include "pw_status/status.h"
#include "pw_unit_test/framework.h"

#define EXPECT_OK(expr) EXPECT_EQ(pw::OkStatus(), (expr))
#define STR_TO_BYTES(str) (as_bytes(span(str).subspan<0, sizeof(str) - 1>()))

namespace pw::crypto::aes {
// Note: The contents of the `backend` namespace here is a placeholder as this
// test currently only ensures that the facade compiles and can be used
// correctly.
namespace backend {
Status DoEncryptBlock(ConstByteSpan, ConstBlockSpan, BlockSpan) {
return OkStatus();
}
} // namespace backend

namespace {

using backend::AesOperation;
using backend::SupportedKeySize;
using internal::BackendSupports;
using unsafe::aes::EncryptBlock;

template <typename T>
void ZeroOut(T& container) {
std::fill(std::begin(container), std::end(container), std::byte{0});
}

// Create a view (`span<const T>`, as opposed to a `span<T>`) of a `pw::Vector`.
template <typename T>
span<const T> View(pw::Vector<T>& vector) {
return span(vector.begin(), vector.end());
}

// Intentionally chosen to not be a valid AES key size, but larger than the
// largest AES key size.
constexpr size_t kMaxVectorSize = 503;

TEST(Aes, UnsafeEncryptApi) {
constexpr auto kRawEncryptBlockOp = AesOperation::kUnsafeEncryptBlock;
ConstBlockSpan message_block = STR_TO_BYTES("hello, world!\0\0\0");

// Ensure various output types work correctly.
std::byte encrypted_carr[16];
std::array<std::byte, 16> encrypted_arr;
Block encrypted_block;

// Ensure dynamically-sized keys will work.
Vector<std::byte, kMaxVectorSize> dynamic_key;

auto reset = [&] {
ZeroOut(dynamic_key);
ZeroOut(encrypted_carr);
ZeroOut(encrypted_arr);
ZeroOut(encrypted_block);

dynamic_key.clear();
};

if constexpr (BackendSupports<kRawEncryptBlockOp>(SupportedKeySize::k128)) {
span<const std::byte, 16> key = STR_TO_BYTES(
"\x13\xA2\x27\x93\x8D\x1D\x89\x46\x07\x4C\xA0\x71\xF2\xF7\x54\xC5");

reset();

EXPECT_OK(EncryptBlock(key, message_block, encrypted_carr));
EXPECT_OK(EncryptBlock(key, message_block, encrypted_arr));
EXPECT_OK(EncryptBlock(key, message_block, encrypted_block));

std::copy(key.begin(), key.end(), std::back_inserter(dynamic_key));
EXPECT_OK(EncryptBlock(View(dynamic_key), message_block, encrypted_block));
}

if constexpr (BackendSupports<kRawEncryptBlockOp>(SupportedKeySize::k192)) {
span<const std::byte, 24> key = STR_TO_BYTES(
"\x2B\x43\x70\x51\xBF\x91\xF0\xFD\x4E\x9B\x89\xB7\x35\x40\xD4\x1B"
"\x15\xBC\xD7\xC2\x22\xBC\x03\x76");

reset();

EXPECT_OK(EncryptBlock(key, message_block, encrypted_carr));
EXPECT_OK(EncryptBlock(key, message_block, encrypted_arr));
EXPECT_OK(EncryptBlock(key, message_block, encrypted_block));

std::copy(key.begin(), key.end(), std::back_inserter(dynamic_key));
EXPECT_OK(EncryptBlock(View(dynamic_key), message_block, encrypted_block));
}

if constexpr (BackendSupports<kRawEncryptBlockOp>(SupportedKeySize::k256)) {
span<const std::byte, 32> key = STR_TO_BYTES(
"\xA4\xB9\x15\x76\xF2\x16\x67\xB0\x33\x5E\xA6\x8D\xBD\x23\xDF\x29"
"\x84\xBF\x8D\xBE\x56\x77\x13\x28\x14\x55\xD9\x75\xDD\xEE\x4E\x0B");

reset();

EXPECT_OK(EncryptBlock(key, message_block, encrypted_carr));
EXPECT_OK(EncryptBlock(key, message_block, encrypted_arr));
EXPECT_OK(EncryptBlock(key, message_block, encrypted_block));

std::copy(key.begin(), key.end(), std::back_inserter(dynamic_key));
EXPECT_OK(EncryptBlock(View(dynamic_key), message_block, encrypted_block));
}
}

} // namespace
} // namespace pw::crypto::aes
21 changes: 21 additions & 0 deletions pw_crypto/docs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@ ECDSA
// Handle errors.
}
---
AES
---

1. Encrypting a single AES 128-bit block.

.. warning::
This is a low-level operation. Users should know exactly what they are doing
and must ensure that this operation does not violate any safety bounds that
more refined operations usually ensure.

.. code-block:: cpp
#include "pw_crypto/aes.h"
std::byte encrypted[16];
if (!pw::crypto::unsafe::aes::EncryptBlock(key, message, encrypted).ok()) {
// Handle errors.
}
-------------
Configuration
-------------
Expand Down
148 changes: 148 additions & 0 deletions pw_crypto/public/pw_crypto/aes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright 2024 The Pigweed Authors
//
// Licensed 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
//
// https://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.

#pragma once

#include "pw_assert/assert.h"
#include "pw_bytes/span.h"
#include "pw_crypto/aes_backend.h"
#include "pw_crypto/aes_backend_defs.h"
#include "pw_status/status.h"

namespace pw::crypto::aes {

/// Number of bytes in an AES block (16). This is independent of key size.
constexpr size_t kBlockSizeBytes = (128 / 8);
/// Number of bytes in a 128-bit key (16).
constexpr size_t kKey128SizeBytes = (128 / 8);
/// Number of bytes in a 192-bit key (24).
constexpr size_t kKey192SizeBytes = (192 / 8);
/// Number of bytes in a 256-bit key (32).
constexpr size_t kKey256SizeBytes = (256 / 8);

/// A single AES block.
using Block = std::array<std::byte, kBlockSizeBytes>;
/// A span of bytes the same size as an AES block.
using BlockSpan = span<std::byte, kBlockSizeBytes>;
/// A span of const bytes the same size as an AES block.
using ConstBlockSpan = span<const std::byte, kBlockSizeBytes>;

namespace internal {
/// Utility to get the appropriate `SupportedKeySize` from number of bytes.
constexpr backend::SupportedKeySize FromKeySizeBytes(size_t size) {
switch (size) {
case kKey128SizeBytes:
return backend::SupportedKeySize::k128;
case kKey192SizeBytes:
return backend::SupportedKeySize::k192;
case kKey256SizeBytes:
return backend::SupportedKeySize::k256;
default:
return backend::SupportedKeySize::kUnsupported;
}
}

template <backend::AesOperation op>
constexpr bool BackendSupports(backend::SupportedKeySize key_size) {
return (backend::supported<op> & key_size) !=
backend::SupportedKeySize::kUnsupported;
}

/// Utility to determine if an operation supports a particular key size.
template <backend::AesOperation op>
constexpr bool BackendSupports(size_t key_size_bytes) {
return BackendSupports<op>(FromKeySizeBytes(key_size_bytes));
}

} // namespace internal

namespace backend {
/// Implement `raw::EncryptBlock` in the backend. This function should not be
/// called directly, call `raw::EncryptBlock` instead.
///
/// @param[in] key A byte string containing the key to use to encrypt the block.
/// The key is guaranteed to be a length that is supported by the backend as
/// declared by `supports<kRawEncryptBlock>`.
///
/// @param[in] plaintext A 128-bit block of data to encrypt.
///
/// @param[in] out_ciphertext A 128-bit destination block in which to store the
/// encrypted data.
///
/// @return @pw_status{OK} for a successful encryption, or an error ``Status``
/// otherwise.
Status DoEncryptBlock(ConstByteSpan key,
ConstBlockSpan plaintext,
BlockSpan out_ciphertext);
} // namespace backend
} // namespace pw::crypto::aes

namespace pw::crypto::unsafe::aes {
/// Perform raw block-level AES encryption of a single AES block.
///
/// @warning This is a low-level operation that should be considered "unsafe" in
/// that users should know exactly what they are doing and must ensure that this
/// operation does not violate any safety bounds that more refined operations
/// usually ensure.
///
/// Example:
///
/// @code{.cpp}
/// #include "pw_crypto/aes.h"
///
/// // Encrypt a single block of data.
/// std::byte encrypted[16];
/// if (pw::crypto::aes::raw::EncryptBlock(key, message_block, encrypted)) {
/// // handle errors.
/// }
/// @endcode
///
/// @param[in] key A byte string containing the key to use to encrypt the block.
/// If `key` has a static extent then this will fail to compile if the key size
/// is not supported by the backend. If it has a dynamic extent, then this will
/// fail an assertion at runtime if it is not a supported size.
///
/// @param[in] plaintext A 128-bit block of data to encrypt.
///
/// @param[in] out_ciphertext A 128-bit destination block in which to store the
/// encrypted data.
///
/// @return @pw_status{OK} for a successful encryption, or an error ``Status``
/// otherwise.
template <size_t KeySize>
inline Status EncryptBlock(span<const std::byte, KeySize> key,
pw::crypto::aes::ConstBlockSpan plaintext,
pw::crypto::aes::BlockSpan out_ciphertext) {
constexpr auto kThisOp =
pw::crypto::aes::backend::AesOperation::kUnsafeEncryptBlock;
static_assert(pw::crypto::aes::internal::BackendSupports<kThisOp>(KeySize),
"Unsupported key size for EncryptBlock for backend.");
return pw::crypto::aes::backend::DoEncryptBlock(
key, plaintext, out_ciphertext);
}

// Specialization for dynamically sized spans.
template <>
inline Status EncryptBlock<dynamic_extent>(
span<const std::byte, dynamic_extent> key,
pw::crypto::aes::ConstBlockSpan plaintext,
pw::crypto::aes::BlockSpan out_ciphertext) {
constexpr auto kThisOp =
pw::crypto::aes::backend::AesOperation::kUnsafeEncryptBlock;
PW_ASSERT(pw::crypto::aes::internal::BackendSupports<kThisOp>(key.size()));
return pw::crypto::aes::backend::DoEncryptBlock(
key, plaintext, out_ciphertext);
}

} // namespace pw::crypto::unsafe::aes
Loading

0 comments on commit 23cc90c

Please sign in to comment.