Skip to content

Commit

Permalink
Op Certs Generation: Support CASE Authenticated Tags and other Attrib…
Browse files Browse the repository at this point in the history
…utes. (#13737)

- Added support for the subject and issuer DNs in the X509CertRequestParams structure.
  - Factored out ChipDN encoding and decoding into separate functions.
  - Other updates and cleanups.
  • Loading branch information
emargolis authored and pull[bot] committed Jan 6, 2024
1 parent 66cec0b commit 1081096
Show file tree
Hide file tree
Showing 9 changed files with 385 additions and 304 deletions.
24 changes: 17 additions & 7 deletions src/controller/ExampleOperationalCredentialsIssuer.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2021 Project CHIP Authors
* Copyright (c) 2021-2022 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -95,14 +95,24 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
MutableByteSpan & rcac, MutableByteSpan & icac,
MutableByteSpan & noc)
{
ChipDN noc_dn;
// TODO: Is there a way to make this code less error-prone for consumers?
// The consumer doesn't need to know the exact OID value.
noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId);
noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId);
// TODO: Add support for the CASE Authenticated Tags attributes
ChipDN icac_dn;
icac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipICAId, mIntermediateIssuerId);
ChipDN rcac_dn;
rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId);

ChipLogProgress(Controller, "Generating NOC");
X509CertRequestParams noc_request = { 1, mIntermediateIssuerId, mNow, mNow + mValidity, true, fabricId, true, nodeId };
ReturnErrorOnFailure(
NewNodeOperationalX509Cert(noc_request, CertificateIssuerLevel::kIssuerIsIntermediateCA, pubkey, mIntermediateIssuer, noc));
X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, icac_dn };
ReturnErrorOnFailure(NewNodeOperationalX509Cert(noc_request, pubkey, mIntermediateIssuer, noc));

ChipLogProgress(Controller, "Generating ICAC");
X509CertRequestParams icac_request = { 0, mIssuerId, mNow, mNow + mValidity, true, fabricId, false, 0 };
ReturnErrorOnFailure(NewICAX509Cert(icac_request, mIntermediateIssuerId, mIntermediateIssuer.Pubkey(), mIssuer, icac));
X509CertRequestParams icac_request = { 0, mNow, mNow + mValidity, icac_dn, rcac_dn };
ReturnErrorOnFailure(NewICAX509Cert(icac_request, mIntermediateIssuer.Pubkey(), mIssuer, icac));

uint16_t rcacBufLen = static_cast<uint16_t>(std::min(rcac.size(), static_cast<size_t>(UINT16_MAX)));
CHIP_ERROR err = CHIP_NO_ERROR;
Expand All @@ -116,7 +126,7 @@ CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
}

ChipLogProgress(Controller, "Generating RCAC");
X509CertRequestParams rcac_request = { 0, mIssuerId, mNow, mNow + mValidity, true, fabricId, false, 0 };
X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn };
ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac));

VerifyOrReturnError(CanCastTo<uint16_t>(rcac.size()), CHIP_ERROR_INTERNAL);
Expand Down
15 changes: 10 additions & 5 deletions src/controller/java/AndroidDeviceControllerWrapper.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2020-2021 Project CHIP Authors
* Copyright (c) 2020-2022 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -67,10 +67,15 @@ CHIP_ERROR AndroidDeviceControllerWrapper::GenerateNOCChainAfterValidation(NodeI
MutableByteSpan & rcac, MutableByteSpan & icac,
MutableByteSpan & noc)
{
ChipDN noc_dn;
noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId);
noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId);
ChipDN rcac_dn;
rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId);

ChipLogProgress(Controller, "Generating NOC");
chip::Credentials::X509CertRequestParams noc_request = { 1, mIssuerId, mNow, mNow + mValidity, true, fabricId, true, nodeId };
ReturnErrorOnFailure(
NewNodeOperationalX509Cert(noc_request, chip::Credentials::CertificateIssuerLevel::kIssuerIsRootCA, pubkey, mIssuer, noc));
chip::Credentials::X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, rcac_dn };
ReturnErrorOnFailure(NewNodeOperationalX509Cert(noc_request, pubkey, mIssuer, noc));
icac.reduce_size(0);

uint16_t rcacBufLen = static_cast<uint16_t>(std::min(rcac.size(), static_cast<size_t>(UINT16_MAX)));
Expand All @@ -85,7 +90,7 @@ CHIP_ERROR AndroidDeviceControllerWrapper::GenerateNOCChainAfterValidation(NodeI
}

