Skip to content

Commit

Permalink
[Android] Implement validate Device attestation API (#32236)
Browse files Browse the repository at this point in the history
* Implement Android validate Device attestation

* Restyled by google-java-format

* Restyled by clang-format

* Update VendorId to uint16_t

* Update from comment

* Restyled by whitespace

* Restyled by google-java-format

* Restyled by clang-format

* Update kotlin codestyle

* Modify type casting

* Restyled by clang-format

---------

Co-authored-by: Restyled.io <commits@restyled.io>
  • Loading branch information
2 people authored and pull[bot] committed Apr 11, 2024
1 parent 40a7885 commit 4ed205a
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.google.chip.chiptool.attestation
import android.util.Base64
import chip.devicecontroller.AttestationTrustStoreDelegate
import chip.devicecontroller.ChipDeviceController
import chip.devicecontroller.DeviceAttestation
import java.util.*

class ExampleAttestationTrustStoreDelegate(val chipDeviceController: ChipDeviceController) :
Expand All @@ -13,9 +14,7 @@ class ExampleAttestationTrustStoreDelegate(val chipDeviceController: ChipDeviceC
override fun getProductAttestationAuthorityCert(skid: ByteArray): ByteArray? {
return paaCerts
.map { Base64.decode(it, Base64.DEFAULT) }
.firstOrNull { cert ->
Arrays.equals(chipDeviceController.extractSkidFromPaaCert(cert), skid)
}
.firstOrNull { cert -> Arrays.equals(DeviceAttestation.extractSkidFromPaaCert(cert), skid) }
}

companion object {
Expand Down
17 changes: 12 additions & 5 deletions src/controller/java/AndroidOperationalCredentialsIssuer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ static CHIP_ERROR N2J_CSRInfo(JNIEnv * env, jbyteArray nonce, jbyteArray element

static CHIP_ERROR N2J_AttestationInfo(JNIEnv * env, jbyteArray challenge, jbyteArray nonce, jbyteArray elements,
jbyteArray elementsSignature, jbyteArray dac, jbyteArray pai, jbyteArray cd,
jbyteArray firmwareInfo, jobject & outAttestationInfo);
jbyteArray firmwareInfo, uint16_t vendorId, uint16_t productId, jobject & outAttestationInfo);

CHIP_ERROR AndroidOperationalCredentialsIssuer::Initialize(PersistentStorageDelegate & storage, AutoCommissioner * autoCommissioner,
jobject javaObjectRef)
Expand Down Expand Up @@ -271,9 +271,15 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::CallbackGenerateNOCChain(const B
JniReferences::GetInstance().N2J_ByteArray(env, firmwareInfoSpan.data(), static_cast<jint>(firmwareInfoSpan.size()),
javaFirmwareInfo);

chip::VendorId vendorId =
mAutoCommissioner->GetCommissioningParameters().GetRemoteVendorId().ValueOr(chip::VendorId::Unspecified);
uint16_t productId =
mAutoCommissioner->GetCommissioningParameters().GetRemoteProductId().ValueOr(0x0000); // 0x0000 is invalid product ID value.

jobject attestationInfo;
err = N2J_AttestationInfo(env, javaAttestationChallenge, javaAttestationNonce, javaAttestationElements,
javaAttestationElementsSignature, javaDAC, javaPAI, javaCD, javaFirmwareInfo, attestationInfo);
javaAttestationElementsSignature, javaDAC, javaPAI, javaCD, javaFirmwareInfo,
static_cast<uint16_t>(vendorId), productId, attestationInfo);
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to create AttestationInfo");
Expand Down Expand Up @@ -482,7 +488,7 @@ CHIP_ERROR N2J_CSRInfo(JNIEnv * env, jbyteArray nonce, jbyteArray elements, jbyt

CHIP_ERROR N2J_AttestationInfo(JNIEnv * env, jbyteArray challenge, jbyteArray nonce, jbyteArray elements,
jbyteArray elementsSignature, jbyteArray dac, jbyteArray pai, jbyteArray cd, jbyteArray firmwareInfo,
jobject & outAttestationInfo)
uint16_t vendorId, uint16_t productId, jobject & outAttestationInfo)
{
CHIP_ERROR err = CHIP_NO_ERROR;
jmethodID constructor;
Expand All @@ -492,11 +498,12 @@ CHIP_ERROR N2J_AttestationInfo(JNIEnv * env, jbyteArray challenge, jbyteArray no
SuccessOrExit(err);

env->ExceptionClear();
constructor = env->GetMethodID(infoClass, "<init>", "([B[B[B[B[B[B[B[B)V");
constructor = env->GetMethodID(infoClass, "<init>", "([B[B[B[B[B[B[B[BII)V");
VerifyOrExit(constructor != nullptr, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND);

outAttestationInfo =
(jobject) env->NewObject(infoClass, constructor, challenge, nonce, elements, elementsSignature, dac, pai, cd, firmwareInfo);
static_cast<jobject>(env->NewObject(infoClass, constructor, challenge, nonce, elements, elementsSignature, dac, pai, cd,
firmwareInfo, static_cast<jint>(vendorId), static_cast<jint>(productId)));

VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN);
exit:
Expand Down
2 changes: 2 additions & 0 deletions src/controller/java/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ shared_library("jni") {
"CHIPAttributeTLVValueDecoder.h",
"CHIPDeviceController-JNI.cpp",
"CHIPEventTLVValueDecoder.h",
"DeviceAttestation-JNI.cpp",
"DeviceAttestationDelegateBridge.cpp",
"DeviceAttestationDelegateBridge.h",
"GroupDeviceProxy.h",
Expand Down Expand Up @@ -455,6 +456,7 @@ android_library("java") {
"src/chip/devicecontroller/CommissioningWindowStatus.java",
"src/chip/devicecontroller/ConnectionFailureException.java",
"src/chip/devicecontroller/ControllerParams.java",
"src/chip/devicecontroller/DeviceAttestation.java",
"src/chip/devicecontroller/DeviceAttestationDelegate.java",
"src/chip/devicecontroller/DiscoveredDevice.java",
"src/chip/devicecontroller/ExtendableInvokeCallback.java",
Expand Down
32 changes: 0 additions & 32 deletions src/controller/java/CHIPDeviceController-JNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1260,38 +1260,6 @@ JNI_METHOD(jbyteArray, convertX509CertToMatterCert)
return outJbytes;
}

JNI_METHOD(jbyteArray, extractSkidFromPaaCert)
(JNIEnv * env, jobject self, jbyteArray paaCert)
{
uint32_t allocatedCertLength = chip::Credentials::kMaxCHIPCertLength;
chip::Platform::ScopedMemoryBuffer<uint8_t> outBuf;
jbyteArray outJbytes = nullptr;
JniByteArray paaCertBytes(env, paaCert);

CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(outBuf.Alloc(allocatedCertLength), err = CHIP_ERROR_NO_MEMORY);
{
MutableByteSpan outBytes(outBuf.Get(), allocatedCertLength);

err = chip::Crypto::ExtractSKIDFromX509Cert(paaCertBytes.byteSpan(), outBytes);
SuccessOrExit(err);

VerifyOrExit(chip::CanCastTo<uint32_t>(outBytes.size()), err = CHIP_ERROR_INTERNAL);

err = JniReferences::GetInstance().N2J_ByteArray(env, outBytes.data(), static_cast<jsize>(outBytes.size()), outJbytes);
SuccessOrExit(err);
}

exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to extract skid frome X509 cert. Err = %" CHIP_ERROR_FORMAT, err.Format());
JniReferences::GetInstance().ThrowError(env, sChipDeviceControllerExceptionCls, err);
}

return outJbytes;
}

JNI_METHOD(void, unpairDevice)(JNIEnv * env, jobject self, jlong handle, jlong deviceId)
{
chip::DeviceLayer::StackLock lock;
Expand Down
225 changes: 225 additions & 0 deletions src/controller/java/DeviceAttestation-JNI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
/*
* Copyright (c) 2024 Project CHIP Authors
* All rights reserved.
*
* 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.
*
*/

/**
* @file
* Implementation of JNI bridge for Device Attestation
*
*/
#include <lib/support/CHIPJNIError.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
#include <lib/support/SafeInt.h>

#include <credentials/CHIPCert.h>
#include <credentials/DeviceAttestationConstructor.h>
#include <credentials/attestation_verifier/DefaultDeviceAttestationVerifier.h>
#include <jni.h>

#define JNI_METHOD(RETURN, METHOD_NAME) \
extern "C" JNIEXPORT RETURN JNICALL Java_chip_devicecontroller_DeviceAttestation_##METHOD_NAME

void ThrowException(JNIEnv * env, CHIP_ERROR err);

JNI_METHOD(jbyteArray, extractSkidFromPaaCert)
(JNIEnv * env, jclass clazz, jbyteArray paaCert)
{
uint32_t allocatedCertLength = chip::Credentials::kMaxCHIPCertLength;
chip::Platform::ScopedMemoryBuffer<uint8_t> outBuf;
jbyteArray outJbytes = nullptr;
chip::JniByteArray paaCertBytes(env, paaCert);

CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(outBuf.Alloc(allocatedCertLength), err = CHIP_ERROR_NO_MEMORY);
{
chip::MutableByteSpan outBytes(outBuf.Get(), allocatedCertLength);

err = chip::Crypto::ExtractSKIDFromX509Cert(paaCertBytes.byteSpan(), outBytes);
SuccessOrExit(err);

VerifyOrExit(chip::CanCastTo<uint32_t>(outBytes.size()), err = CHIP_ERROR_INTERNAL);

err =
chip::JniReferences::GetInstance().N2J_ByteArray(env, outBytes.data(), static_cast<jsize>(outBytes.size()), outJbytes);
SuccessOrExit(err);
}

exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to extract skid frome X509 cert. Err = %" CHIP_ERROR_FORMAT, err.Format());
ThrowException(env, err);
}

return outJbytes;
}

JNI_METHOD(jbyteArray, extractAkidFromPaiCert)
(JNIEnv * env, jclass clazz, jbyteArray paiCert)
{
uint32_t allocatedCertLength = chip::Credentials::kMaxCHIPCertLength;
chip::Platform::ScopedMemoryBuffer<uint8_t> outBuf;
jbyteArray outJbytes = nullptr;
chip::JniByteArray paiCertBytes(env, paiCert);

CHIP_ERROR err = CHIP_NO_ERROR;
VerifyOrExit(outBuf.Alloc(allocatedCertLength), err = CHIP_ERROR_NO_MEMORY);
{
chip::MutableByteSpan outBytes(outBuf.Get(), allocatedCertLength);

err = chip::Crypto::ExtractAKIDFromX509Cert(paiCertBytes.byteSpan(), outBytes);
SuccessOrExit(err);

VerifyOrExit(chip::CanCastTo<uint32_t>(outBytes.size()), err = CHIP_ERROR_INTERNAL);

err =
chip::JniReferences::GetInstance().N2J_ByteArray(env, outBytes.data(), static_cast<jsize>(outBytes.size()), outJbytes);
SuccessOrExit(err);
}

exit:
if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to extract akid frome X509 cert. Err = %" CHIP_ERROR_FORMAT, err.Format());
ThrowException(env, err);
}

return outJbytes;
}

JNI_METHOD(void, validateAttestationInfo)
(JNIEnv * env, jclass clazz, jint vendorId, jint productId, jbyteArray paaCert, jbyteArray paiCert, jbyteArray dacCert,
jbyteArray attestationElements)
{
chip::Credentials::AttestationVerificationResult attestationError = chip::Credentials::AttestationVerificationResult::kSuccess;
CHIP_ERROR err = CHIP_NO_ERROR;

VerifyOrExit(paaCert != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(paiCert != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrExit(dacCert != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);

{
chip::JniByteArray paaCertBytes(env, paaCert);
chip::JniByteArray paiCertBytes(env, paiCert);
chip::JniByteArray dacCertBytes(env, dacCert);

chip::Crypto::AttestationCertVidPid paaVidPid;
chip::Crypto::AttestationCertVidPid paiVidPid;
chip::Crypto::AttestationCertVidPid dacVidPid;

uint8_t skidBuf[chip::Crypto::kAuthorityKeyIdentifierLength];
chip::MutableByteSpan paaSKID(skidBuf);

chip::Crypto::CertificateChainValidationResult chainValidationResult;

err = chip::Crypto::VerifyAttestationCertificateFormat(paiCertBytes.byteSpan(), chip::Crypto::AttestationCertType::kPAI);
VerifyOrExit(err == CHIP_NO_ERROR,
ChipLogError(Controller, "Verify PAI Attestation Cert format Error! : %" CHIP_ERROR_FORMAT, err.Format()));

err = chip::Crypto::VerifyAttestationCertificateFormat(dacCertBytes.byteSpan(), chip::Crypto::AttestationCertType::kDAC);
VerifyOrExit(err == CHIP_NO_ERROR,
ChipLogError(Controller, "Verify DAC Attestation Cert format Error! : %" CHIP_ERROR_FORMAT, err.Format()));

err = chip::Crypto::ExtractVIDPIDFromX509Cert(paaCertBytes.byteSpan(), paaVidPid);
VerifyOrExit(err == CHIP_NO_ERROR,
ChipLogError(Controller, "Extract VID, PID from PAA Error! : %" CHIP_ERROR_FORMAT, err.Format()));

err = chip::Crypto::ExtractVIDPIDFromX509Cert(paiCertBytes.byteSpan(), paiVidPid);
VerifyOrExit(err == CHIP_NO_ERROR,
ChipLogError(Controller, "Extract VID, PID from PAI Error! : %" CHIP_ERROR_FORMAT, err.Format()));

err = chip::Crypto::ExtractVIDPIDFromX509Cert(dacCertBytes.byteSpan(), dacVidPid);
VerifyOrExit(err == CHIP_NO_ERROR,
ChipLogError(Controller, "Extract VID, PID from DAC Error! : %" CHIP_ERROR_FORMAT, err.Format()));

if (paaVidPid.mVendorId.HasValue())
{
VerifyOrExit(paaVidPid.mVendorId == paiVidPid.mVendorId,
attestationError = chip::Credentials::AttestationVerificationResult::kPaiVendorIdMismatch);
}

VerifyOrExit(!paaVidPid.mProductId.HasValue(),
attestationError = chip::Credentials::AttestationVerificationResult::kPaaFormatInvalid);

err = chip::Crypto::ValidateCertificateChain(
paaCertBytes.byteSpan().data(), paaCertBytes.byteSpan().size(), paiCertBytes.byteSpan().data(),
paiCertBytes.byteSpan().size(), dacCertBytes.byteSpan().data(), dacCertBytes.byteSpan().size(), chainValidationResult);
VerifyOrExit(err == CHIP_NO_ERROR,
attestationError = static_cast<chip::Credentials::AttestationVerificationResult>(chainValidationResult));

err = chip::Crypto::ExtractSKIDFromX509Cert(paaCertBytes.byteSpan(), paaSKID);
VerifyOrExit(err == CHIP_NO_ERROR, attestationError = chip::Credentials::AttestationVerificationResult::kPaaFormatInvalid);
VerifyOrExit(paaSKID.size() == chip::Crypto::kAuthorityKeyIdentifierLength,
attestationError = chip::Credentials::AttestationVerificationResult::kPaaFormatInvalid);
}

{
VerifyOrExit(attestationElements != nullptr, err = CHIP_ERROR_INVALID_ARGUMENT);

chip::JniByteArray attestationElementsBytes(env, attestationElements);

chip::ByteSpan certificationDeclarationSpan;
chip::ByteSpan attestationNonceSpan;
uint32_t timestampDeconstructed;
chip::ByteSpan firmwareInfoSpan;
chip::Credentials::DeviceAttestationVendorReservedDeconstructor vendorReserved;
chip::ByteSpan certificationDeclarationPayload;

const chip::Credentials::AttestationTrustStore * testingRootStore = chip::Credentials::GetTestAttestationTrustStore();
chip::Credentials::DeviceAttestationVerifier * dacVertifier = chip::Credentials::GetDefaultDACVerifier(testingRootStore);

err = chip::Credentials::DeconstructAttestationElements(attestationElementsBytes.byteSpan(), certificationDeclarationSpan,
attestationNonceSpan, timestampDeconstructed, firmwareInfoSpan,
vendorReserved);

VerifyOrExit(err == CHIP_NO_ERROR,
attestationError = chip::Credentials::AttestationVerificationResult::kAttestationElementsMalformed);

attestationError =
dacVertifier->ValidateCertificationDeclarationSignature(certificationDeclarationSpan, certificationDeclarationPayload);
VerifyOrExit(attestationError == chip::Credentials::AttestationVerificationResult::kSuccess, err = CHIP_ERROR_INTERNAL);
}

exit:
if (err == CHIP_NO_ERROR && attestationError != chip::Credentials::AttestationVerificationResult::kSuccess)
{
err = CHIP_ERROR_INTERNAL;
}

if (err != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to validate Attestation Info. Err = %u, %" CHIP_ERROR_FORMAT,
static_cast<uint16_t>(attestationError), err.Format());
ThrowException(env, err);
}
}

void ThrowException(JNIEnv * env, CHIP_ERROR err)
{
jclass controllerExceptionCls;
CHIP_ERROR classRefErr = chip::JniReferences::GetInstance().GetLocalClassRef(
env, "chip/devicecontroller/ChipDeviceControllerException", controllerExceptionCls);

if (classRefErr != CHIP_NO_ERROR)
{
ChipLogError(Controller, "Failed to GetLocalClassRef: %" CHIP_ERROR_FORMAT, classRefErr.Format());
return;
}

chip::JniReferences::GetInstance().ThrowError(env, controllerExceptionCls, err);
}
7 changes: 5 additions & 2 deletions src/controller/java/DeviceAttestationDelegateBridge.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ CHIP_ERROR N2J_AttestationInfo(JNIEnv * env, const chip::Credentials::DeviceAtte
const ByteSpan DAC = info.dacDerBuffer();
const ByteSpan PAI = info.paiDerBuffer();
const Optional<ByteSpan> certificationDeclarationSpan = info.cdBuffer();
uint16_t vendorId = info.BasicInformationVendorId();
uint16_t productId = info.BasicInformationProductId();

err = JniReferences::GetInstance().GetLocalClassRef(env, "chip/devicecontroller/AttestationInfo", infoClass);
SuccessOrExit(err);

env->ExceptionClear();
constructor = env->GetMethodID(infoClass, "<init>", "([B[B[B)V");
constructor = env->GetMethodID(infoClass, "<init>", "([B[B[BII)V");
VerifyOrExit(constructor != nullptr, err = CHIP_JNI_ERROR_METHOD_NOT_FOUND);

err = JniReferences::GetInstance().N2J_ByteArray(env, DAC.data(), static_cast<jsize>(DAC.size()), javaDAC);
Expand All @@ -54,7 +56,8 @@ CHIP_ERROR N2J_AttestationInfo(JNIEnv * env, const chip::Credentials::DeviceAtte
static_cast<jsize>(certificationDeclarationSpan.Value().size()), javaCD);
SuccessOrExit(err);
}
outAttestationInfo = (jobject) env->NewObject(infoClass, constructor, javaDAC, javaPAI, javaCD);
outAttestationInfo = (jobject) env->NewObject(infoClass, constructor, javaDAC, javaPAI, javaCD, static_cast<jint>(vendorId),
static_cast<jint>(productId));
VerifyOrExit(!env->ExceptionCheck(), err = CHIP_JNI_ERROR_EXCEPTION_THROWN);
exit:
return err;
Expand Down
Loading

0 comments on commit 4ed205a

Please sign in to comment.