| // Copyright 2015 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "webservd/utils.h" |
| |
| #include <sys/socket.h> |
| |
| #include <openssl/bio.h> |
| #include <openssl/evp.h> |
| #include <openssl/pem.h> |
| #include <openssl/rsa.h> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/time/time.h> |
| |
| namespace webservd { |
| |
| namespace { |
| |
| // Returns the current date/time. This is used for TLS certificate validation |
| // very early in process start when the system clock might not be adjusted |
| // yet on devices that don't have a real-time clock. So, try to get the system |
| // time and if it is earlier than the build date of this executable, use |
| // the build date instead as a lower limit to the date/time. |
| // See http://b/23897170 for more details. |
| base::Time GetTimeNow() { |
| base::Time now = base::Time::Now(); |
| |
| base::File::Info info; |
| base::FilePath exe_path; |
| if (!base::ReadSymbolicLink(base::FilePath{"/proc/self/exe"}, &exe_path) || |
| !base::GetFileInfo(exe_path, &info) || info.creation_time < now) { |
| return now; |
| } |
| |
| LOG(WARNING) << "Current time (" << now |
| << ") is earlier than the application build time. Using " |
| << info.creation_time << " instead!"; |
| return info.creation_time; |
| } |
| |
| } // anonymous namespace |
| |
| X509Ptr CreateCertificate(int serial_number, |
| const base::TimeDelta& cert_expiration, |
| const std::string& common_name) { |
| auto cert = X509Ptr{X509_new(), X509_free}; |
| CHECK(cert.get()); |
| X509_set_version(cert.get(), 2); // Using X.509 v3 certificate... |
| |
| // Set certificate properties... |
| ASN1_INTEGER* sn = X509_get_serialNumber(cert.get()); |
| ASN1_INTEGER_set(sn, serial_number); |
| time_t current_time = GetTimeNow().ToTimeT(); |
| X509_time_adj(X509_get_notBefore(cert.get()), 0, ¤t_time); |
| X509_time_adj(X509_get_notAfter(cert.get()), cert_expiration.InSeconds(), |
| ¤t_time); |
| |
| // The issuer is the same as the subject, since this cert is self-signed. |
| X509_NAME* name = X509_get_subject_name(cert.get()); |
| if (!common_name.empty()) { |
| X509_NAME_add_entry_by_txt( |
| name, "CN", MBSTRING_UTF8, |
| reinterpret_cast<const unsigned char*>(common_name.c_str()), |
| common_name.length(), -1, 0); |
| } |
| X509_set_issuer_name(cert.get(), name); |
| return cert; |
| } |
| |
| std::unique_ptr<RSA, void (*)(RSA*)> GenerateRSAKeyPair(int key_length_bits) { |
| // Create RSA key pair. |
| auto rsa_key_pair = std::unique_ptr<RSA, void (*)(RSA*)>{RSA_new(), RSA_free}; |
| CHECK(rsa_key_pair.get()); |
| auto big_num = std::unique_ptr<BIGNUM, void (*)(BIGNUM*)>{BN_new(), BN_free}; |
| CHECK(big_num.get()); |
| CHECK(BN_set_word(big_num.get(), 65537)); |
| CHECK(RSA_generate_key_ex(rsa_key_pair.get(), key_length_bits, big_num.get(), |
| nullptr)); |
| return rsa_key_pair; |
| } |
| |
| brillo::SecureBlob StoreRSAPrivateKey(RSA* rsa_key_pair) { |
| auto bio = |
| std::unique_ptr<BIO, void (*)(BIO*)>{BIO_new(BIO_s_mem()), BIO_vfree}; |
| CHECK(bio); |
| CHECK(PEM_write_bio_RSAPrivateKey(bio.get(), rsa_key_pair, nullptr, nullptr, |
| 0, nullptr, nullptr)); |
| uint8_t* buffer = nullptr; |
| size_t size = BIO_get_mem_data(bio.get(), reinterpret_cast<char**>(&buffer)); |
| CHECK_GT(size, 0u); |
| CHECK(buffer); |
| brillo::SecureBlob key_blob(buffer, buffer + size); |
| brillo::SecureClearBytes(buffer, size); |
| return key_blob; |
| } |
| |
| bool ValidateRSAPrivateKey(const brillo::SecureBlob& key) { |
| std::unique_ptr<BIO, void (*)(BIO*)> bio{ |
| BIO_new_mem_buf(const_cast<uint8_t*>(key.data()), key.size()), BIO_vfree}; |
| std::unique_ptr<RSA, void (*)(RSA*)> rsa_key{ |
| PEM_read_bio_RSAPrivateKey(bio.get(), nullptr, nullptr, nullptr), |
| RSA_free}; |
| return rsa_key.get() != nullptr; |
| } |
| |
| brillo::Blob StoreCertificate(X509* cert) { |
| auto bio = |
| std::unique_ptr<BIO, void (*)(BIO*)>{BIO_new(BIO_s_mem()), BIO_vfree}; |
| CHECK(bio); |
| CHECK(PEM_write_bio_X509(bio.get(), cert)); |
| uint8_t* buffer = nullptr; |
| size_t size = BIO_get_mem_data(bio.get(), reinterpret_cast<char**>(&buffer)); |
| CHECK_GT(size, 0u); |
| CHECK(buffer); |
| return brillo::Blob(buffer, buffer + size); |
| } |
| |
| bool StoreCertificate(X509* cert, const base::FilePath& file) { |
| std::unique_ptr<BIO, void (*)(BIO*)> bio{ |
| BIO_new_file(file.value().c_str(), "w"), BIO_vfree}; |
| return bio && PEM_write_bio_X509(bio.get(), cert) != 0; |
| } |
| |
| X509Ptr LoadAndValidateCertificate(const base::FilePath& file) { |
| X509Ptr cert{nullptr, X509_free}; |
| std::unique_ptr<BIO, void (*)(BIO*)> bio{ |
| BIO_new_file(file.value().c_str(), "r"), BIO_vfree}; |
| if (!bio) |
| return cert; |
| LOG(INFO) << "Loading certificate from " << file.value(); |
| cert.reset(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr)); |
| if (cert) { |
| // Regenerate certificate 30 days before it expires. |
| time_t deadline = (GetTimeNow() + base::TimeDelta::FromDays(30)).ToTimeT(); |
| if (X509_cmp_time(X509_get_notAfter(cert.get()), &deadline) < 0) { |
| LOG(WARNING) << "Certificate is expiring soon. Regenerating new one."; |
| cert.reset(); |
| } |
| } |
| return cert; |
| } |
| |
| // Same as openssl x509 -fingerprint -sha256. |
| brillo::Blob GetSha256Fingerprint(X509* cert) { |
| brillo::Blob fingerprint(256 / 8); |
| uint32_t len = 0; |
| CHECK(X509_digest(cert, EVP_sha256(), fingerprint.data(), &len)); |
| CHECK_EQ(len, fingerprint.size()); |
| return fingerprint; |
| } |
| |
| int CreateNetworkInterfaceSocket(const std::string& if_name) { |
| // The following is basically the steps libmicrohttpd normally takes |
| // when creating a new listening socket and binding it to a port. |
| int socket_fd = socket(PF_INET6, SOCK_STREAM | SOCK_CLOEXEC, 0); |
| if (socket_fd < 0 && errno == EINVAL) |
| socket_fd = socket(PF_INET6, SOCK_STREAM, 0); |
| if (socket_fd < 0) { |
| PLOG(ERROR) << "Unable to create a listening socket"; |
| return -1; |
| } |
| |
| // Now, specify that we want this socket to bind only to a particular |
| // network interface. Note, this call requires root privileges, so this |
| // should be done before the privileges are dropped. |
| if (setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, if_name.c_str(), |
| if_name.size()) < 0) { |
| PLOG(WARNING) << "Failed to bind socket (SO_BINDTODEVICE) to " << if_name; |
| close(socket_fd); |
| return -1; |
| } |
| return socket_fd; |
| } |
| |
| } // namespace webservd |