ChipLogProgress(Controller, "Generating RCAC");
chip::Credentials::X509CertRequestParams rcac_request = { 0, mIssuerId, mNow, mNow + mValidity, true, fabricId, false, 0 };
chip::Credentials::X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn };
ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac));

VerifyOrReturnError(CanCastTo<uint16_t>(rcac.size()), CHIP_ERROR_INTERNAL);
Expand Down
153 changes: 146 additions & 7 deletions src/credentials/CHIPCert.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2020-2021 Project CHIP Authors
* Copyright (c) 2020-2022 Project CHIP Authors
* Copyright (c) 2019 Google LLC.
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
Expand Down Expand Up @@ -515,7 +515,8 @@ void ValidationContext::Reset()

bool ChipRDN::IsEqual(const ChipRDN & other) const
{
if (mAttrOID == kOID_Unknown || mAttrOID == kOID_NotSpecified || mAttrOID != other.mAttrOID)
if (mAttrOID == kOID_Unknown || mAttrOID == kOID_NotSpecified || mAttrOID != other.mAttrOID ||
mAttrIsPrintableString != other.mAttrIsPrintableString)
{
return false;
}
Expand Down Expand Up @@ -569,22 +570,24 @@ CHIP_ERROR ChipDN::AddAttribute(chip::ASN1::OID oid, uint64_t val)
VerifyOrReturnError(CanCastTo<uint32_t>(val), CHIP_ERROR_INVALID_ARGUMENT);
}

rdn[rdnCount].mAttrOID = oid;
rdn[rdnCount].mChipVal = val;
rdn[rdnCount].mAttrOID = oid;
rdn[rdnCount].mChipVal = val;
rdn[rdnCount].mAttrIsPrintableString = false;

return CHIP_NO_ERROR;
}

CHIP_ERROR ChipDN::AddAttribute(chip::ASN1::OID oid, CharSpan val)
CHIP_ERROR ChipDN::AddAttribute(chip::ASN1::OID oid, CharSpan val, bool isPrintableString)
{
uint8_t rdnCount = RDNCount();

VerifyOrReturnError(rdnCount < CHIP_CONFIG_CERT_MAX_RDN_ATTRIBUTES, CHIP_ERROR_NO_MEMORY);
VerifyOrReturnError(!IsChipDNAttr(oid), CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(oid != kOID_NotSpecified, CHIP_ERROR_INVALID_ARGUMENT);

rdn[rdnCount].mAttrOID = oid;
rdn[rdnCount].mString = val;
rdn[rdnCount].mAttrOID = oid;
rdn[rdnCount].mString = val;
rdn[rdnCount].mAttrIsPrintableString = isPrintableString;

return CHIP_NO_ERROR;
}
Expand Down Expand Up @@ -695,6 +698,142 @@ CHIP_ERROR ChipDN::GetCertFabricId(uint64_t & fabricId) const
return CHIP_NO_ERROR;
}

CHIP_ERROR ChipDN::DecodeFromTLV(TLVReader & reader)
{
CHIP_ERROR err;
TLVType outerContainer;

static constexpr uint32_t kOID_AttributeIsPrintableString_Flag = 0x00000080;
static constexpr uint32_t kOID_AttributeType_Mask = 0x0000007F;

VerifyOrReturnError(reader.GetType() == kTLVType_List, CHIP_ERROR_WRONG_TLV_TYPE);

// Enter the List TLV element that represents the DN in TLV format.
ReturnErrorOnFailure(reader.EnterContainer(outerContainer));

// Read the RDN attributes in the List.
while ((err = reader.Next()) == CHIP_NO_ERROR)
{
// Get the TLV tag, make sure it is a context tag and extract the context tag number.
Tag tlvTag = reader.GetTag();
VerifyOrReturnError(IsContextTag(tlvTag), CHIP_ERROR_INVALID_TLV_TAG);
uint32_t tlvTagNum = TagNumFromTag(tlvTag);

// Derive the OID of the corresponding ASN.1 attribute from the TLV tag number.
// The numeric value of the OID is encoded in the bottom 7 bits of the TLV tag number.
// This eliminates the need for a translation table/switch statement but has the
// effect of tying the two encodings together.
//
// NOTE: In the event that the computed OID value is not one that we recognize
// (specifically, is not in the table of OIDs defined in ASN1OID.h) then the
// macro call below that encodes the attribute's object id (ASN1_ENCODE_OBJECT_ID)
// will fail for lack of the OID's encoded representation. Given this there's no
// need to test the validity of the OID here.
//
OID attrOID = GetOID(kOIDCategory_AttributeType, static_cast<uint8_t>(tlvTagNum & kOID_AttributeType_Mask));

bool attrIsPrintableString = (tlvTagNum & kOID_AttributeIsPrintableString_Flag) == kOID_AttributeIsPrintableString_Flag;

// For 64-bit CHIP-defined DN attributes.
if (IsChip64bitDNAttr(attrOID))
{
uint64_t chipAttr;
VerifyOrReturnError(attrIsPrintableString == false, CHIP_ERROR_INVALID_TLV_TAG);
ReturnErrorOnFailure(reader.Get(chipAttr));
ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr));
}
// For 32-bit CHIP-defined DN attributes.
else if (IsChip32bitDNAttr(attrOID))
{
uint32_t chipAttr;
VerifyOrReturnError(attrIsPrintableString == false, CHIP_ERROR_INVALID_TLV_TAG);
ReturnErrorOnFailure(reader.Get(chipAttr));
ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr));
}
// Otherwise the attribute is one of the supported X.509 attributes
else
{
CharSpan asn1Attr;
ReturnErrorOnFailure(reader.Get(asn1Attr));
ReturnErrorOnFailure(AddAttribute(attrOID, asn1Attr, attrIsPrintableString));
}
}
VerifyOrReturnError(err == CHIP_END_OF_TLV, err);
ReturnErrorOnFailure(reader.ExitContainer(outerContainer));

