Skip to content

Commit

Permalink
Updated OpCert Generation in the Operational Credentials Issuer. (#16261
Browse files Browse the repository at this point in the history
)

Problem: In the current implementation when ICAC and RCAC are
found in the storage the Subject DN for these certificates is generated
separately. This might be an issue if after software update the way
Subject DN is generated is changed.

Fix: The proper implementation is to extract Subject DN from ICAC and RCAC
if they are found in the device memory.
  • Loading branch information
emargolis authored and pull[bot] committed Dec 13, 2023
1 parent 47573ca commit 3257674
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 224 deletions.
70 changes: 36 additions & 34 deletions src/controller/ExampleOperationalCredentialsIssuer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,61 +116,63 @@ 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.
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId));
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId));
ReturnErrorOnFailure(noc_dn.AddCATs(cats));
ChipDN icac_dn;
ReturnErrorOnFailure(icac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipICAId, mIntermediateIssuerId));
ChipDN rcac_dn;
ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId));
CHIP_ERROR err = CHIP_NO_ERROR;
uint16_t rcacBufLen = static_cast<uint16_t>(std::min(rcac.size(), static_cast<size_t>(UINT16_MAX)));
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key,
err = mStorage->SyncGetKeyValue(key, rcac.data(), rcacBufLen));
if (err == CHIP_NO_ERROR)
{
// Found root certificate in the storage.
rcac.reduce_size(rcacBufLen);
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn));
}
// If root certificate not found in the storage, generate new root certificate.
else
{
ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId));

ChipLogProgress(Controller, "Generating NOC");
X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, icac_dn };
ReturnErrorOnFailure(NewNodeOperationalX509Cert(noc_request, pubkey, mIntermediateIssuer, noc));
ChipLogProgress(Controller, "Generating RCAC");
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);
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key,
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, rcac.data(), static_cast<uint16_t>(rcac.size()))));
}

ChipDN icac_dn;
uint16_t icacBufLen = static_cast<uint16_t>(std::min(icac.size(), static_cast<size_t>(UINT16_MAX)));
CHIP_ERROR err = CHIP_NO_ERROR;
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsIntermediateCertificateStorage, key,
err = mStorage->SyncGetKeyValue(key, icac.data(), icacBufLen));
if (err == CHIP_NO_ERROR)
{
// Found root certificate in the storage.
// Found intermediate certificate in the storage.
icac.reduce_size(icacBufLen);
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(icac, icac_dn));
}
// If intermediate certificate not found in the storage, generate new intermediate certificate.
else
{
ReturnErrorOnFailure(icac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipICAId, mIntermediateIssuerId));

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

VerifyOrReturnError(CanCastTo<uint16_t>(icac.size()), CHIP_ERROR_INTERNAL);
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsIntermediateCertificateStorage, key,
err = mStorage->SyncSetKeyValue(key, icac.data(), static_cast<uint16_t>(icac.size())));
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, icac.data(), static_cast<uint16_t>(icac.size()))));
}

uint16_t rcacBufLen = static_cast<uint16_t>(std::min(rcac.size(), static_cast<size_t>(UINT16_MAX)));
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key,
err = mStorage->SyncGetKeyValue(key, rcac.data(), rcacBufLen));
if (err == CHIP_NO_ERROR)
{
// Found root certificate in the storage.
rcac.reduce_size(rcacBufLen);
}
else
{
ChipLogProgress(Controller, "Generating RCAC");
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);
PERSISTENT_KEY_OP(mIndex, kOperationalCredentialsRootCertificateStorage, key,
err = mStorage->SyncSetKeyValue(key, rcac.data(), static_cast<uint16_t>(rcac.size())));
}
ChipDN noc_dn;
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId));
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId));
ReturnErrorOnFailure(noc_dn.AddCATs(cats));

return err;
ChipLogProgress(Controller, "Generating NOC");
X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, icac_dn };
return NewNodeOperationalX509Cert(noc_request, pubkey, mIntermediateIssuer, noc);
}

CHIP_ERROR ExampleOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan & csrElements,
Expand Down
41 changes: 22 additions & 19 deletions src/controller/java/AndroidOperationalCredentialsIssuer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,7 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
MutableByteSpan & rcac, MutableByteSpan & icac,
MutableByteSpan & noc)
{
ChipDN noc_dn;
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId));
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId));
ReturnErrorOnFailure(noc_dn.AddCATs(cats));
ChipDN rcac_dn;
ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId));

