Skip to content

Commit

Permalink
Enable TV Casting App Example to using Hardware-backed keys For DAC (#…
Browse files Browse the repository at this point in the history
…26022)

* Enable TV Casting App Example to using Hardware-backed keys For DAC (#142)

It is best practice for private keys to not be available to the application layer in plain form. In
fact you can generate keys within the secure element on device, or TrustZone, that is not extractable
in plain form. In order to support such best practices, the change addresses this by modifying the DAC Provider's
SignWithDeviceAttestationKey function that is provided by the example tv castin app to:

- For Android, ask the Java application layer to do the signing via the Android Keystore.
- For iOS, request the SecKeyRef for the private key as part of setting the DAC provider and hence allowing the SDK
 to just sign using the Security APIs.

* Update JNI Interface in tv-app to allow app to sign messages for DAC stored in Android KeyStore

In an effort to leverage hardware keys for DAC, the JNI layer was updated to ask the JVM layer to
sign message by leveraging the Android KeyStore instead of requesting to the raw private key bytes
only to sign it within the c++ layer. This adds a level of security to the device attestation process.
  • Loading branch information
rawadhilal88 authored and pull[bot] committed Sep 29, 2023
1 parent c0ab468 commit 2c05385
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 178 deletions.
79 changes: 42 additions & 37 deletions examples/tv-app/android/java/JNIDACProvider.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,51 @@ JNIDACProvider::JNIDACProvider(jobject provider)
env->ExceptionClear();
}

mGetDeviceAttestationCertPrivateKeyMethod = env->GetMethodID(JNIDACProviderClass, "GetDeviceAttestationCertPrivateKey", "()[B");
if (mGetDeviceAttestationCertPrivateKeyMethod == nullptr)
mSignWithDeviceAttestationKeyMethod = env->GetMethodID(JNIDACProviderClass, "SignWithDeviceAttestationKey", "([B)[B");
if (mSignWithDeviceAttestationKeyMethod == nullptr)
{
ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetDeviceAttestationCertPrivateKey' method");
ChipLogError(Zcl, "Failed to access JNIDACProvider 'SignWithDeviceAttestationKey' method");
env->ExceptionClear();
}
}

mGetDeviceAttestationCertPublicKeyKeyMethod =
env->GetMethodID(JNIDACProviderClass, "GetDeviceAttestationCertPublicKeyKey", "()[B");
if (mGetDeviceAttestationCertPublicKeyKeyMethod == nullptr)
CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, MutableByteSpan & out_buffer)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnLogError(mJNIDACProviderObject != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(method != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(env != nullptr, CHIP_JNI_ERROR_NO_ENV);

jbyteArray outArray = (jbyteArray) env->CallObjectMethod(mJNIDACProviderObject, method);
if (env->ExceptionCheck())
{
ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetDeviceAttestationCertPublicKeyKey' method");
ChipLogError(Zcl, "Java exception in get Method");
env->ExceptionDescribe();
env->ExceptionClear();
return CHIP_ERROR_INCORRECT_STATE;
}

if (outArray == nullptr || env->GetArrayLength(outArray) <= 0)
{
out_buffer.reduce_size(0);
return CHIP_NO_ERROR;
}

JniByteArray JniOutArray(env, outArray);
return CopySpanToMutableSpan(JniOutArray.byteSpan(), out_buffer);
}

CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, MutableByteSpan & out_buffer)
CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, const ByteSpan & in_buffer, MutableByteSpan & out_buffer)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnLogError(mJNIDACProviderObject != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(method != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(env != nullptr, CHIP_JNI_ERROR_NO_ENV);

jbyteArray outArray = (jbyteArray) env->CallObjectMethod(mJNIDACProviderObject, method);
jbyteArray in_buffer_jbyteArray = env->NewByteArray((jsize)(in_buffer.size()));
env->SetByteArrayRegion(in_buffer_jbyteArray, 0, (int) in_buffer.size(), reinterpret_cast<const jbyte *>(in_buffer.data()));

jbyteArray outArray = (jbyteArray) env->CallObjectMethod(mJNIDACProviderObject, method, in_buffer_jbyteArray);
if (env->ExceptionCheck())
{
ChipLogError(Zcl, "Java exception in get Method");
Expand All @@ -101,6 +122,8 @@ CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, MutableByteSpan
return CHIP_ERROR_INCORRECT_STATE;
}

env->DeleteLocalRef(in_buffer_jbyteArray);

if (outArray == nullptr || env->GetArrayLength(outArray) <= 0)
{
out_buffer.reduce_size(0);
Expand Down Expand Up @@ -135,38 +158,20 @@ CHIP_ERROR JNIDACProvider::GetProductAttestationIntermediateCert(MutableByteSpan
return GetJavaByteByMethod(mGetProductAttestationIntermediateCertMethod, out_pai_buffer);
}

// TODO: This should be moved to a method of P256Keypair
CHIP_ERROR LoadKeypairFromRaw(ByteSpan private_key, ByteSpan public_key, Crypto::P256Keypair & keypair)
{
Crypto::P256SerializedKeypair serialized_keypair;
ReturnErrorOnFailure(serialized_keypair.SetLength(private_key.size() + public_key.size()));
memcpy(serialized_keypair.Bytes(), public_key.data(), public_key.size());
memcpy(serialized_keypair.Bytes() + public_key.size(), private_key.data(), private_key.size());
return keypair.Deserialize(serialized_keypair);
}

CHIP_ERROR JNIDACProvider::SignWithDeviceAttestationKey(const ByteSpan & message_to_sign, MutableByteSpan & out_signature_buffer)
{
ChipLogProgress(Zcl, "Received SignWithDeviceAttestationKey");
Crypto::P256ECDSASignature signature;
Crypto::P256Keypair keypair;
uint8_t mAsn1SignatureBytes[73];

VerifyOrReturnError(IsSpanUsable(out_signature_buffer), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSpanUsable(message_to_sign), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(out_signature_buffer.size() >= signature.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
MutableByteSpan asn1_signature_buffer(mAsn1SignatureBytes, sizeof(mAsn1SignatureBytes));

uint8_t privateKeyBuf[Crypto::kP256_PrivateKey_Length];
MutableByteSpan privateKeyBufSpan(privateKeyBuf);
ReturnErrorOnFailure(GetJavaByteByMethod(mGetDeviceAttestationCertPrivateKeyMethod, privateKeyBufSpan));

uint8_t publicKeyBuf[Crypto::kP256_PublicKey_Length];
MutableByteSpan publicKeyBufSpan(publicKeyBuf);
ReturnErrorOnFailure(GetJavaByteByMethod(mGetDeviceAttestationCertPublicKeyKeyMethod, publicKeyBufSpan));

// In a non-exemplary implementation, the public key is not needed here. It is used here merely because
// Crypto::P256Keypair is only (currently) constructable from raw keys if both private/public keys are present.
ReturnErrorOnFailure(LoadKeypairFromRaw(privateKeyBufSpan, publicKeyBufSpan, keypair));
ReturnErrorOnFailure(keypair.ECDSA_sign_msg(message_to_sign.data(), message_to_sign.size(), signature));
CHIP_ERROR error = GetJavaByteByMethod(mSignWithDeviceAttestationKeyMethod, message_to_sign, asn1_signature_buffer);
if (error != CHIP_NO_ERROR)
{
ChipLogProgress(Zcl, "SignWithDeviceAttestationKey failed");
return error;
}

return CopySpanToMutableSpan(ByteSpan{ signature.ConstBytes(), signature.Length() }, out_signature_buffer);
return chip::Crypto::EcdsaAsn1SignatureToRaw(32, ByteSpan(asn1_signature_buffer.data(), asn1_signature_buffer.size()),
out_signature_buffer);
}
4 changes: 2 additions & 2 deletions examples/tv-app/android/java/JNIDACProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,11 @@ class JNIDACProvider : public chip::Credentials::DeviceAttestationCredentialsPro

private:
CHIP_ERROR GetJavaByteByMethod(jmethodID method, chip::MutableByteSpan & out_buffer);
CHIP_ERROR GetJavaByteByMethod(jmethodID method, const chip::ByteSpan & in_buffer, chip::MutableByteSpan & out_buffer);
jobject mJNIDACProviderObject = nullptr;
jmethodID mGetCertificationDeclarationMethod = nullptr;
jmethodID mGetFirmwareInformationMethod = nullptr;
jmethodID mGetDeviceAttestationCertMethod = nullptr;
jmethodID mGetProductAttestationIntermediateCertMethod = nullptr;
jmethodID mGetDeviceAttestationCertPrivateKeyMethod = nullptr;
jmethodID mGetDeviceAttestationCertPublicKeyKeyMethod = nullptr;
jmethodID mSignWithDeviceAttestationKeyMethod = nullptr;
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ public interface DACProvider {

byte[] GetProductAttestationIntermediateCert();

byte[] GetDeviceAttestationCertPrivateKey();

byte[] GetDeviceAttestationCertPublicKeyKey();
/**
* Sign a mesage with the device attestation key.
*
* <p>The signature should be a SHA256withECDSA Signature that's returned in the ECDSA X9.62 Asn1
* format. This is the default behavior when using java.security.Signature with an EC P-256 curve.
*
* @param message The message to sign
* @return The signature in ECDSA X9.62 Asn1 format.
*/
byte[] SignWithDeviceAttestationKey(byte[] message);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
package com.matter.tv.server.tvapp;

import android.util.Base64;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPrivateKeySpec;

public class DACProviderStub implements DACProvider {

Expand Down Expand Up @@ -46,12 +54,29 @@ public byte[] GetProductAttestationIntermediateCert() {
}

@Override
public byte[] GetDeviceAttestationCertPrivateKey() {
return Base64.decode(kDevelopmentDAC_PrivateKey_FFF1_8001, Base64.DEFAULT);
}
public byte[] SignWithDeviceAttestationKey(byte[] message) {

@Override
public byte[] GetDeviceAttestationCertPublicKeyKey() {
return Base64.decode(kDevelopmentDAC_PublicKey_FFF1_8001, Base64.DEFAULT);
try {
byte[] privateKeyBytes = Base64.decode(kDevelopmentDAC_PrivateKey_FFF1_8001, Base64.DEFAULT);

AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("EC");
algorithmParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algorithmParameters.getParameterSpec(ECParameterSpec.class);
ECPrivateKeySpec ecPrivateKeySpec =
new ECPrivateKeySpec(new BigInteger(1, privateKeyBytes), parameterSpec);

KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey privateKey = keyFactory.generatePrivate(ecPrivateKeySpec);

Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initSign(privateKey);

signature.update(message);

return signature.sign();

} catch (Exception e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

import android.util.Base64;
import com.chip.casting.DACProvider;
import java.math.BigInteger;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPrivateKeySpec;

public class DACProviderStub implements DACProvider {

Expand Down Expand Up @@ -47,12 +55,29 @@ public byte[] GetProductAttestationIntermediateCert() {
}

@Override
public byte[] GetDeviceAttestationCertPrivateKey() {
return Base64.decode(kDevelopmentDAC_PrivateKey_FFF1_8001, Base64.DEFAULT);
}
public byte[] SignWithDeviceAttestationKey(byte[] message) {

@Override
public byte[] GetDeviceAttestationCertPublicKeyKey() {
return Base64.decode(kDevelopmentDAC_PublicKey_FFF1_8001, Base64.DEFAULT);
try {
byte[] privateKeyBytes = Base64.decode(kDevelopmentDAC_PrivateKey_FFF1_8001, Base64.DEFAULT);

AlgorithmParameters algorithmParameters = AlgorithmParameters.getInstance("EC");
algorithmParameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec parameterSpec = algorithmParameters.getParameterSpec(ECParameterSpec.class);
ECPrivateKeySpec ecPrivateKeySpec =
new ECPrivateKeySpec(new BigInteger(1, privateKeyBytes), parameterSpec);

KeyFactory keyFactory = KeyFactory.getInstance("EC");
PrivateKey privateKey = keyFactory.generatePrivate(ecPrivateKeySpec);

Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initSign(privateKey);

signature.update(message);

return signature.sign();

} catch (Exception e) {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ public interface DACProvider {

byte[] GetProductAttestationIntermediateCert();

byte[] GetDeviceAttestationCertPrivateKey();

byte[] GetDeviceAttestationCertPublicKeyKey();
/**
* Sign a mesage with the device attestation key.
*
* <p>The signature should be a SHA256withECDSA Signature that's returned in the ECDSA X9.62 Asn1
* format. This is the default behavior when using java.security.Signature with an EC P-256 curve.
*
* @param message The message to sign
* @return The signature in ECDSA X9.62 Asn1 format.
*/
byte[] SignWithDeviceAttestationKey(byte[] message);
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,51 @@ JNIDACProvider::JNIDACProvider(jobject provider)
env->ExceptionClear();
}

mGetDeviceAttestationCertPrivateKeyMethod = env->GetMethodID(JNIDACProviderClass, "GetDeviceAttestationCertPrivateKey", "()[B");
if (mGetDeviceAttestationCertPrivateKeyMethod == nullptr)
mSignWithDeviceAttestationKeyMethod = env->GetMethodID(JNIDACProviderClass, "SignWithDeviceAttestationKey", "([B)[B");
if (mSignWithDeviceAttestationKeyMethod == nullptr)
{
ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetDeviceAttestationCertPrivateKey' method");
ChipLogError(Zcl, "Failed to access JNIDACProvider 'SignWithDeviceAttestationKey' method");
env->ExceptionClear();
}
}

mGetDeviceAttestationCertPublicKeyKeyMethod =
env->GetMethodID(JNIDACProviderClass, "GetDeviceAttestationCertPublicKeyKey", "()[B");
if (mGetDeviceAttestationCertPublicKeyKeyMethod == nullptr)
CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, MutableByteSpan & out_buffer)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnLogError(mJNIDACProviderObject != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(method != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(env != nullptr, CHIP_JNI_ERROR_NO_ENV);

jbyteArray outArray = (jbyteArray) env->CallObjectMethod(mJNIDACProviderObject, method);
if (env->ExceptionCheck())
{
ChipLogError(Zcl, "Failed to access JNIDACProvider 'GetDeviceAttestationCertPublicKeyKey' method");
ChipLogError(Zcl, "Java exception in get Method");
env->ExceptionDescribe();
env->ExceptionClear();
return CHIP_ERROR_INCORRECT_STATE;
}

if (outArray == nullptr || env->GetArrayLength(outArray) <= 0)
{
out_buffer.reduce_size(0);
return CHIP_NO_ERROR;
}

JniByteArray JniOutArray(env, outArray);
return CopySpanToMutableSpan(JniOutArray.byteSpan(), out_buffer);
}

CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, MutableByteSpan & out_buffer)
CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, const ByteSpan & in_buffer, MutableByteSpan & out_buffer)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
VerifyOrReturnLogError(mJNIDACProviderObject != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(method != nullptr, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnLogError(env != nullptr, CHIP_JNI_ERROR_NO_ENV);

jbyteArray outArray = (jbyteArray) env->CallObjectMethod(mJNIDACProviderObject, method);
jbyteArray in_buffer_jbyteArray = env->NewByteArray((jsize)(in_buffer.size()));
env->SetByteArrayRegion(in_buffer_jbyteArray, 0, (int) in_buffer.size(), reinterpret_cast<const jbyte *>(in_buffer.data()));

jbyteArray outArray = (jbyteArray) env->CallObjectMethod(mJNIDACProviderObject, method, in_buffer_jbyteArray);
if (env->ExceptionCheck())
{
ChipLogError(Zcl, "Java exception in get Method");
Expand All @@ -101,6 +122,8 @@ CHIP_ERROR JNIDACProvider::GetJavaByteByMethod(jmethodID method, MutableByteSpan
return CHIP_ERROR_INCORRECT_STATE;
}

env->DeleteLocalRef(in_buffer_jbyteArray);

if (outArray == nullptr || env->GetArrayLength(outArray) <= 0)
{
out_buffer.reduce_size(0);
Expand Down Expand Up @@ -135,38 +158,20 @@ CHIP_ERROR JNIDACProvider::GetProductAttestationIntermediateCert(MutableByteSpan
return GetJavaByteByMethod(mGetProductAttestationIntermediateCertMethod, out_pai_buffer);
}

// TODO: This should be moved to a method of P256Keypair
CHIP_ERROR LoadKeypairFromRaw(ByteSpan private_key, ByteSpan public_key, Crypto::P256Keypair & keypair)
{
Crypto::P256SerializedKeypair serialized_keypair;
ReturnErrorOnFailure(serialized_keypair.SetLength(private_key.size() + public_key.size()));
memcpy(serialized_keypair.Bytes(), public_key.data(), public_key.size());
memcpy(serialized_keypair.Bytes() + public_key.size(), private_key.data(), private_key.size());
return keypair.Deserialize(serialized_keypair);
}

CHIP_ERROR JNIDACProvider::SignWithDeviceAttestationKey(const ByteSpan & message_to_sign, MutableByteSpan & out_signature_buffer)
{
ChipLogProgress(Zcl, "Received SignWithDeviceAttestationKey");
Crypto::P256ECDSASignature signature;
Crypto::P256Keypair keypair;
uint8_t mAsn1SignatureBytes[73];

VerifyOrReturnError(IsSpanUsable(out_signature_buffer), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(IsSpanUsable(message_to_sign), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(out_signature_buffer.size() >= signature.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
MutableByteSpan asn1_signature_buffer(mAsn1SignatureBytes, sizeof(mAsn1SignatureBytes));

uint8_t privateKeyBuf[Crypto::kP256_PrivateKey_Length];
MutableByteSpan privateKeyBufSpan(privateKeyBuf);
ReturnErrorOnFailure(GetJavaByteByMethod(mGetDeviceAttestationCertPrivateKeyMethod, privateKeyBufSpan));

uint8_t publicKeyBuf[Crypto::kP256_PublicKey_Length];
MutableByteSpan publicKeyBufSpan(publicKeyBuf);
ReturnErrorOnFailure(GetJavaByteByMethod(mGetDeviceAttestationCertPublicKeyKeyMethod, publicKeyBufSpan));

// In a non-exemplary implementation, the public key is not needed here. It is used here merely because
// Crypto::P256Keypair is only (currently) constructable from raw keys if both private/public keys are present.
ReturnErrorOnFailure(LoadKeypairFromRaw(privateKeyBufSpan, publicKeyBufSpan, keypair));
ReturnErrorOnFailure(keypair.ECDSA_sign_msg(message_to_sign.data(), message_to_sign.size(), signature));
CHIP_ERROR error = GetJavaByteByMethod(mSignWithDeviceAttestationKeyMethod, message_to_sign, asn1_signature_buffer);
if (error != CHIP_NO_ERROR)
{
ChipLogProgress(Zcl, "SignWithDeviceAttestationKey failed");
return error;
}

return CopySpanToMutableSpan(ByteSpan{ signature.ConstBytes(), signature.Length() }, out_signature_buffer);
return chip::Crypto::EcdsaAsn1SignatureToRaw(32, ByteSpan(asn1_signature_buffer.data(), asn1_signature_buffer.size()),
out_signature_buffer);
}
Loading

0 comments on commit 2c05385

Please sign in to comment.