blob: e1fb2b21c9776ff6e2241d664b4c56304d39d8a1 [file]
/*
*
* Copyright (c) 2026 Project CHIP 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
*
* 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.
*/
// The feature depends only on ESP-TEE secure storage: keys are generated there and all
// private-key operations (CSR self-signature, CASE signatures) use the raw secure-storage
// ECDSA sign primitive. It deliberately avoids the mbedTLS TEE-pk wrapper. These sources are
// compiled only when chip_enable_esp32_tee (set from CONFIG_SECURE_ENABLE_TEE) is set.
#include <platform/ESP32/ESP32TEEOpKey.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <esp_idf_version.h>
#include <esp_tee_sec_storage.h>
#include <cstring>
namespace chip {
namespace DeviceLayer {
namespace Internal {
namespace {
constexpr size_t kP256FieldLength = 32; // secp256r1 coordinate / scalar length
esp_tee_sec_storage_key_cfg_t MakeKeyCfg(const char * keyId)
{
esp_tee_sec_storage_key_cfg_t cfg = {};
cfg.id = keyId;
cfg.type = ESP_SEC_STG_KEY_ECDSA_SECP256R1;
cfg.flags = SEC_STORAGE_FLAG_NONE;
return cfg;
}
} // namespace
CHIP_ERROR ESP32TEEOpKeyGenerate(const char * keyId)
{
VerifyOrReturnError(keyId != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
// esp_tee_sec_storage_gen_key rejects an already-populated id, so clear any existing key
// first. This makes the primitive idempotent (honoring the "overwrites" contract) and
// recovers a slot left populated by a reboot-aborted fail-safe.
ReturnErrorOnFailure(ESP32TEEOpKeyRemove(keyId));
esp_tee_sec_storage_key_cfg_t cfg = MakeKeyCfg(keyId);
esp_err_t err = esp_tee_sec_storage_gen_key(&cfg);
VerifyOrReturnError(err == ESP_OK, CHIP_ERROR_INTERNAL, ChipLogError(Crypto, "gen_key(%s) failed: %d", keyId, err));
return CHIP_NO_ERROR;
}
CHIP_ERROR ESP32TEEOpKeyGetPublicKey(const char * keyId, Crypto::P256PublicKey & outPublicKey)
{
VerifyOrReturnError(keyId != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
esp_tee_sec_storage_key_cfg_t cfg = MakeKeyCfg(keyId);
esp_tee_sec_storage_ecdsa_pubkey_t pk = {};
esp_err_t err = esp_tee_sec_storage_ecdsa_get_pubkey(&cfg, &pk);
VerifyOrReturnError(err == ESP_OK, CHIP_ERROR_INVALID_KEY_ID, ChipLogError(Crypto, "get_pubkey(%s) failed: %d", keyId, err));
// P256PublicKey holds the uncompressed point: 0x04 || X || Y.
uint8_t * dst = outPublicKey.Bytes();
dst[0] = 0x04;
memcpy(dst + 1, pk.pub_x, kP256FieldLength);
memcpy(dst + 1 + kP256FieldLength, pk.pub_y, kP256FieldLength);
return CHIP_NO_ERROR;
}
namespace {
// Minimal DER INTEGER encoding of a 32-byte big-endian value into @p out (needs >= 35 bytes:
// tag + length + optional 0x00 sign-padding + up to 32 magnitude bytes). Strips leading zero
// bytes and prepends 0x00 when the top bit is set, so the value stays positive.
size_t EncodeDerInteger(const uint8_t * val, uint8_t * out)
{
size_t i = 0;
while ((i < kP256FieldLength - 1) && (val[i] == 0))
{
i++;
}
const size_t magLen = kP256FieldLength - i;
const bool pad = (val[i] & 0x80) != 0;
out[0] = 0x02; // INTEGER
out[1] = static_cast<uint8_t>(magLen + (pad ? 1 : 0));
size_t p = 2;
if (pad)
{
out[p++] = 0x00;
}
memcpy(out + p, val + i, magLen);
return p + magLen;
}
} // namespace
// Builds a PKCS#10 CSR without the mbedTLS pk wrapper: the public key comes from the TEE and
// the CertificationRequestInfo is signed with the same raw secure-storage ECDSA primitive used
// for CASE. For a P-256 key every ASN.1 element except the key and signature is fixed-shape.
CHIP_ERROR ESP32TEEOpKeyNewCSR(const char * keyId, MutableByteSpan & outCsr)
{
VerifyOrReturnError(keyId != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
Crypto::P256PublicKey pub; // uncompressed: 0x04 || X || Y (65 bytes)
ReturnErrorOnFailure(ESP32TEEOpKeyGetPublicKey(keyId, pub));
VerifyOrReturnError(pub.Length() == 1 + 2 * kP256FieldLength, CHIP_ERROR_INTERNAL);
// SubjectPublicKeyInfo for P-256: fixed 26-byte prefix + the 65-byte public key.
static const uint8_t kSpkiPrefix[] = { 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01,
0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00 };
// Subject Name "O=CSR": a non-empty placeholder (x509 parsers reject an empty subject). The
// commissioner uses only the embedded public key from the CSR, not the subject.
static const uint8_t kSubject[] = { 0x30, 0x0e, 0x31, 0x0c, 0x30, 0x0a, 0x06, 0x03,
0x55, 0x04, 0x0a, 0x0c, 0x03, 0x43, 0x53, 0x52 };
static const uint8_t kVersion[] = { 0x02, 0x01, 0x00 }; // INTEGER 0
static const uint8_t kAttrs[] = { 0xa0, 0x00 }; // attributes [0] IMPLICIT, empty
static const uint8_t kSigAlg[] = {
0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02
}; // ecdsa-with-SHA256
// CertificationRequestInfo body (its content, without the outer SEQUENCE header).
uint8_t body[128];
size_t n = 0;
memcpy(body + n, kVersion, sizeof(kVersion));
n += sizeof(kVersion);
memcpy(body + n, kSubject, sizeof(kSubject));
n += sizeof(kSubject);
memcpy(body + n, kSpkiPrefix, sizeof(kSpkiPrefix));
n += sizeof(kSpkiPrefix);
memcpy(body + n, pub.ConstBytes(), pub.Length());
n += pub.Length();
memcpy(body + n, kAttrs, sizeof(kAttrs));
n += sizeof(kAttrs);
VerifyOrReturnError(n < 0x80, CHIP_ERROR_INTERNAL); // fixed size (112) fits a 1-byte length
uint8_t cri[130];
cri[0] = 0x30;
cri[1] = static_cast<uint8_t>(n);
memcpy(cri + 2, body, n);
const size_t criLen = n + 2;
// Sign the CertificationRequestInfo inside the TEE.
Crypto::P256ECDSASignature rawSig;
ReturnErrorOnFailure(ESP32TEEOpKeySign(keyId, ByteSpan(cri, criLen), rawSig));
VerifyOrReturnError(rawSig.Length() == 2 * kP256FieldLength, CHIP_ERROR_INTERNAL);
// signature: BIT STRING wrapping ECDSA-Sig-Value ::= SEQUENCE { INTEGER r, INTEGER s }.
// A DER INTEGER for a 32-byte value can be up to 35 bytes (tag + len + sign pad + 32).
uint8_t rEnc[kP256FieldLength + 3];
uint8_t sEnc[kP256FieldLength + 3];
const size_t rl = EncodeDerInteger(rawSig.ConstBytes(), rEnc);
const size_t sl = EncodeDerInteger(rawSig.ConstBytes() + kP256FieldLength, sEnc);
const size_t seqLen = rl + sl;
VerifyOrReturnError(seqLen < 0x80, CHIP_ERROR_INTERNAL);
uint8_t sig[8 + 2 * (kP256FieldLength + 3)];
size_t b = 0;
sig[b++] = 0x03; // BIT STRING
sig[b++] = static_cast<uint8_t>(seqLen + 2 + 1); // SEQUENCE header (2) + unused-bits byte (1)
sig[b++] = 0x00; // 0 unused bits
sig[b++] = 0x30; // SEQUENCE (ECDSA-Sig-Value)
sig[b++] = static_cast<uint8_t>(seqLen);
memcpy(sig + b, rEnc, rl);
b += rl;
memcpy(sig + b, sEnc, sl);
b += sl;
// CertificationRequest ::= SEQUENCE { CertificationRequestInfo, sigAlg, signature }.
const size_t contentLen = criLen + sizeof(kSigAlg) + b;
VerifyOrReturnError(contentLen <= 0xff, CHIP_ERROR_INTERNAL);
const size_t hdrLen = (contentLen < 0x80) ? 2 : 3; // 2-byte length prefix once content >= 128
VerifyOrReturnError(outCsr.size() >= hdrLen + contentLen, CHIP_ERROR_BUFFER_TOO_SMALL);
uint8_t * p = outCsr.data();
*p++ = 0x30;
if (hdrLen == 3)
{
*p++ = 0x81;
}
*p++ = static_cast<uint8_t>(contentLen);
memcpy(p, cri, criLen);
p += criLen;
memcpy(p, kSigAlg, sizeof(kSigAlg));
p += sizeof(kSigAlg);
memcpy(p, sig, b);
outCsr.reduce_size(hdrLen + contentLen);
return CHIP_NO_ERROR;
}
CHIP_ERROR ESP32TEEOpKeySign(const char * keyId, ByteSpan message, Crypto::P256ECDSASignature & outSignature)
{
VerifyOrReturnError(keyId != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
uint8_t digest[Crypto::kSHA256_Hash_Length] = {};
ReturnErrorOnFailure(Crypto::Hash_SHA256(message.data(), message.size(), digest));
esp_tee_sec_storage_key_cfg_t cfg = MakeKeyCfg(keyId);
esp_tee_sec_storage_ecdsa_sign_t sig = {};
esp_err_t err = esp_tee_sec_storage_ecdsa_sign(&cfg, digest, sizeof(digest), &sig);
VerifyOrReturnError(err == ESP_OK, CHIP_ERROR_INTERNAL, ChipLogError(Crypto, "ecdsa_sign(%s) failed: %d", keyId, err));
// P256ECDSASignature is the raw R || S (2 * 32 bytes). IDF v6.0 merged the separate
// sign_r/sign_s fields into a single signature[] holding R||S packed at the field length.
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
memcpy(outSignature.Bytes(), sig.signature, 2 * kP256FieldLength);
#else
memcpy(outSignature.Bytes(), sig.sign_r, kP256FieldLength);
memcpy(outSignature.Bytes() + kP256FieldLength, sig.sign_s, kP256FieldLength);
#endif
return outSignature.SetLength(2 * kP256FieldLength);
}
CHIP_ERROR ESP32TEEOpKeyRemove(const char * keyId)
{
VerifyOrReturnError(keyId != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
esp_err_t err = esp_tee_sec_storage_clear_key(keyId);
// Treat "not present" as success so Remove is idempotent.
VerifyOrReturnError(err == ESP_OK || err == ESP_ERR_NOT_FOUND, CHIP_ERROR_INTERNAL,
ChipLogError(Crypto, "clear_key(%s) failed: %d", keyId, err));
return CHIP_NO_ERROR;
}
CHIP_ERROR ESP32TEEOpKeySelfTest()
{
static const char * kId = "mtr-op-selftest";
CHIP_ERROR err = CHIP_NO_ERROR;
LogErrorOnFailure(ESP32TEEOpKeyRemove(kId)); // start clean, best-effort
Crypto::P256PublicKey pub;
uint8_t csrBuf[Crypto::kMIN_CSR_Buffer_Size];
MutableByteSpan csr(csrBuf);
Crypto::P256PublicKey csrPub;
Crypto::P256ECDSASignature sig;
const uint8_t msg[] = "esp-tee-op-key-selftest";
VerifyOrExit((err = ESP32TEEOpKeyGenerate(kId)) == CHIP_NO_ERROR, ChipLogError(Crypto, "selftest: gen failed"));
VerifyOrExit((err = ESP32TEEOpKeyGetPublicKey(kId, pub)) == CHIP_NO_ERROR, ChipLogError(Crypto, "selftest: pubkey failed"));
// Raw TEE sign path (this is what CASE / SignWithOpKeypair uses) — test it first.
VerifyOrExit((err = ESP32TEEOpKeySign(kId, ByteSpan(msg, sizeof(msg)), sig)) == CHIP_NO_ERROR,
ChipLogError(Crypto, "selftest: sign failed"));
VerifyOrExit((err = pub.ECDSA_validate_msg_signature(msg, sizeof(msg), sig)) == CHIP_NO_ERROR,
ChipLogError(Crypto, "selftest: signature verify failed"));
ChipLogProgress(Crypto, "selftest: sign+verify OK");
VerifyOrExit((err = ESP32TEEOpKeyNewCSR(kId, csr)) == CHIP_NO_ERROR, ChipLogError(Crypto, "selftest: CSR failed"));
// Verifies the CSR self-signature AND recovers the embedded public key.
VerifyOrExit((err = Crypto::VerifyCertificateSigningRequest(csr.data(), csr.size(), csrPub)) == CHIP_NO_ERROR,
ChipLogError(Crypto, "selftest: CSR verify failed"));
VerifyOrExit(csrPub.Matches(pub), err = CHIP_ERROR_INTERNAL; ChipLogError(Crypto, "selftest: CSR pubkey mismatch"));
ChipLogProgress(Crypto, "TEE operational-key self-test PASSED (gen/pubkey/sign/CSR all verified in TEE)");
exit:
// Cleanup is part of the "every step verified" contract: if the checks above passed,
// a failure to delete the throwaway key must fail the self-test rather than report PASSED.
CHIP_ERROR cleanupErr = ESP32TEEOpKeyRemove(kId);
if (err == CHIP_NO_ERROR)
{
err = cleanupErr;
}
else
{
LogErrorOnFailure(cleanupErr);
}
return err;
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip