| /* |
| * |
| * Copyright (c) 2021 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 |
| * This file implements methods for generating CHIP X.509 certificate. |
| * |
| */ |
| |
| #ifndef __STDC_LIMIT_MACROS |
| #define __STDC_LIMIT_MACROS |
| #endif |
| |
| #include <algorithm> |
| #include <inttypes.h> |
| #include <stddef.h> |
| |
| #include <credentials/CHIPCert.h> |
| #include <lib/asn1/ASN1.h> |
| #include <lib/asn1/ASN1Macros.h> |
| #include <lib/core/CHIPCore.h> |
| #include <lib/core/CHIPSafeCasts.h> |
| #include <lib/support/CodeUtils.h> |
| #include <lib/support/DLLUtil.h> |
| #include <protocols/Protocols.h> |
| |
| namespace chip { |
| namespace Credentials { |
| |
| using namespace chip::ASN1; |
| using namespace chip::Crypto; |
| using namespace chip::Protocols; |
| |
| namespace { |
| |
| struct ChipDNParams |
| { |
| OID AttrOID; |
| uint64_t Value; |
| }; |
| |
| enum IsCACert |
| { |
| kCACert, |
| kNotCACert, |
| }; |
| |
| CHIP_ERROR EncodeSubjectPublicKeyInfo(const Crypto::P256PublicKey & pubkey, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_SEQUENCE |
| { |
| ASN1_START_SEQUENCE |
| { |
| ASN1_ENCODE_OBJECT_ID(kOID_PubKeyAlgo_ECPublicKey); |
| ASN1_ENCODE_OBJECT_ID(kOID_EllipticCurve_prime256v1); |
| } |
| ASN1_END_SEQUENCE; |
| |
| ReturnErrorOnFailure(writer.PutBitString(0, pubkey, static_cast<uint8_t>(pubkey.Length()))); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeAuthorityKeyIdentifierExtension(const Crypto::P256PublicKey & pubkey, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_SEQUENCE |
| { |
| OID extensionOID = GetOID(kOIDCategory_Extension, static_cast<uint8_t>(kTag_AuthorityKeyIdentifier)); |
| |
| ASN1_ENCODE_OBJECT_ID(extensionOID); |
| |
| ASN1_START_OCTET_STRING_ENCAPSULATED |
| { |
| ASN1_START_SEQUENCE |
| { |
| uint8_t keyid[kSHA1_Hash_Length]; |
| ReturnErrorOnFailure(Crypto::Hash_SHA1(pubkey, pubkey.Length(), keyid)); |
| |
| ReturnErrorOnFailure( |
| writer.PutOctetString(kASN1TagClass_ContextSpecific, 0, keyid, static_cast<uint8_t>(sizeof(keyid)))); |
| } |
| ASN1_END_SEQUENCE; |
| } |
| ASN1_END_ENCAPSULATED; |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeSubjectKeyIdentifierExtension(const Crypto::P256PublicKey & pubkey, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_SEQUENCE |
| { |
| OID extensionOID = GetOID(kOIDCategory_Extension, static_cast<uint8_t>(kTag_SubjectKeyIdentifier)); |
| |
| ASN1_ENCODE_OBJECT_ID(extensionOID); |
| |
| ASN1_START_OCTET_STRING_ENCAPSULATED |
| { |
| uint8_t keyid[kSHA1_Hash_Length]; |
| ReturnErrorOnFailure(Crypto::Hash_SHA1(pubkey, pubkey.Length(), keyid)); |
| |
| ReturnErrorOnFailure(writer.PutOctetString(keyid, static_cast<uint8_t>(sizeof(keyid)))); |
| } |
| ASN1_END_ENCAPSULATED; |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeKeyUsageExtension(uint16_t keyUsageBits, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_SEQUENCE |
| { |
| OID extensionOID = GetOID(kOIDCategory_Extension, static_cast<uint8_t>(kTag_KeyUsage)); |
| |
| ASN1_ENCODE_OBJECT_ID(extensionOID); |
| |
| // KeyUsage extension MUST be marked as critical. |
| ASN1_ENCODE_BOOLEAN(true); |
| ASN1_START_OCTET_STRING_ENCAPSULATED { ASN1_ENCODE_BIT_STRING(keyUsageBits); } |
| ASN1_END_ENCAPSULATED; |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeIsCAExtension(IsCACert isCA, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_SEQUENCE |
| { |
| OID extensionOID = GetOID(kOIDCategory_Extension, static_cast<uint8_t>(kTag_BasicConstraints)); |
| |
| ASN1_ENCODE_OBJECT_ID(extensionOID); |
| |
| // BasicConstraints extension MUST be marked as critical. |
| ASN1_ENCODE_BOOLEAN(true); |
| |
| ASN1_START_OCTET_STRING_ENCAPSULATED |
| { |
| ASN1_START_SEQUENCE |
| { |
| // cA BOOLEAN |
| if (isCA == kCACert) |
| { |
| // Encode the boolean only if isCA is true |
| ASN1_ENCODE_BOOLEAN(true); |
| } |
| } |
| ASN1_END_SEQUENCE; |
| } |
| ASN1_END_ENCAPSULATED; |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeCASpecificExtensions(ASN1Writer & writer) |
| { |
| ReturnErrorOnFailure(EncodeIsCAExtension(kCACert, writer)); |
| |
| uint16_t keyUsageBits = static_cast<uint16_t>(KeyUsageFlags::kKeyCertSign) | static_cast<uint16_t>(KeyUsageFlags::kCRLSign); |
| |
| ReturnErrorOnFailure(EncodeKeyUsageExtension(keyUsageBits, writer)); |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR EncodeNOCSpecificExtensions(ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| uint16_t keyUsageBits = static_cast<uint16_t>(KeyUsageFlags::kDigitalSignature); |
| |
| ReturnErrorOnFailure(EncodeIsCAExtension(kNotCACert, writer)); |
| ReturnErrorOnFailure(EncodeKeyUsageExtension(keyUsageBits, writer)); |
| |
| ASN1_START_SEQUENCE |
| { |
| OID extensionOID = GetOID(kOIDCategory_Extension, static_cast<uint8_t>(kTag_ExtendedKeyUsage)); |
| |
| ASN1_ENCODE_OBJECT_ID(extensionOID); |
| |
| // ExtKeyUsage extension MUST be marked as critical. |
| ASN1_ENCODE_BOOLEAN(true); |
| ASN1_START_OCTET_STRING_ENCAPSULATED |
| { |
| ASN1_START_SEQUENCE |
| { |
| ASN1_ENCODE_OBJECT_ID(kOID_KeyPurpose_ClientAuth); |
| ASN1_ENCODE_OBJECT_ID(kOID_KeyPurpose_ServerAuth); |
| } |
| ASN1_END_SEQUENCE; |
| } |
| ASN1_END_ENCAPSULATED; |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeExtensions(bool isCA, const Crypto::P256PublicKey & SKI, const Crypto::P256PublicKey & AKI, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 3) |
| { |
| ASN1_START_SEQUENCE |
| { |
| if (isCA) |
| { |
| ReturnErrorOnFailure(EncodeCASpecificExtensions(writer)); |
| } |
| else |
| { |
| ReturnErrorOnFailure(EncodeNOCSpecificExtensions(writer)); |
| } |
| |
| ReturnErrorOnFailure(EncodeSubjectKeyIdentifierExtension(SKI, writer)); |
| |
| ReturnErrorOnFailure(EncodeAuthorityKeyIdentifierExtension(AKI, writer)); |
| } |
| ASN1_END_SEQUENCE; |
| } |
| ASN1_END_CONSTRUCTED; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeChipDNs(ChipDNParams * params, uint8_t numParams, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_SEQUENCE |
| { |
| for (uint8_t i = 0; i < numParams; i++) |
| { |
| ASN1_START_SET |
| { |
| uint8_t chipAttrStr[kChip64bitAttrUTF8Length + 1]; |
| snprintf(reinterpret_cast<char *>(chipAttrStr), sizeof(chipAttrStr), ChipLogFormatX64, |
| ChipLogValueX64(params[i].Value)); |
| |
| ASN1_START_SEQUENCE |
| { |
| ASN1_ENCODE_OBJECT_ID(params[i].AttrOID); |
| ReturnErrorOnFailure(writer.PutString(kASN1UniversalTag_UTF8String, Uint8::to_const_char(chipAttrStr), 16)); |
| } |
| ASN1_END_SEQUENCE; |
| } |
| ASN1_END_SET; |
| } |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeValidity(uint32_t validityStart, uint32_t validityEnd, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| ASN1UniversalTime asn1Time; |
| |
| ASN1_START_SEQUENCE |
| { |
| ReturnErrorOnFailure(ChipEpochToASN1Time(validityStart, asn1Time)); |
| ASN1_ENCODE_TIME(asn1Time); |
| |
| ReturnErrorOnFailure(ChipEpochToASN1Time(validityEnd, asn1Time)); |
| ASN1_ENCODE_TIME(asn1Time); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR EncodeChipECDSASignature(Crypto::P256ECDSASignature & signature, ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| |
| ASN1_START_BIT_STRING_ENCAPSULATED |
| { |
| // Convert RAW signature to DER when generating X509 certs. |
| P256ECDSASignatureSpan raw_sig(signature.Bytes()); |
| ReturnErrorOnFailure(ConvertECDSASignatureRawToDER(raw_sig, writer)); |
| } |
| ASN1_END_ENCAPSULATED; |
| |
| exit: |
| return err; |
| } |
| |
| } // namespace |
| |
| CHIP_ERROR EncodeTBSCert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel, uint64_t subject, |
| const Crypto::P256PublicKey & subjectPubkey, const Crypto::P256PublicKey & issuerPubkey, |
| ASN1Writer & writer) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| ChipDNParams dnParams[2]; |
| uint8_t numDNs = 1; |
| bool isCA = true; |
| |
| VerifyOrReturnError(requestParams.SerialNumber >= 0, CHIP_ERROR_INVALID_ARGUMENT); |
| |
| ASN1_START_SEQUENCE |
| { |
| // version [0] EXPLICIT Version DEFAULT v1 |
| ASN1_START_CONSTRUCTED(kASN1TagClass_ContextSpecific, 0) |
| { |
| // Version ::= INTEGER { v1(0), v2(1), v3(2) } |
| ASN1_ENCODE_INTEGER(2); |
| } |
| ASN1_END_CONSTRUCTED; |
| |
| ReturnErrorOnFailure(writer.PutInteger(requestParams.SerialNumber)); |
| |
| ASN1_START_SEQUENCE { ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256); } |
| ASN1_END_SEQUENCE; |
| |
| // Issuer OID depends on if cert is being signed by the root CA |
| if (issuerLevel == kIssuerIsRootCA) |
| { |
| dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipRootId; |
| } |
| else |
| { |
| dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipICAId; |
| } |
| dnParams[0].Value = requestParams.Issuer; |
| |
| if (requestParams.HasFabricID) |
| { |
| dnParams[1].AttrOID = chip::ASN1::kOID_AttributeType_ChipFabricId; |
| dnParams[1].Value = requestParams.FabricID; |
| numDNs = 2; |
| } |
| ReturnErrorOnFailure(EncodeChipDNs(dnParams, numDNs, writer)); |
| |
| // validity Validity, |
| ReturnErrorOnFailure(EncodeValidity(requestParams.ValidityStart, requestParams.ValidityEnd, writer)); |
| |
| // subject Name |
| if (requestParams.HasNodeID) |
| { |
| dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipNodeId; |
| |
| isCA = false; |
| } |
| else if (subjectPubkey != issuerPubkey) |
| { |
| dnParams[0].AttrOID = chip::ASN1::kOID_AttributeType_ChipICAId; |
| } |
| dnParams[0].Value = subject; |
| |
| ReturnErrorOnFailure(EncodeChipDNs(dnParams, numDNs, writer)); |
| |
| ReturnErrorOnFailure(EncodeSubjectPublicKeyInfo(subjectPubkey, writer)); |
| |
| // certificate extensions |
| ReturnErrorOnFailure(EncodeExtensions(isCA, subjectPubkey, issuerPubkey, writer)); |
| } |
| ASN1_END_SEQUENCE; |
| |
| exit: |
| return err; |
| } |
| |
| CHIP_ERROR NewChipX509Cert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel, uint64_t subject, |
| const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, |
| MutableByteSpan & x509Cert) |
| { |
| CHIP_ERROR err = CHIP_NO_ERROR; |
| ASN1Writer writer; |
| writer.Init(x509Cert); |
| |
| ReturnErrorOnFailure(EncodeTBSCert(requestParams, issuerLevel, subject, subjectPubkey, issuerKeypair.Pubkey(), writer)); |
| |
| Crypto::P256ECDSASignature signature; |
| ReturnErrorOnFailure(issuerKeypair.ECDSA_sign_msg(x509Cert.data(), writer.GetLengthWritten(), signature)); |
| |
| writer.Init(x509Cert); |
| |
| ASN1_START_SEQUENCE |
| { |
| ReturnErrorOnFailure(EncodeTBSCert(requestParams, issuerLevel, subject, subjectPubkey, issuerKeypair.Pubkey(), writer)); |
| |
| ASN1_START_SEQUENCE { ASN1_ENCODE_OBJECT_ID(kOID_SigAlgo_ECDSAWithSHA256); } |
| ASN1_END_SEQUENCE; |
| |
| ReturnErrorOnFailure(EncodeChipECDSASignature(signature, writer)); |
| } |
| ASN1_END_SEQUENCE; |
| |
| x509Cert.reduce_size(writer.GetLengthWritten()); |
| |
| exit: |
| return err; |
| } |
| |
| DLL_EXPORT CHIP_ERROR NewRootX509Cert(const X509CertRequestParams & requestParams, Crypto::P256Keypair & issuerKeypair, |
| MutableByteSpan & x509Cert) |
| { |
| ReturnErrorCodeIf(requestParams.HasNodeID, CHIP_ERROR_INVALID_ARGUMENT); |
| return NewChipX509Cert(requestParams, kIssuerIsRootCA, requestParams.Issuer, issuerKeypair.Pubkey(), issuerKeypair, x509Cert); |
| } |
| |
| DLL_EXPORT CHIP_ERROR NewICAX509Cert(const X509CertRequestParams & requestParams, uint64_t subject, |
| const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, |
| MutableByteSpan & x509Cert) |
| { |
| ReturnErrorCodeIf(requestParams.HasNodeID, CHIP_ERROR_INVALID_ARGUMENT); |
| return NewChipX509Cert(requestParams, kIssuerIsRootCA, subject, subjectPubkey, issuerKeypair, x509Cert); |
| } |
| |
| DLL_EXPORT CHIP_ERROR NewNodeOperationalX509Cert(const X509CertRequestParams & requestParams, CertificateIssuerLevel issuerLevel, |
| const Crypto::P256PublicKey & subjectPubkey, Crypto::P256Keypair & issuerKeypair, |
| MutableByteSpan & x509Cert) |
| { |
| VerifyOrReturnError(requestParams.HasNodeID, CHIP_ERROR_INVALID_ARGUMENT); |
| VerifyOrReturnError(requestParams.HasFabricID, CHIP_ERROR_INVALID_ARGUMENT); |
| return NewChipX509Cert(requestParams, issuerLevel, requestParams.NodeID, subjectPubkey, issuerKeypair, x509Cert); |
| } |
| |
| } // namespace Credentials |
| } // namespace chip |