| // Copyright 2026 Google LLC | |
| // | |
| // 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 | |
| // | |
| // https://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. | |
| // | |
| #include "crypto/default/cms/certificates.h" | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include "absl/log/check.h" | |
| #include "absl/log/die_if_null.h" | |
| #include "absl/strings/escaping.h" | |
| #include "crypto/default/cms/cms_error_code.h" | |
| #include "crypto/default/cms/cms_parser.h" | |
| #include "crypto/default/cms/cms_test_helper.h" | |
| #include "gmock/gmock.h" | |
| #include "gtest/gtest.h" | |
| #include "openssl/base.h" | |
| #include "openssl/bio.h" | |
| #include "openssl/bytestring.h" | |
| #include "openssl/safestack.h" | |
| #include "openssl/stack.h" | |
| #include "openssl/x509.h" | |
| using testing::ElementsAre; | |
| namespace credentio_cms { | |
| namespace { | |
| std::string X509ToDer(X509* x509) { | |
| const int len = i2d_X509(x509, nullptr /* buffer */); | |
| CHECK_GT(len, 0); | |
| std::unique_ptr<char[]> data(new char[len]); | |
| unsigned char* dataptr = reinterpret_cast<unsigned char*>(data.get()); | |
| const int len2 = i2d_X509(x509, &dataptr); | |
| CHECK_EQ(len, len2); | |
| std::string der_output; | |
| der_output.assign(data.get(), len); | |
| return der_output; | |
| } | |
| ByteString ByteStringFromString(const std::string& value) { | |
| ByteString bs; | |
| CBS_init(bs.cbs_ptr(), reinterpret_cast<const unsigned char*>(value.data()), | |
| value.length()); | |
| return bs; | |
| } | |
| class CertificateTest : public testing::Test { | |
| protected: | |
| void SetUp() override { | |
| user1_rsa_.reset( | |
| ABSL_DIE_IF_NULL(PemToCertificate(GetFileContents("user1_both.pem")))); | |
| user1_ecc_.reset( | |
| ABSL_DIE_IF_NULL(PemToCertificate(GetFileContents("user1_ecc.pem")))); | |
| user2_rsa_.reset( | |
| ABSL_DIE_IF_NULL(PemToCertificate(GetFileContents("user2_both.pem")))); | |
| user1_rsa_der_ = X509ToDer(user1_rsa_.get()); | |
| user1_ecc_der_ = X509ToDer(user1_ecc_.get()); | |
| user2_rsa_der_ = X509ToDer(user2_rsa_.get()); | |
| content_.certificates.push_back(ByteStringFromString(user1_rsa_der_)); | |
| content_.certificates.push_back(ByteStringFromString(user1_ecc_der_)); | |
| content_.certificates.push_back(ByteStringFromString(user2_rsa_der_)); | |
| test_stack_ = sk_X509_new_null(); | |
| sk_X509_push(test_stack_, user1_rsa_.get()); | |
| sk_X509_push(test_stack_, user1_ecc_.get()); | |
| sk_X509_push(test_stack_, user2_rsa_.get()); | |
| } | |
| void TearDown() override { | |
| if (test_stack_ != nullptr) { | |
| sk_X509_free(test_stack_); | |
| } | |
| } | |
| STACK_OF(X509) * test_stack_ = nullptr; | |
| Content content_; | |
| bssl::UniquePtr<X509> user1_rsa_; | |
| bssl::UniquePtr<X509> user1_ecc_; | |
| bssl::UniquePtr<X509> user2_rsa_; | |
| std::string user1_rsa_der_; | |
| std::string user1_ecc_der_; | |
| std::string user2_rsa_der_; | |
| }; | |
| TEST_F(CertificateTest, GetAllCertificates) { | |
| bssl::UniquePtr<STACK_OF(X509)> stack(GetAllCertificates(content_)); | |
| ASSERT_EQ(3, sk_X509_num(stack.get())); | |
| EXPECT_EQ(0, X509_cmp(sk_X509_value(stack.get(), 0), user1_rsa_.get())); | |
| EXPECT_EQ(0, X509_cmp(sk_X509_value(stack.get(), 1), user1_ecc_.get())); | |
| EXPECT_EQ(0, X509_cmp(sk_X509_value(stack.get(), 2), user2_rsa_.get())); | |
| } | |
| TEST_F(CertificateTest, GetByName) { | |
| X509_NAME* issuer = X509_get_issuer_name(user1_rsa_.get()); | |
| const int len = i2d_X509_NAME(issuer, nullptr /* buffer */); | |
| CHECK_GT(len, 0); | |
| std::unique_ptr<char[]> data(new char[len]); | |
| unsigned char* dataptr = reinterpret_cast<unsigned char*>(data.get()); | |
| const int len2 = i2d_X509_NAME(issuer, &dataptr); | |
| CHECK_EQ(len, len2); | |
| std::string name_der; | |
| name_der.assign(data.get(), len); | |
| SignerInfo signer_info; | |
| signer_info.issuer_name = ByteStringFromString(name_der); | |
| std::string serial = "\x12\x3b"; | |
| signer_info.serial_number = ByteStringFromString(serial); | |
| std::vector<X509*> matching = GetSignerCertificates(test_stack_, signer_info); | |
| EXPECT_THAT(matching, ElementsAre(user1_ecc_.get())); | |
| CHECK(absl::HexStringToBytes("123f", &serial)); | |
| signer_info.serial_number = ByteStringFromString(serial); | |
| matching = GetSignerCertificates(test_stack_, signer_info); | |
| EXPECT_THAT(matching, ElementsAre()); | |
| } | |
| TEST_F(CertificateTest, GetBySki) { | |
| SignerInfo signer_info; | |
| std::string ski; | |
| CHECK( | |
| absl::HexStringToBytes("9B6EAE67945D61FB736371448CE0F2ECBA43CBA0", &ski)); | |
| signer_info.subject_key_identifier = ByteStringFromString(ski); | |
| std::vector<X509*> matching = GetSignerCertificates(test_stack_, signer_info); | |
| EXPECT_THAT(matching, ElementsAre(user1_ecc_.get())); | |
| } | |
| TEST_F(CertificateTest, RFCSkiExample) { | |
| // The RFC 4134 4.7 example uses the Subject Key Id to match the signer's | |
| // certificate. | |
| std::string cms = GetCms(GetFileContents("rfc4134-4.7.msg")); | |
| Content contents; | |
| std::string error_message; | |
| ASSERT_EQ(ErrorCode::OK, | |
| ParseCms(cms.data(), cms.length(), &contents, &error_message)) | |
| << error_message; | |
| bssl::UniquePtr<STACK_OF(X509)> certs(GetAllCertificates(contents)); | |
| ASSERT_EQ(1, sk_X509_num(certs.get())); | |
| ASSERT_EQ(1, contents.signers.size()); | |
| std::vector<X509*> matching = | |
| GetSignerCertificates(certs.get(), contents.signers[0]); | |
| EXPECT_THAT(matching, ElementsAre(sk_X509_value(certs.get(), 0))); | |
| } | |
| TEST_F(CertificateTest, GetAllCertificatesWithEmptyCert) { | |
| // Zero out the 2nd certificate in the stack. | |
| CBS_init(content_.certificates[1].cbs_ptr(), nullptr, 0); | |
| bssl::UniquePtr<STACK_OF(X509)> stack(GetAllCertificates(content_)); | |
| ASSERT_TRUE(stack == nullptr); | |
| } | |
| TEST_F(CertificateTest, GetAllCertificatesWithBrokenCert) { | |
| // A valid X509 certificate should start with a 0x30 (Constructed Sequence) | |
| // tag. | |
| uint8_t kBrokenCert[] = {0xFF}; | |
| // Make the second certificate in the stack broken. | |
| CBS_init(content_.certificates[1].cbs_ptr(), kBrokenCert, | |
| sizeof(kBrokenCert)); | |
| bssl::UniquePtr<STACK_OF(X509)> stack(GetAllCertificates(content_)); | |
| ASSERT_TRUE(stack == nullptr); | |
| } | |
| } // namespace | |
| } // namespace credentio_cms |