| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromeos/ash/components/kcer/helpers/pkcs12_validator.h" |
| |
| #include "base/compiler_specific.h" |
| #include "base/containers/span.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "chromeos/ash/components/kcer/kcer_nss/test_utils.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/test/cert_builder.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace kcer::internal { |
| namespace { |
| |
| std::string GetPassword(const std::string& file_name) { |
| if (file_name == "client.p12") { |
| return "12345"; |
| } |
| if (file_name == "client_with_ec_key.p12") { |
| return "123456"; |
| } |
| ADD_FAILURE() << "GetPassword() is called with an unexpected file name"; |
| return ""; |
| } |
| |
| scoped_refptr<const Cert> MakeKcerCert( |
| const std::string& nickname, |
| scoped_refptr<net::X509Certificate> cert) { |
| // CertCache only cares about the `cert`, other fields are can be anything. |
| return base::MakeRefCounted<Cert>(Token::kUser, Pkcs11Id(), nickname, |
| std::move(cert)); |
| } |
| |
| scoped_refptr<const Cert> MakeKcerCertFromBsslCert(const std::string& nickname, |
| X509* cert) { |
| int cert_der_size = 0; |
| bssl::UniquePtr<uint8_t> cert_der; |
| Pkcs12Reader pkcs12_reader; |
| Pkcs12ReaderStatusCode get_cert_der_result = |
| pkcs12_reader.GetDerEncodedCert(cert, cert_der, cert_der_size); |
| if (get_cert_der_result != Pkcs12ReaderStatusCode::kSuccess) { |
| ADD_FAILURE() << "GetDerEncodedCert failed"; |
| return nullptr; |
| } |
| |
| scoped_refptr<net::X509Certificate> x509_cert = |
| net::X509Certificate::CreateFromBytes(UNSAFE_TODO(base::span( |
| cert_der.get(), base::checked_cast<size_t>(cert_der_size)))); |
| return MakeKcerCert("name", x509_cert); |
| } |
| |
| class KcerPkcs12ValidatorTest : public ::testing::Test { |
| public: |
| void GetData(const char* file_name, |
| KeyData& key_data, |
| bssl::UniquePtr<STACK_OF(X509)>& certs) { |
| Pkcs12Blob pkcs12_blob(ReadTestFile(file_name)); |
| std::string password(GetPassword(file_name)); |
| |
| Pkcs12ReaderStatusCode get_key_and_cert_status = |
| pkcs12_reader_.GetPkcs12KeyAndCerts(pkcs12_blob.value(), password, |
| key_data.key, certs); |
| if (get_key_and_cert_status != Pkcs12ReaderStatusCode::kSuccess) { |
| ADD_FAILURE() << "GetPkcs12KeyAndCerts failed"; |
| return; |
| } |
| |
| if (pkcs12_reader_.EnrichKeyData(key_data) != |
| Pkcs12ReaderStatusCode::kSuccess) { |
| ADD_FAILURE() << "EnrichKeyData failed"; |
| return; |
| } |
| } |
| |
| Pkcs12Reader pkcs12_reader_; |
| CertCache cert_cache_; |
| }; |
| |
| // Test that ValidateAndPrepareCertData() returns success on validating a |
| // correct RSA PKCS#12 data and chooses the correct nickname. |
| TEST_F(KcerPkcs12ValidatorTest, RsaSuccess) { |
| KeyData key_data; |
| bssl::UniquePtr<STACK_OF(X509)> certs; |
| ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs)); |
| |
| std::vector<CertData> certs_data; |
| Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData( |
| cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data); |
| EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess); |
| ASSERT_EQ(certs_data.size(), 1u); |
| // The file doesn't have the nickname set and there are no other certs to copy |
| // it from, so the subject name should be used. |
| EXPECT_EQ(certs_data[0].nickname, "testusercert"); |
| } |
| |
| // Test that ValidateAndPrepareCertData() returns success on validating a |
| // correct EC PKCS#12 data and chooses the correct nickname. |
| TEST_F(KcerPkcs12ValidatorTest, EcSuccess) { |
| KeyData key_data; |
| bssl::UniquePtr<STACK_OF(X509)> certs; |
| ASSERT_NO_FATAL_FAILURE(GetData("client_with_ec_key.p12", key_data, certs)); |
| |
| std::vector<CertData> certs_data; |
| Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData( |
| cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data); |
| EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess); |
| ASSERT_EQ(certs_data.size(), 1u); |
| // There are no other certs to copy the nickname from, the file has the |
| // nickname set and it should be used. |
| EXPECT_EQ(certs_data[0].nickname, "serverkey"); |
| } |
| |
| // Test that ValidateAndPrepareCertData() correctly fails when the key and the |
| // certs are not related. |
| TEST_F(KcerPkcs12ValidatorTest, UnrelatedCert) { |
| KeyData key_data_1; |
| bssl::UniquePtr<STACK_OF(X509)> certs_1; |
| ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data_1, certs_1)); |
| |
| KeyData key_data_2; |
| bssl::UniquePtr<STACK_OF(X509)> certs_2; |
| ASSERT_NO_FATAL_FAILURE( |
| GetData("client_with_ec_key.p12", key_data_2, certs_2)); |
| |
| std::vector<CertData> certs_data; |
| Pkcs12ReaderStatusCode prepare_certs_status; |
| |
| // `certs_1` and `key_data_2` don't match and should fail. |
| prepare_certs_status = ValidateAndPrepareCertData( |
| cert_cache_, pkcs12_reader_, std::move(certs_1), key_data_2, certs_data); |
| EXPECT_EQ(prepare_certs_status, |
| Pkcs12ReaderStatusCode::kPkcs12NoValidCertificatesFound); |
| |
| // `certs_2` and `key_data_1` don't match and should fail. |
| prepare_certs_status = ValidateAndPrepareCertData( |
| cert_cache_, pkcs12_reader_, std::move(certs_2), key_data_1, certs_data); |
| EXPECT_EQ(prepare_certs_status, |
| Pkcs12ReaderStatusCode::kPkcs12NoValidCertificatesFound); |
| } |
| |
| // Test that ValidateAndPrepareCertData() skips already imported keys and |
| // correctly fails if there's nothing new to import. |
| TEST_F(KcerPkcs12ValidatorTest, CertExists) { |
| KeyData key_data; |
| bssl::UniquePtr<STACK_OF(X509)> certs; |
| ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs)); |
| |
| X509* cert = sk_X509_value(certs.get(), 0); |
| ASSERT_TRUE(cert); |
| |
| scoped_refptr<const Cert> kcer_cert = MakeKcerCertFromBsslCert("name", cert); |
| cert_cache_ = CertCache(base::span_from_ref(kcer_cert)); |
| |
| std::vector<CertData> certs_data; |
| Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData( |
| cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data); |
| EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kAlreadyExists); |
| } |
| |
| // Test that ValidateAndPrepareCertData() takes the nickname from an existing |
| // cert when it has the same subject name as the one for import. (This is |
| // implemented for backwards compatibility with NSS, potentially doesn't have to |
| // stay this way long term.) |
| TEST_F(KcerPkcs12ValidatorTest, NicknameFromExistingCert) { |
| KeyData key_data; |
| bssl::UniquePtr<STACK_OF(X509)> certs; |
| ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs)); |
| |
| X509* cert = sk_X509_value(certs.get(), 0); |
| ASSERT_TRUE(cert); |
| |
| base::span<const uint8_t> subject_der; |
| Pkcs12ReaderStatusCode get_subject_der_result = |
| pkcs12_reader_.GetSubjectNameDer(cert, subject_der); |
| ASSERT_EQ(get_subject_der_result, Pkcs12ReaderStatusCode::kSuccess); |
| |
| std::vector<std::unique_ptr<net::CertBuilder>> cert_builders = |
| net::CertBuilder::CreateSimpleChain(/*chain_length=*/1); |
| cert_builders[0]->SetSubjectTLV(subject_der); |
| |
| const char kNickname[] = "nickname123"; |
| scoped_refptr<const Cert> kcer_cert = |
| MakeKcerCert(kNickname, cert_builders[0]->GetX509Certificate()); |
| cert_cache_ = CertCache(base::span_from_ref(kcer_cert)); |
| |
| std::vector<CertData> certs_data; |
| Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData( |
| cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data); |
| EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess); |
| ASSERT_EQ(certs_data.size(), 1u); |
| EXPECT_EQ(certs_data[0].nickname, kNickname); |
| } |
| |
| // Test that ValidateAndPrepareCertData() will generate a unique nickname when |
| // the default option is already taken. |
| TEST_F(KcerPkcs12ValidatorTest, UniqueNickname) { |
| KeyData key_data; |
| bssl::UniquePtr<STACK_OF(X509)> certs; |
| ASSERT_NO_FATAL_FAILURE(GetData("client.p12", key_data, certs)); |
| |
| X509* cert = sk_X509_value(certs.get(), 0); |
| ASSERT_TRUE(cert); |
| |
| std::vector<std::unique_ptr<net::CertBuilder>> cert_builders = |
| net::CertBuilder::CreateSimpleChain(/*chain_length=*/1); |
| |
| const char kNickname[] = "testusercert"; |
| scoped_refptr<const Cert> kcer_cert = |
| MakeKcerCert(kNickname, cert_builders[0]->GetX509Certificate()); |
| cert_cache_ = CertCache(base::span_from_ref(kcer_cert)); |
| |
| std::vector<CertData> certs_data; |
| Pkcs12ReaderStatusCode prepare_certs_status = ValidateAndPrepareCertData( |
| cert_cache_, pkcs12_reader_, std::move(certs), key_data, certs_data); |
| EXPECT_EQ(prepare_certs_status, Pkcs12ReaderStatusCode::kSuccess); |
| ASSERT_EQ(certs_data.size(), 1u); |
| EXPECT_EQ(certs_data[0].nickname, std::string(kNickname) + " 1"); |
| } |
| |
| } // namespace |
| } // namespace kcer::internal |