blob: d224183fe755813e10807f3d6599fb40c721cf39 [file]
// 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