| // 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/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 = "c2pa/"; |
| |
| std::string GetRawFileContents(const std::string& filename) { |
| std::string path = |
| absl::StrCat(kTestRepoDir, "crypto/default/cms/testdata/", filename); |
| auto contents = credentio_testing::GetContents(path); |
| 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"); |
| } |
| CHECK(pos != std::string::npos); |
| auto start = message.find("\x0d\x0a\x0d\x0a", pos); |
| 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; |
| 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); |
| 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); |
| } |
| 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); |
| CHECK_NE(std::string::npos, part_start); |
| part_start = contents.find("\r\n", part_start + boundary.size()); |
| CHECK_NE(std::string::npos, part_start); |
| part_start += 2; |
| int part_end = contents.find(boundary, part_start); |
| CHECK_NE(std::string::npos, part_end); |
| 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]); |
| 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; |
| CHECK(CBB_init(&cbb, 0)); |
| 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; |
| 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'); |
| CHECK(CBS_copy_bytes(&raw_oid_cbs, reinterpret_cast<uint8_t*>(ret.data()), |
| raw_oid_len)); |
| CBB_cleanup(&cbb); |
| return ret; |
| } |
| |
| } // namespace credentio_cms |