return CHIP_NO_ERROR;
}

CHIP_ERROR ChipDN::EncodeToASN1(ASN1Writer & writer) const
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t rdnCount = RDNCount();

ASN1_START_SEQUENCE
{
for (uint8_t i = 0; i < rdnCount; i++)
{
ASN1_START_SET
{
char chipAttrStr[kChip64bitAttrUTF8Length + 1];
CharSpan asn1Attr;
uint8_t asn1Tag;
chip::ASN1::OID attrOID = rdn[i].mAttrOID;

if (IsChip64bitDNAttr(attrOID))
{
snprintf(chipAttrStr, sizeof(chipAttrStr), ChipLogFormatX64, ChipLogValueX64(rdn[i].mChipVal));
asn1Attr = CharSpan(chipAttrStr, kChip64bitAttrUTF8Length);
asn1Tag = kASN1UniversalTag_UTF8String;
}
else if (IsChip32bitDNAttr(attrOID))
{
snprintf(chipAttrStr, sizeof(chipAttrStr), "%08" PRIX32, static_cast<uint32_t>(rdn[i].mChipVal));
asn1Attr = CharSpan(chipAttrStr, kChip32bitAttrUTF8Length);
asn1Tag = kASN1UniversalTag_UTF8String;
}
else
{
asn1Attr = rdn[i].mString;

// Determine the appropriate ASN.1 tag for the DN attribute.
// - DomainComponent is always an IA5String.
// - For all other ASN.1 defined attributes, bit 0x80 in the TLV tag value conveys whether the attribute
// is a UTF8String or a PrintableString (in some cases the certificate generator has a choice).
if (attrOID == kOID_AttributeType_DomainComponent)
{
asn1Tag = kASN1UniversalTag_IA5String;
}
else
{
asn1Tag = rdn[i].mAttrIsPrintableString ? kASN1UniversalTag_PrintableString : kASN1UniversalTag_UTF8String;
}
}

// AttributeTypeAndValue ::= SEQUENCE
ASN1_START_SEQUENCE
{
// type AttributeType
// AttributeType ::= OBJECT IDENTIFIER
ASN1_ENCODE_OBJECT_ID(attrOID);

VerifyOrReturnError(CanCastTo<uint16_t>(asn1Attr.size()), CHIP_ERROR_UNSUPPORTED_CERT_FORMAT);

// value AttributeValue
// AttributeValue ::= ANY -- DEFINED BY AttributeType
ReturnErrorOnFailure(writer.PutString(asn1Tag, asn1Attr.data(), static_cast<uint16_t>(asn1Attr.size())));
}
ASN1_END_SEQUENCE;
}
ASN1_END_SET;
}
}
ASN1_END_SEQUENCE;

exit:
return err;
}

