| // 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/cms_test_helper.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <memory> | |
| #include <string> | |
| #include "absl/log/absl_check.h" | |
| #include "absl/strings/escaping.h" | |
| #include "absl/strings/str_cat.h" | |
| #include "absl/strings/string_view.h" | |
| #include "crypto/default/cms/cms_parser.h" | |
| #include "openssl/base.h" | |
| #include "openssl/bio.h" | |
| #include "openssl/bytestring.h" | |
| #include "openssl/obj.h" | |
| #include "openssl/pem.h" | |
| #include "openssl/x509.h" | |
| #include "testing/test_file_utils.h" | |
| namespace credentio_cms { | |
| constexpr absl::string_view kTestRepoDir = "credentio/"; | |
| std::string GetRawFileContents(const std::string& filename) { | |
| std::string path = | |
| absl::StrCat(kTestRepoDir, "crypto/default/cms/testdata/", filename); | |
| auto contents = credentio_testing::GetContents(path); | |
| ABSL_CHECK(contents.ok()) | |
| << "Failed to load: " << path << ", " << contents.status(); | |
| return *contents; | |
| } | |
| std::string GetFileContents(const std::string& filename) { | |
| std::string contents = GetRawFileContents(filename); | |
| // Normalize line endings by replacing \n with \r\n when needed. | |
| std::string normalized_contents; | |
| bool has_cr = false; | |
| for (char c : contents) { | |
| if (c == '\r') { | |
| has_cr = true; | |
| } else { | |
| if (c == '\n' && !has_cr) { | |
| normalized_contents += '\r'; | |
| } | |
| has_cr = false; | |
| } | |
| normalized_contents += c; | |
| } | |
| return normalized_contents; | |
| } | |
| // Quick and dirty functions to extract the CMS object. It probably only works | |
| // on the test cases. | |
| std::string GetCms(const std::string& message) { | |
| auto pos = message.find("name=\"smime.p7s\""); | |
| if (pos == std::string::npos) { | |
| pos = message.find("name=\"smime.p7m\""); | |
| } | |
| if (pos == std::string::npos) { | |
| pos = message.find("name=smime.p7s"); | |
| } | |
| if (pos == std::string::npos) { | |
| pos = message.find("name=smime.p7m"); | |
| } | |
| if (pos == std::string::npos) { | |
| pos = message.find("name=smime.p7c"); | |
| } | |
| ABSL_CHECK(pos != std::string::npos); | |
| auto start = message.find("\x0d\x0a\x0d\x0a", pos); | |
| ABSL_CHECK(start != std::string::npos); | |
| start += 2; | |
| auto end = message.find('-', start); | |
| if (end == std::string::npos) { | |
| end = message.find("\x0d\x0a\x0d\x0a", start); | |
| } | |
| if (end == std::string::npos) { | |
| end = message.size(); | |
| } | |
| std::string decoded; | |
| ABSL_CHECK( | |
| absl::Base64Unescape(message.substr(start, end - start), &decoded)); | |
| return decoded; | |
| } | |
| std::string GetFirstMimePart(const std::string& contents) { | |
| const char kBoundary[] = "boundary=\""; | |
| const char kUnquotedBoundary[] = "boundary="; | |
| int boundary_start = contents.find(kBoundary); | |
| int boundary_end; | |
| if (boundary_start == std::string::npos) { | |
| boundary_start = contents.find(kUnquotedBoundary); | |
| ABSL_CHECK_NE(std::string::npos, boundary_start); | |
| boundary_start += sizeof(kUnquotedBoundary) - 1; | |
| boundary_end = contents.find_first_of(";\r", boundary_start); | |
| } else { | |
| boundary_start += sizeof(kBoundary) - 1; | |
| boundary_end = contents.find('\"', boundary_start); | |
| } | |
| ABSL_CHECK_NE(std::string::npos, boundary_end); | |
| std::string boundary = | |
| "--" + contents.substr(boundary_start, boundary_end - boundary_start); | |
| int part_start = contents.find(boundary, boundary_end); | |
| ABSL_CHECK_NE(std::string::npos, part_start); | |
| part_start = contents.find("\r\n", part_start + boundary.size()); | |
| ABSL_CHECK_NE(std::string::npos, part_start); | |
| part_start += 2; | |
| int part_end = contents.find(boundary, part_start); | |
| ABSL_CHECK_NE(std::string::npos, part_end); | |
| ABSL_CHECK_GT(part_end, part_start + 2); | |
| // String the CRLF that is right before the boundary end. | |
| return contents.substr(part_start, part_end - part_start - 2); | |
| } | |
| // Convert an ASN.1 X509 name to a single line string. | |
| std::string CBSToName(const ByteString& raw_name) { | |
| X509_NAME* name = nullptr; | |
| const unsigned char* p = CBS_data(raw_name.cbs_ptr()); | |
| d2i_X509_NAME(&name, &p, CBS_len(raw_name.cbs_ptr())); | |
| std::string line; | |
| BIO* bio = BIO_new(BIO_s_mem()); | |
| X509_NAME_print_ex(bio, name, 0 /* indent */, 0 /* flags */); | |
| int size = BIO_pending(bio); | |
| std::unique_ptr<char[]> buffer(new char[size]); | |
| ABSL_CHECK_EQ(size, BIO_read(bio, buffer.get(), size)); | |
| line.assign(&buffer[0], size); | |
| X509_NAME_free(name); | |
| BIO_free_all(bio); | |
| return line; | |
| } | |
| X509* PemToCertificate(const std::string& pem) { | |
| bssl::UniquePtr<BIO> bio( | |
| BIO_new_mem_buf(const_cast<char*>(pem.data()), pem.size())); | |
| return PEM_read_bio_X509(bio.get(), nullptr /* existing key */, | |
| nullptr /* password callback */, | |
| nullptr /* password */); | |
| } | |
| EVP_PKEY* PemToKey(const std::string& pem) { | |
| bssl::UniquePtr<BIO> bio( | |
| BIO_new_mem_buf(const_cast<char*>(pem.data()), pem.size())); | |
| return PEM_read_bio_PrivateKey(bio.get(), nullptr /* existing key */, | |
| nullptr /* password callback */, | |
| nullptr /* password */); | |
| } | |
| std::string NidToDerString(int nid) { | |
| // Encode the OID as an ASN.1 OBJECT. | |
| CBB cbb; | |
| ABSL_CHECK(CBB_init(&cbb, 0)); | |
| ABSL_CHECK(OBJ_nid2cbb(&cbb, nid)); | |
| CBS asn1_cbs; | |
| CBS_init(&asn1_cbs, CBB_data(&cbb), CBB_len(&cbb)); | |
| // Copy the raw OID into the return value and discard the ASN.1 headers. | |
| CBS raw_oid_cbs; | |
| ABSL_CHECK(CBS_get_asn1(&asn1_cbs, &raw_oid_cbs, CBS_ASN1_OBJECT)); | |
| size_t raw_oid_len = CBS_len(&raw_oid_cbs); | |
| std::string ret(raw_oid_len, '\0'); | |
| ABSL_CHECK(CBS_copy_bytes( | |
| &raw_oid_cbs, reinterpret_cast<uint8_t*>(ret.data()), raw_oid_len)); | |
| CBB_cleanup(&cbb); | |
| return ret; | |
| } | |
| } // namespace credentio_cms |