ChipLogProgress(Controller, "Generating 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)));
CHIP_ERROR err = CHIP_NO_ERROR;
PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key,
Expand All @@ -102,18 +91,32 @@ CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChainAfterValidation(
{
// Found root certificate in the storage.
rcac.reduce_size(rcacBufLen);
return CHIP_NO_ERROR;
ReturnErrorOnFailure(ExtractSubjectDNFromX509Cert(rcac, rcac_dn));
}
// If root certificate not found in the storage, generate new root certificate.
else
{
ReturnErrorOnFailure(rcac_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipRootId, mIssuerId));

ChipLogProgress(Controller, "Generating RCAC");
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);
PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key,
ReturnErrorOnFailure(mStorage->SyncSetKeyValue(key, rcac.data(), static_cast<uint16_t>(rcac.size()))));
}

ChipLogProgress(Controller, "Generating RCAC");
chip::Credentials::X509CertRequestParams rcac_request = { 0, mNow, mNow + mValidity, rcac_dn, rcac_dn };
ReturnErrorOnFailure(NewRootX509Cert(rcac_request, mIssuer, rcac));
icac.reduce_size(0);

VerifyOrReturnError(CanCastTo<uint16_t>(rcac.size()), CHIP_ERROR_INTERNAL);
PERSISTENT_KEY_OP(fabricId, kOperationalCredentialsRootCertificateStorage, key,
err = mStorage->SyncSetKeyValue(key, rcac.data(), static_cast<uint16_t>(rcac.size())));
ChipDN noc_dn;
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipFabricId, fabricId));
ReturnErrorOnFailure(noc_dn.AddAttribute(chip::ASN1::kOID_AttributeType_ChipNodeId, nodeId));
ReturnErrorOnFailure(noc_dn.AddCATs(cats));

return err;
ChipLogProgress(Controller, "Generating NOC");
chip::Credentials::X509CertRequestParams noc_request = { 1, mNow, mNow + mValidity, noc_dn, rcac_dn };
return NewNodeOperationalX509Cert(noc_request, pubkey, mIssuer, noc);
}

CHIP_ERROR AndroidOperationalCredentialsIssuer::GenerateNOCChain(const ByteSpan & csrElements,
Expand Down
180 changes: 180 additions & 0 deletions src/credentials/CHIPCert.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,36 @@ CHIP_ERROR ChipDN::GetCertFabricId(uint64_t & fabricId) const
return CHIP_NO_ERROR;
}

CHIP_ERROR ChipDN::EncodeToTLV(TLVWriter & writer, Tag tag) const
{
TLVType outerContainer;
uint8_t rdnCount = RDNCount();

ReturnErrorOnFailure(writer.StartContainer(tag, kTLVType_List, outerContainer));

for (uint8_t i = 0; i < rdnCount; i++)
{
// Derive the TLV tag number from the enum value assigned to the attribute type OID. For attributes that can be
// either UTF8String or PrintableString, use the high bit in the tag number to distinguish the two.
uint8_t tlvTagNum = GetOIDEnum(rdn[i].mAttrOID);
if (rdn[i].mAttrIsPrintableString)
{
tlvTagNum |= 0x80;
}

if (IsChipDNAttr(rdn[i].mAttrOID))
{
ReturnErrorOnFailure(writer.Put(ContextTag(tlvTagNum), rdn[i].mChipVal));
}
else
{
ReturnErrorOnFailure(writer.PutString(ContextTag(tlvTagNum), rdn[i].mString));
}
}

return writer.EndContainer(outerContainer);
}

CHIP_ERROR ChipDN::DecodeFromTLV(TLVReader & reader)
{
CHIP_ERROR err;
Expand Down Expand Up @@ -860,6 +890,100 @@ CHIP_ERROR ChipDN::EncodeToASN1(ASN1Writer & writer) const
return err;
}

