Define MACsec HAL and ref impl
Add MACsec HAL interface and reference implementation.
This allow OEM to store MACsec PSK key in secure storage and provide
functions to use that key,
Bug: 254108688
Test: atest VtsHalMacsecPSKPluginV1_0Test
Change-Id: Iecfe4828839a1dab81989bf9b178ae41c6f46b82
diff --git a/macsec/aidl/default/Android.bp b/macsec/aidl/default/Android.bp
new file mode 100644
index 0000000..7c7346f
--- /dev/null
+++ b/macsec/aidl/default/Android.bp
@@ -0,0 +1,64 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// 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
+//
+// 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.
+//
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+cc_binary {
+ name: "android.hardware.macsec-service",
+ init_rc: ["android.hardware.macsec.rc"],
+ vendor: true,
+ relative_install_path: "hw",
+ srcs: [
+ "MacsecPskPlugin.cpp",
+ "service.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.macsec-V1-ndk",
+ "libcrypto",
+ "libbase",
+ "libbinder_ndk",
+ ],
+ vintf_fragments: ["android.hardware.macsec.xml"],
+}
+
+cc_fuzz {
+ name: "android.hardware.macsec@V1-default-service.aidl_fuzzer",
+ vendor: true,
+ srcs: [
+ "MacsecPskPlugin.cpp",
+ "fuzzer/fuzzer.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.macsec-V1-ndk",
+ "libcrypto",
+ "liblog",
+ ],
+ defaults: [
+ "service_fuzzer_defaults",
+ ],
+ fuzz_config: {
+ cc: [
+ "keithmok@google.com",
+ ],
+ },
+}
diff --git a/macsec/aidl/default/MacsecPskPlugin.cpp b/macsec/aidl/default/MacsecPskPlugin.cpp
new file mode 100644
index 0000000..82d2545
--- /dev/null
+++ b/macsec/aidl/default/MacsecPskPlugin.cpp
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * 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
+ *
+ * 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.
+ */
+
+#include "MacsecPskPlugin.h"
+#include <openssl/cipher.h>
+#include <openssl/mem.h>
+
+#include <android-base/format.h>
+#include <android-base/logging.h>
+
+namespace aidl::android::hardware::macsec {
+
+constexpr auto ok = &ndk::ScopedAStatus::ok;
+
+// vendor should hide the key in TEE/TA
+// CAK key can be either 16 / 32 bytes
+const std::vector<uint8_t> CAK_ID_1 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01};
+const std::vector<uint8_t> CAK_KEY_1 = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
+std::vector<uint8_t> CKN_1 = {0x31, 0x32, 0x33, 0x34}; // maximum 16 bytes
+
+const std::vector<uint8_t> CAK_ID_2 = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02};
+const std::vector<uint8_t> CAK_KEY_2 = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF,
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
+std::vector<uint8_t> CKN_2 = {0x35, 0x36, 0x37, 0x38}; // maximum 16 bytes
+
+static ndk::ScopedAStatus resultToStatus(binder_exception_t res, const std::string& msg = "") {
+ if (msg.empty()) {
+ return ndk::ScopedAStatus::fromExceptionCode(res);
+ }
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(res, msg.c_str());
+}
+
+static int omac1_aes(CMAC_CTX* ctx, const uint8_t* data, size_t data_len,
+ uint8_t* mac /* 16 bytes */) {
+ size_t outlen;
+
+ // Just reuse same key in ctx
+ if (!CMAC_Reset(ctx)) {
+ return -1;
+ }
+
+ if (!CMAC_Update(ctx, data, data_len)) {
+ return -1;
+ }
+
+ if (!CMAC_Final(ctx, mac, &outlen) || outlen != 16) {
+ return -1;
+ }
+ return 0;
+}
+
+static void put_be16(uint8_t* addr, uint16_t value) {
+ *addr++ = value >> 8;
+ *addr = value & 0xff;
+}
+
+/* IEEE Std 802.1X-2010, 6.2.1 KDF */
+static int aes_kdf(CMAC_CTX* ctx, const char* label, const uint8_t* context, int ctx_bits,
+ int ret_bits, uint8_t* ret) {
+ const int h = 128;
+ const int r = 8;
+ int i, n;
+ int lab_len, ctx_len, ret_len, buf_len;
+ uint8_t* buf;
+
+ lab_len = strlen(label);
+ ctx_len = (ctx_bits + 7) / 8;
+ ret_len = ((ret_bits & 0xffff) + 7) / 8;
+ buf_len = lab_len + ctx_len + 4;
+
+ memset(ret, 0, ret_len);
+
+ n = (ret_bits + h - 1) / h;
+ if (n > ((0x1 << r) - 1)) return -1;
+
+ buf = (uint8_t*)calloc(1, buf_len);
+ if (buf == NULL) return -1;
+
+ memcpy(buf + 1, label, lab_len);
+ memcpy(buf + lab_len + 2, context, ctx_len);
+ put_be16(&buf[buf_len - 2], ret_bits);
+
+ for (i = 0; i < n; i++) {
+ int res;
+
+ buf[0] = (uint8_t)(i + 1);
+ res = omac1_aes(ctx, buf, buf_len, ret);
+ if (res) {
+ free(buf);
+ return -1;
+ }
+ ret = ret + h / 8;
+ }
+ free(buf);
+ return 0;
+}
+
+MacsecPskPlugin::MacsecPskPlugin() {
+ // always make sure ckn is 16 bytes, zero padded
+ CKN_1.resize(16);
+ CKN_2.resize(16);
+
+ addTestKey(CAK_ID_1, CAK_KEY_1, CKN_1);
+ addTestKey(CAK_ID_2, CAK_KEY_2, CKN_2);
+}
+
+MacsecPskPlugin::~MacsecPskPlugin() {
+ for (auto s : mKeys) {
+ OPENSSL_cleanse(&s.kekEncCtx, sizeof(AES_KEY));
+ OPENSSL_cleanse(&s.kekDecCtx, sizeof(AES_KEY));
+ CMAC_CTX_free(s.ickCtx);
+ CMAC_CTX_free(s.cakCtx);
+ }
+}
+
+ndk::ScopedAStatus MacsecPskPlugin::addTestKey(const std::vector<uint8_t>& keyId,
+ const std::vector<uint8_t>& CAK,
+ const std::vector<uint8_t>& CKN) {
+ if (CAK.size() != 16 && CAK.size() != 32) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "CAK length must be 16 or 32 bytes");
+ }
+
+ if (keyId.size() != CAK.size()) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "Key ID must be same as CAK length");
+ }
+
+ std::vector<uint8_t> ckn;
+ ckn = CKN;
+ ckn.resize(16); // make sure it is always zero padded with maximum length of
+ // 16 bytes
+
+ AES_KEY kekEncCtx;
+ AES_KEY kekDecCtx;
+ CMAC_CTX* ickCtx;
+ CMAC_CTX* cakCtx;
+
+ // Create the CAK openssl context
+ cakCtx = CMAC_CTX_new();
+
+ CMAC_Init(cakCtx, CAK.data(), CAK.size(),
+ CAK.size() == 16 ? EVP_aes_128_cbc() : EVP_aes_256_cbc(), NULL);
+
+ // derive KEK from CAK (ieee802_1x_kek_aes_cmac)
+ std::vector<uint8_t> kek;
+ kek.resize(CAK.size());
+
+ aes_kdf(cakCtx, "IEEE8021 KEK", (const uint8_t*)ckn.data(), ckn.size() * 8, 8 * kek.size(),
+ kek.data());
+
+ AES_set_encrypt_key(kek.data(), kek.size() << 3, &kekEncCtx);
+ AES_set_decrypt_key(kek.data(), kek.size() << 3, &kekDecCtx);
+
+ // derive ICK from CAK (ieee802_1x_ick_aes_cmac)
+ std::vector<uint8_t> ick;
+ ick.resize(CAK.size());
+
+ aes_kdf(cakCtx, "IEEE8021 ICK", (const uint8_t*)CKN.data(), CKN.size() * 8, 8 * ick.size(),
+ ick.data());
+
+ ickCtx = CMAC_CTX_new();
+
+ CMAC_Init(ickCtx, ick.data(), ick.size(),
+ ick.size() == 16 ? EVP_aes_128_cbc() : EVP_aes_256_cbc(), NULL);
+
+ mKeys.push_back({keyId, kekEncCtx, kekDecCtx, ickCtx, cakCtx});
+
+ return ok();
+}
+
+ndk::ScopedAStatus MacsecPskPlugin::calcIcv(const std::vector<uint8_t>& keyId,
+ const std::vector<uint8_t>& data,
+ std::vector<uint8_t>* out) {
+ CMAC_CTX* ctx = NULL;
+
+ for (auto s : mKeys) {
+ if (s.keyId == keyId) {
+ ctx = s.ickCtx;
+ break;
+ }
+ }
+
+ if (ctx == NULL) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "Key not exist");
+ }
+
+ out->resize(16);
+ if (omac1_aes(ctx, data.data(), data.size(), out->data()) != 0) {
+ return resultToStatus(EX_SERVICE_SPECIFIC, "Internal error");
+ }
+
+ return ok();
+}
+
+ndk::ScopedAStatus MacsecPskPlugin::generateSak(const std::vector<uint8_t>& keyId,
+ const std::vector<uint8_t>& data,
+ const int sakLength, std::vector<uint8_t>* out) {
+ CMAC_CTX* ctx = NULL;
+
+ if ((sakLength != 16) && (sakLength != 32)) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "Invalid SAK length");
+ }
+
+ if (data.size() < sakLength) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "Invalid data length");
+ }
+
+ for (auto s : mKeys) {
+ if (s.keyId == keyId) {
+ ctx = s.cakCtx;
+ break;
+ }
+ }
+
+ if (ctx == NULL) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "Key not exist");
+ }
+
+ out->resize(sakLength);
+
+ if (aes_kdf(ctx, "IEEE8021 SAK", data.data(), data.size() * 8, out->size() * 8, out->data()) !=
+ 0) {
+ return resultToStatus(EX_SERVICE_SPECIFIC, "Internal error");
+ }
+
+ return ok();
+}
+
+ndk::ScopedAStatus MacsecPskPlugin::wrapSak(const std::vector<uint8_t>& keyId,
+ const std::vector<uint8_t>& sak,
+ std::vector<uint8_t>* out) {
+ if (sak.size() == 0 || sak.size() % 8 != 0) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT,
+ "SAK length not multiple of 8 or greater than 0");
+ }
+
+ AES_KEY* ctx = NULL;
+
+ for (auto s : mKeys) {
+ if (s.keyId == keyId) {
+ ctx = &s.kekEncCtx;
+ break;
+ }
+ }
+
+ if (ctx == NULL) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "Key not exist");
+ }
+
+ out->resize(sak.size() + 8);
+
+ if (AES_wrap_key(ctx, NULL, out->data(), sak.data(), sak.size()) > 0) {
+ return ok();
+ }
+
+ return resultToStatus(EX_SERVICE_SPECIFIC, "Internal error");
+}
+
+ndk::ScopedAStatus MacsecPskPlugin::unwrapSak(const std::vector<uint8_t>& keyId,
+ const std::vector<uint8_t>& sak,
+ std::vector<uint8_t>* out) {
+ if (sak.size() <= 8 || sak.size() % 8 != 0) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT,
+ "SAK length not multiple of 8 or greater than 0");
+ }
+
+ AES_KEY* ctx = NULL;
+
+ for (auto s : mKeys) {
+ if (s.keyId == keyId) {
+ ctx = &s.kekDecCtx;
+ break;
+ }
+ }
+
+ if (ctx == NULL) {
+ return resultToStatus(EX_ILLEGAL_ARGUMENT, "Key not exist");
+ }
+
+ out->resize(sak.size() - 8);
+
+ if (AES_unwrap_key(ctx, NULL, out->data(), sak.data(), sak.size()) > 0) {
+ return ok();
+ }
+
+ return resultToStatus(EX_SERVICE_SPECIFIC, "Internal error");
+}
+
+} // namespace aidl::android::hardware::macsec
diff --git a/macsec/aidl/default/MacsecPskPlugin.h b/macsec/aidl/default/MacsecPskPlugin.h
new file mode 100644
index 0000000..0b056e3
--- /dev/null
+++ b/macsec/aidl/default/MacsecPskPlugin.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * 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
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/macsec/BnMacsecPskPlugin.h>
+
+#include <openssl/aes.h>
+#include <openssl/cmac.h>
+
+namespace aidl::android::hardware::macsec {
+
+struct keys {
+ std::vector<uint8_t> keyId;
+ AES_KEY kekEncCtx;
+ AES_KEY kekDecCtx;
+ CMAC_CTX* ickCtx;
+ CMAC_CTX* cakCtx;
+};
+
+class MacsecPskPlugin : public BnMacsecPskPlugin {
+ public:
+ MacsecPskPlugin();
+ ~MacsecPskPlugin();
+ ndk::ScopedAStatus addTestKey(const std::vector<uint8_t>& keyId,
+ const std::vector<uint8_t>& CAK,
+ const std::vector<uint8_t>& CKN) override;
+ ndk::ScopedAStatus calcIcv(const std::vector<uint8_t>& keyId, const std::vector<uint8_t>& data,
+ std::vector<uint8_t>* out) override;
+
+ ndk::ScopedAStatus generateSak(const std::vector<uint8_t>& keyId,
+ const std::vector<uint8_t>& data, const int sakLength,
+ std::vector<uint8_t>* out);
+
+ ndk::ScopedAStatus wrapSak(const std::vector<uint8_t>& keyId, const std::vector<uint8_t>& sak,
+ std::vector<uint8_t>* out) override;
+
+ ndk::ScopedAStatus unwrapSak(const std::vector<uint8_t>& keyId, const std::vector<uint8_t>& sak,
+ std::vector<uint8_t>* out) override;
+
+ private:
+ std::vector<struct keys> mKeys;
+};
+} // namespace aidl::android::hardware::macsec
diff --git a/macsec/aidl/default/android.hardware.macsec.rc b/macsec/aidl/default/android.hardware.macsec.rc
new file mode 100644
index 0000000..0ff0e53
--- /dev/null
+++ b/macsec/aidl/default/android.hardware.macsec.rc
@@ -0,0 +1,3 @@
+service android.hardware.macsec /vendor/bin/hw/android.hardware.macsec-service
+ class early_hal
+ user nobody
diff --git a/macsec/aidl/default/android.hardware.macsec.xml b/macsec/aidl/default/android.hardware.macsec.xml
new file mode 100644
index 0000000..9cf9e5a
--- /dev/null
+++ b/macsec/aidl/default/android.hardware.macsec.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+ 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
+
+ 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.
+-->
+
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.macsec</name>
+ <version>1</version>
+ <interface>
+ <name>IMacsecPskPlugin</name>
+ <instance>default</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/macsec/aidl/default/fuzzer/fuzzer.cpp b/macsec/aidl/default/fuzzer/fuzzer.cpp
new file mode 100644
index 0000000..d912a67
--- /dev/null
+++ b/macsec/aidl/default/fuzzer/fuzzer.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * 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
+ *
+ * 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.
+ */
+
+#include <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "MacsecPskPlugin.h"
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::shared_ptr<aidl::android::hardware::macsec::MacsecPskPlugin> service =
+ ndk::SharedRefBase::make<aidl::android::hardware::macsec::MacsecPskPlugin>();
+ android::fuzzService(service->asBinder().get(), FuzzedDataProvider(data, size));
+
+ return 0;
+}
diff --git a/macsec/aidl/default/service.cpp b/macsec/aidl/default/service.cpp
new file mode 100644
index 0000000..faf3a09
--- /dev/null
+++ b/macsec/aidl/default/service.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * 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
+ *
+ * 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.
+ */
+
+#include "MacsecPskPlugin.h"
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+namespace android::hardware::macsec {
+
+using namespace std::string_literals;
+using ::aidl::android::hardware::macsec::MacsecPskPlugin;
+
+extern "C" int main() {
+ base::SetDefaultTag("MacsecPskPlugin");
+ base::SetMinimumLogSeverity(base::VERBOSE);
+
+ LOG(VERBOSE) << "Starting up...";
+ auto service = ndk::SharedRefBase::make<MacsecPskPlugin>();
+ const auto instance = MacsecPskPlugin::descriptor + "/default"s;
+ const auto status = AServiceManager_addService(service->asBinder().get(), instance.c_str());
+ CHECK_EQ(status, STATUS_OK) << "Failed to add service " << instance;
+ LOG(VERBOSE) << "Started successfully!";
+
+ ABinderProcess_joinThreadPool();
+ LOG(FATAL) << "MacsecPskPlugin exited unexpectedly!";
+ return EXIT_FAILURE;
+}
+} // namespace android::hardware::macsec