Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement android device attestation #198

Merged
merged 10 commits into from
Mar 7, 2024
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
18 changes: 12 additions & 6 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,11 @@ 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);
outAttestationInfo = (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 @@ -63,6 +63,7 @@ shared_library("jni") {
"CHIPAttributeTLVValueDecoder.h",
"CHIPDeviceController-JNI.cpp",
"CHIPEventTLVValueDecoder.h",
"DeviceAttestation-JNI.cpp",
"DeviceAttestationDelegateBridge.cpp",
"DeviceAttestationDelegateBridge.h",
"GroupDeviceProxy.h",
Expand Down Expand Up @@ -457,6 +458,7 @@ android_library("java") {
"src/chip/devicecontroller/ChipDeviceControllerException.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/GetConnectedDeviceCallbackJni.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 @@ -1238,38 +1238,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
Loading