bool ChipDN::IsEqual(const ChipDN & other) const
{
bool res = true;
Expand Down
49 changes: 25 additions & 24 deletions src/credentials/CHIPCert.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2020-2021 Project CHIP Authors
* Copyright (c) 2020-2022 Project CHIP Authors
* Copyright (c) 2019 Google LLC.
* Copyright (c) 2013-2017 Nest Labs, Inc.
* All rights reserved.
Expand Down Expand Up @@ -198,9 +198,10 @@ enum
*/
struct ChipRDN
{
CharSpan mString; /**< Attribute value when encoded as a string. */
uint64_t mChipVal; /**< CHIP specific DN attribute value. */
chip::ASN1::OID mAttrOID; /**< DN attribute CHIP OID. */
CharSpan mString; /**< Attribute value when encoded as a string. */
uint64_t mChipVal; /**< CHIP specific DN attribute value. */
chip::ASN1::OID mAttrOID = chip::ASN1::kOID_NotSpecified; /**< DN attribute CHIP OID. */
bool mAttrIsPrintableString; /**< Specifies if attribute is a printable string type. */

bool IsEqual(const ChipRDN & other) const;
bool IsEmpty() const { return mAttrOID == chip::ASN1::kOID_NotSpecified; }
Expand Down Expand Up @@ -235,10 +236,11 @@ class ChipDN
* @param oid String OID for DN attribute.
* @param val A CharSpan object containing a pointer and length of the DN string attribute
* buffer. The value in the buffer should remain valid while the object is in use.
* @param isPrintableString Specifies if attribute ASN1 type is a printable string.
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR AddAttribute(chip::ASN1::OID oid, CharSpan val);
CHIP_ERROR AddAttribute(chip::ASN1::OID oid, CharSpan val, bool isPrintableString);

/**
* @brief Determine type of a CHIP certificate.
Expand All @@ -265,6 +267,18 @@ class ChipDN
**/
CHIP_ERROR GetCertFabricId(uint64_t & fabricId) const;

/**
* @brief Decode ChipDN attributes from TLV encoded format.
*
* @param reader A TLVReader positioned at the ChipDN TLV list.
**/
CHIP_ERROR DecodeFromTLV(chip::TLV::TLVReader & reader);

/**
* @brief Encode ChipDN attributes in ASN1 form.
**/
CHIP_ERROR EncodeToASN1(ASN1::ASN1Writer & writer) const;

bool IsEqual(const ChipDN & other) const;

/**
Expand Down Expand Up @@ -613,23 +627,13 @@ CHIP_ERROR ConvertX509CertToChipCert(const ByteSpan x509Cert, MutableByteSpan &
**/
CHIP_ERROR ConvertChipCertToX509Cert(const ByteSpan chipCert, MutableByteSpan & x509Cert);

// TODO: Add support for Authentication Tag Attribute
struct X509CertRequestParams
{
int64_t SerialNumber;
uint64_t Issuer;
uint32_t ValidityStart;
uint32_t ValidityEnd;
bool HasFabricID;
uint64_t FabricID;
bool HasNodeID;
uint64_t NodeID;
};

enum CertificateIssuerLevel
{
kIssuerIsRootCA,
kIssuerIsIntermediateCA,
ChipDN SubjectDN;
ChipDN IssuerDN;
};

/**
Expand All @@ -648,16 +652,14 @@ CHIP_ERROR NewRootX509Cert(const X509CertRequestParams & requestParams, Crypto::
* @brief Generate a new X.509 DER encoded Intermediate CA certificate
*
* @param requestParams Certificate request parameters.
* @param subject The requested subject ID
* @param subjectPubkey The public key of subject
* @param issuerKeypair The certificate signing key
* @param x509Cert Buffer to store signed certificate in X.509 DER format.
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR NewICAX509Cert(const X509CertRequestParams & requestParams, uint64_t subject,
const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair,
MutableByteSpan & x509Cert);
CHIP_ERROR NewICAX509Cert(const X509CertRequestParams & requestParams, const Crypto::P256PublicKey & subjectPubkey,
Crypto::P256Keypair & issuerKeypair, MutableByteSpan & x509Cert);

/**
* @brief Generate a new X.509 DER encoded Node operational certificate
Expand All @@ -670,9 +672,8 @@ CHIP_ERROR NewICAX509Cert(const X509CertRequestParams & requestParams, uint64_t
*
* @return Returns a CHIP_ERROR on error, CHIP_NO_ERROR otherwise
**/
CHIP_ERROR NewNodeOperationalX509Cert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel,
const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair,
MutableByteSpan & x509Cert);
CHIP_ERROR NewNodeOperationalX509Cert(const X509CertRequestParams & requestParams, const Crypto::P256PublicKey & subjectPubkey,
Crypto::P256Keypair & issuerKeypair, MutableByteSpan & x509Cert);

/**
* @brief
Expand Down
Loading

0 comments on commit 1081096

Please sign in to comment.