| // 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 |