CHIP_ERROR ChipDN::DecodeFromASN1(ASN1Reader & reader)
{
CHIP_ERROR err;

// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
ASN1_PARSE_ENTER_SEQUENCE
{
while ((err = reader.Next()) == CHIP_NO_ERROR)
{
// RelativeDistinguishedName ::= SET SIZE (1..MAX) OF AttributeTypeAndValue
ASN1_ENTER_SET
{
// AttributeTypeAndValue ::= SEQUENCE
ASN1_PARSE_ENTER_SEQUENCE
{
// type AttributeType
// AttributeType ::= OBJECT IDENTIFIER
OID attrOID;
ASN1_PARSE_OBJECT_ID(attrOID);
VerifyOrReturnError(GetOIDCategory(attrOID) == kOIDCategory_AttributeType, ASN1_ERROR_INVALID_ENCODING);

// AttributeValue ::= ANY -- DEFINED BY AttributeType
ASN1_PARSE_ANY;

uint8_t attrTag = reader.GetTag();

// Can only support UTF8String, PrintableString and IA5String.
VerifyOrReturnError(reader.GetClass() == kASN1TagClass_Universal &&
(attrTag == kASN1UniversalTag_PrintableString ||
attrTag == kASN1UniversalTag_UTF8String || attrTag == kASN1UniversalTag_IA5String),
ASN1_ERROR_UNSUPPORTED_ENCODING);

// CHIP attributes must be UTF8Strings.
if (IsChipDNAttr(attrOID))
{
VerifyOrReturnError(attrTag == kASN1UniversalTag_UTF8String, ASN1_ERROR_INVALID_ENCODING);
}

// If 64-bit CHIP attribute.
if (IsChip64bitDNAttr(attrOID))
{
uint64_t chipAttr;
VerifyOrReturnError(Encoding::UppercaseHexToUint64(reinterpret_cast<const char *>(reader.GetValue()),
static_cast<size_t>(reader.GetValueLen()),
chipAttr) == sizeof(uint64_t),
ASN1_ERROR_INVALID_ENCODING);

if (attrOID == chip::ASN1::kOID_AttributeType_ChipNodeId)
{
VerifyOrReturnError(IsOperationalNodeId(chipAttr), CHIP_ERROR_WRONG_CERT_DN);
}
else if (attrOID == chip::ASN1::kOID_AttributeType_ChipFabricId)
{
VerifyOrReturnError(IsValidFabricId(chipAttr), CHIP_ERROR_WRONG_CERT_DN);
}

ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr));
}
// If 32-bit CHIP attribute.
else if (IsChip32bitDNAttr(attrOID))
{
CASEAuthTag chipAttr;
VerifyOrReturnError(Encoding::UppercaseHexToUint32(reinterpret_cast<const char *>(reader.GetValue()),
reader.GetValueLen(), chipAttr) == sizeof(CASEAuthTag),
ASN1_ERROR_INVALID_ENCODING);

VerifyOrReturnError(IsValidCASEAuthTag(chipAttr), CHIP_ERROR_WRONG_CERT_DN);

ReturnErrorOnFailure(AddAttribute(attrOID, chipAttr));
}
// Otherwise, it is a string.
else
{
ReturnErrorOnFailure(AddAttribute(attrOID,
CharSpan(Uint8::to_const_char(reader.GetValue()), reader.GetValueLen()),
attrTag == kASN1UniversalTag_PrintableString));
}
}
ASN1_EXIT_SEQUENCE;

// Only one AttributeTypeAndValue allowed per RDN.
err = reader.Next();
ReturnErrorCodeIf(err == CHIP_NO_ERROR, ASN1_ERROR_UNSUPPORTED_ENCODING);
VerifyOrReturnError(err == ASN1_END, err);
}
ASN1_EXIT_SET;
}
}
ASN1_EXIT_SEQUENCE;

exit:
return err;
}

bool ChipDN::IsEqual(const ChipDN & other) const
{
bool res = true;
Expand Down Expand Up @@ -1159,5 +1283,61 @@ CHIP_ERROR ExtractSKIDFromChipCert(const ByteSpan & chipCert, CertificateKeyId &
return CHIP_NO_ERROR;
}

CHIP_ERROR ExtractSubjectDNFromChipCert(const ByteSpan & chipCert, ChipDN & dn)
{
ChipCertificateSet certSet;
ChipCertificateData certData;

ReturnErrorOnFailure(certSet.Init(&certData, 1));

ReturnErrorOnFailure(certSet.LoadCert(chipCert, BitFlags<CertDecodeFlags>()));

dn = certData.mSubjectDN;

return CHIP_NO_ERROR;
}

CHIP_ERROR ExtractSubjectDNFromX509Cert(const ByteSpan & x509Cert, ChipDN & dn)
{
CHIP_ERROR err;
ASN1Reader reader;

VerifyOrReturnError(CanCastTo<uint32_t>(x509Cert.size()), CHIP_ERROR_INVALID_ARGUMENT);

reader.Init(x509Cert);

// Certificate ::= SEQUENCE
ASN1_PARSE_ENTER_SEQUENCE
{
// tbsCertificate TBSCertificate,
// TBSCertificate ::= SEQUENCE
ASN1_PARSE_ENTER_SEQUENCE
{
// Skip version [0] EXPLICIT Version DEFAULT v1
ASN1_PARSE_ELEMENT(kASN1TagClass_ContextSpecific, 0);

// Skip serialNumber CertificateSerialNumber
ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Integer);

// Skip signature AlgorithmIdentifier
ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Sequence);

// Skip issuer Name
ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Sequence);

// Skip validity Validity,
ASN1_PARSE_ELEMENT(kASN1TagClass_Universal, kASN1UniversalTag_Sequence);

// Decode subject Name,
ReturnErrorOnFailure(dn.DecodeFromASN1(reader));
}
ASN1_SKIP_AND_EXIT_SEQUENCE;
}
ASN1_SKIP_AND_EXIT_SEQUENCE;

exit:
return err;
}

} // namespace Credentials
} // namespace chip
Loading

0 comments on commit 3257674

Please sign in to comment.