| // Copyright (c) 2013 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 <limits> |
| #include <string> |
| #include <vector> |
| #include <unistd.h> |
| |
| #include <base/command_line.h> |
| #include <base/logging.h> |
| #include <base/memory/scoped_ptr.h> |
| #include <base/posix/eintr_wrapper.h> |
| #include <chromeos/syslog_logging.h> |
| #include <openssl/bio.h> |
| #include <openssl/conf.h> |
| #include <openssl/err.h> |
| #include <openssl/evp.h> |
| #include <openssl/pem.h> |
| #include <openssl/rsa.h> |
| #include <openssl/sha.h> |
| #include <openssl/x509.h> |
| |
| #include "shill/proto_bindings/shims/protos/crypto_util.pb.h" |
| |
| using shill_protos::EncryptDataMessage; |
| using shill_protos::EncryptDataResponse; |
| using shill_protos::VerifyCredentialsMessage; |
| using shill_protos::VerifyCredentialsResponse; |
| using std::numeric_limits; |
| using std::string; |
| using std::vector; |
| |
| namespace { |
| |
| const char kTrustedCAModulus[] = |
| "BC2280BD80F63A21003BAE765E357F3DC3645C559486342F058728CDF7698C17B350A7B8" |
| "82FADFC7432DD67EABA06FB7137280A44715C1209950CDEC1462095BA498CDD241B6364E" |
| "FFE82E32304A81A842A36C9B336ECAB2F55366E02753861A851EA7393F4A778EFB546666" |
| "FB5854C05E39C7F550060BE08AD4CEE16A551F8B1700E669A327E60825693C129D8D052C" |
| "D62EA231DEB45250D62049DE71A0F9AD204012F1DD25EBD5E6B836F4D68F7FCA43DCD710" |
| "5BE63F518A85B3F3FFF6032DCB234F9CAD18E793058CAC529AF74CE9997ABE6E7E4D0AE3" |
| "C61CA993FA3AA5915D1CBD66EBCC60DC8674CACFF8921C987D57FA61479EAB80B7E44880" |
| "2A92C51B"; |
| const char kCommandVerify[] = "verify"; |
| const char kCommandEncrypt[] = "encrypt"; |
| const size_t kMacLength = 12; |
| |
| // Encrypt |data| with |public_key|. |public_key| is the raw bytes of a key in |
| // RSAPublicKey format. |data| is some string of bytes smaller than the |
| // maximum length permissable for encryption with a key of |public_key| size. |
| // |rsa_ptr| should point to NULL (but should not be NULL). This function may |
| // set *|rsa_ptr| to an RSA object which should be freed in the caller. |
| // Returns the encrypted result in |encrypted_output| and returns true on |
| // success. Returns false on failure. |
| bool EncryptByteStringImpl(const string &public_key, |
| const string &data, |
| RSA **rsa_ptr, |
| string *encrypted_output) { |
| CHECK(rsa_ptr); |
| CHECK(!*rsa_ptr); |
| CHECK(encrypted_output); |
| |
| // This pointer will be incremented internally by the parsing routine. |
| const unsigned char *throwaway_ptr = |
| reinterpret_cast<const unsigned char *>(public_key.data()); |
| *rsa_ptr = d2i_RSAPublicKey(NULL, &throwaway_ptr, public_key.length()); |
| RSA *rsa = *rsa_ptr; |
| if (!rsa) { |
| LOG(ERROR) << "Failed to parse public key."; |
| return false; |
| } |
| |
| scoped_array<unsigned char> rsa_output( |
| new unsigned char[RSA_size(rsa)]); |
| LOG(INFO) << "Encrypting data with public key."; |
| const int encrypted_length = RSA_public_encrypt( |
| data.length(), |
| // The API helpfully tells us that this operation will treat this buffer |
| // as read only, but fails to mark the parameter const. |
| reinterpret_cast<unsigned char *>(const_cast<char *>(data.data())), |
| rsa_output.get(), |
| rsa, |
| RSA_PKCS1_PADDING); |
| if (encrypted_length <= 0) { |
| LOG(ERROR) << "Error during encryption."; |
| return false; |
| } |
| |
| encrypted_output->assign(reinterpret_cast<char *>(rsa_output.get()), |
| encrypted_length); |
| return true; |
| } |
| |
| // Parse the EncryptDataMessage contained in |raw_input| and return an |
| // EncryptDataResponse in output on success. Returns true on success and |
| // false otherwise. |
| bool EncryptByteString(const string &raw_input, string *output) { |
| EncryptDataMessage message; |
| if (!message.ParseFromString(raw_input)) { |
| LOG(ERROR) << "Failed to read VerifyCredentialsMessage from stdin."; |
| return false; |
| } |
| |
| if (!message.has_public_key() || !message.has_data()) { |
| LOG(ERROR) << "Request lacked necessary fields."; |
| return false; |
| } |
| |
| RSA *rsa = NULL; |
| string encrypted_output; |
| bool operation_successful = EncryptByteStringImpl( |
| message.public_key(), message.data(), &rsa, &encrypted_output); |
| if (rsa) { |
| RSA_free(rsa); |
| rsa = NULL; |
| } |
| |
| if (operation_successful) { |
| LOG(INFO) << "Filling out protobuf."; |
| EncryptDataResponse response; |
| response.set_encrypted_data(encrypted_output); |
| response.set_ret(shill_protos::OK); |
| output->clear(); |
| LOG(INFO) << "Serializing protobuf."; |
| if (!response.SerializeToString(output)) { |
| LOG(ERROR) << "Failed while writing encrypted data."; |
| return false; |
| } |
| LOG(INFO) << "Encoding finished successfully."; |
| } |
| |
| return operation_successful; |
| } |
| |
| // Verify that the destination described by |certificate| is valid. |
| // |
| // 1) The MAC address listed in the certificate matches |connected_mac|. |
| // 2) The certificate is a valid PEM encoded certificate signed by our |
| // trusted CA. |
| // 3) |signed_data| matches the hashed |unsigned_data| encrypted with |
| // the public key in |certificate|. |
| // |
| // All pointers should be valid, but point to NULL values. Sets *ptr to |
| // NULL or a valid object which should be freed with the appropriate destructor |
| // upon completion. |
| bool VerifyCredentialsImpl(const string &certificate, |
| const string &signed_data, |
| const string &unsigned_data, |
| const string &connected_mac, |
| RSA **rsa_ptr, |
| EVP_PKEY **pkey_ptr, |
| BIO **raw_certificate_bio_ptr, |
| X509 **x509_ptr) { |
| CHECK(rsa_ptr); |
| CHECK(pkey_ptr); |
| CHECK(raw_certificate_bio_ptr); |
| CHECK(x509_ptr); |
| CHECK(!*rsa_ptr); |
| CHECK(!*pkey_ptr); |
| CHECK(!*raw_certificate_bio_ptr); |
| CHECK(!*x509_ptr); |
| |
| *rsa_ptr = RSA_new(); |
| RSA *rsa = *rsa_ptr; |
| *pkey_ptr = EVP_PKEY_new(); |
| EVP_PKEY *pkey = *pkey_ptr; |
| if (!rsa || !pkey) { |
| LOG(ERROR) << "Failed to allocate key."; |
| return false; |
| } |
| |
| rsa->e = BN_new(); |
| rsa->n = BN_new(); |
| if (!rsa->e || !rsa->n || |
| !BN_set_word(rsa->e, RSA_F4) || |
| !BN_hex2bn(&rsa->n, kTrustedCAModulus)) { |
| LOG(ERROR) << "Failed to allocate key pieces."; |
| return false; |
| } |
| |
| if (!EVP_PKEY_assign_RSA(pkey, rsa)) { |
| LOG(ERROR) << "Failed to assign RSA to PKEY."; |
| return false; |
| } |
| |
| *rsa_ptr = NULL; // pkey took ownership |
| // Another helpfully unmarked const interface. |
| *raw_certificate_bio_ptr = BIO_new_mem_buf( |
| const_cast<char *>(certificate.data()), certificate.length()); |
| BIO *raw_certificate_bio = *raw_certificate_bio_ptr; |
| if (!raw_certificate_bio) { |
| LOG(ERROR) << "Failed to allocate openssl certificate buffer."; |
| return false; |
| } |
| |
| // No callback for a passphrase, and no passphrase either. |
| *x509_ptr = PEM_read_bio_X509(raw_certificate_bio, NULL, NULL, NULL); |
| X509 *x509 = *x509_ptr; |
| if (!x509) { |
| LOG(ERROR) << "Failed to parse certificate."; |
| return false; |
| } |
| |
| if (X509_verify(x509, pkey) <= 0) { |
| LOG(ERROR) << "Failed to verify certificate."; |
| return false; |
| } |
| |
| // Check that the device listed in the certificate is correct. |
| char device_name[100]; // A longer CN will truncate. |
| const int device_name_length = X509_NAME_get_text_by_NID( |
| x509->cert_info->subject, |
| NID_commonName, |
| device_name, |
| arraysize(device_name)); |
| if (device_name_length == -1) { |
| LOG(ERROR) << "Subject invalid."; |
| return false; |
| } |
| |
| // Something like evt_e161 001a11ffacdf |
| string device_cn(device_name, device_name_length); |
| const size_t space_idx = device_cn.rfind(' '); |
| if (space_idx == string::npos) { |
| LOG(ERROR) << "Badly formatted subject"; |
| return false; |
| } |
| |
| string device_mac; |
| for (size_t i = space_idx + 1; i < device_cn.length(); ++i) { |
| device_mac.push_back(tolower(device_cn[i])); |
| } |
| if (connected_mac != device_mac) { |
| LOG(ERROR) << "MAC addresses don't match."; |
| return false; |
| } |
| |
| // Excellent, the certificate checks out, now make sure that the certificate |
| // matches the unsigned data presented. |
| // We're going to verify that hash(unsigned_data) == public(signed_data) |
| EVP_PKEY *cert_pubkey = X509_get_pubkey(x509); |
| if (!cert_pubkey) { |
| LOG(ERROR) << "Unable to extract public key from certificate."; |
| return false; |
| } |
| |
| RSA *cert_rsa = EVP_PKEY_get1_RSA(cert_pubkey); |
| if (!cert_rsa) { |
| LOG(ERROR) << "Failed to extract RSA key from certificate."; |
| return false; |
| } |
| |
| const unsigned char *signature = |
| reinterpret_cast<const unsigned char *>(signed_data.data()); |
| const size_t signature_len = signed_data.length(); |
| unsigned char *unsigned_data_bytes = |
| reinterpret_cast<unsigned char *>(const_cast<char *>( |
| unsigned_data.data())); |
| const size_t unsigned_data_len = unsigned_data.length(); |
| unsigned char digest[SHA_DIGEST_LENGTH]; |
| if (signature_len > numeric_limits<unsigned int>::max() || |
| unsigned_data_len > numeric_limits<unsigned long>::max()) { |
| LOG(ERROR) << "Arguments to signature match were too large."; |
| return false; |
| } |
| SHA1(unsigned_data_bytes, unsigned_data_len, digest); |
| if (RSA_verify(NID_sha1, digest, arraysize(digest), |
| signature, signature_len, cert_rsa) != 1) { |
| LOG(ERROR) << "Signed blobs did not match."; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // Verify the credentials of the destination described in |raw_input|. Takes |
| // a serialized VerifyCredentialsMessage protobuffer in |raw_input|, returns a |
| // serialized VerifyCredentialsResponse protobuffer in |output| on success. |
| // Returns false if the credentials fail to meet a check, and true on success. |
| bool VerifyCredentials(const string &raw_input, string *output) { |
| VerifyCredentialsMessage message; |
| if (!message.ParseFromString(raw_input)) { |
| LOG(ERROR) << "Failed to read VerifyCredentialsMessage from stdin."; |
| return false; |
| } |
| |
| if (!message.has_certificate() || !message.has_signed_data() || |
| !message.has_unsigned_data() || !message.has_mac_address()) { |
| LOG(ERROR) << "Request lacked necessary fields."; |
| return false; |
| } |
| |
| string connected_mac; |
| for (size_t i = 0; i < message.mac_address().length(); ++i) { |
| const char c = message.mac_address()[i]; |
| if (c != ':') { |
| connected_mac.push_back(tolower(c)); |
| } |
| } |
| if (connected_mac.length() != kMacLength) { |
| LOG(ERROR) << "shill gave us a bad MAC?"; |
| return false; |
| } |
| |
| RSA *rsa = NULL; |
| EVP_PKEY *pkey = NULL; |
| BIO *raw_certificate_bio = NULL; |
| X509 *x509 = NULL; |
| bool operation_successful = VerifyCredentialsImpl(message.certificate(), |
| message.signed_data(), message.unsigned_data(), connected_mac, |
| &rsa, &pkey, &raw_certificate_bio, &x509); |
| if (x509) { |
| X509_free(x509); |
| x509 = NULL; |
| } |
| if (raw_certificate_bio) { |
| BIO_free(raw_certificate_bio); |
| raw_certificate_bio = NULL; |
| } |
| if (pkey) { |
| EVP_PKEY_free(pkey); |
| pkey = NULL; |
| } |
| if (rsa) { |
| RSA_free(rsa); |
| rsa = NULL; |
| } |
| |
| if (operation_successful) { |
| LOG(INFO) << "Filling out protobuf."; |
| VerifyCredentialsResponse response; |
| response.set_ret(shill_protos::OK); |
| output->clear(); |
| LOG(INFO) << "Serializing protobuf."; |
| if (!response.SerializeToString(output)) { |
| LOG(ERROR) << "Failed while writing encrypted data."; |
| return false; |
| } |
| LOG(INFO) << "Encoding finished successfully."; |
| } |
| |
| return operation_successful; |
| } |
| |
| // Read the full stdin stream into a buffer, and execute the operation |
| // described in |command| with the contends of the stdin buffer. Write |
| // the serialized protocol buffer output of the command to stdout. |
| bool ParseAndExecuteCommand(const string &command) { |
| string raw_input; |
| char input_buffer[512]; |
| LOG(INFO) << "Reading input for command " << command << "."; |
| while (true) { |
| const ssize_t bytes_read = HANDLE_EINTR(read(STDIN_FILENO, |
| input_buffer, |
| arraysize(input_buffer))); |
| if (bytes_read < 0) { |
| // Abort abort abort. |
| LOG(ERROR) << "Failed while reading from stdin."; |
| return false; |
| } else if (bytes_read > 0) { |
| raw_input.append(input_buffer, bytes_read); |
| } else { |
| break; |
| } |
| } |
| LOG(INFO) << "Read " << raw_input.length() << " bytes."; |
| ERR_clear_error(); |
| string raw_output; |
| bool ret = false; |
| if (command == kCommandVerify) { |
| ret = VerifyCredentials(raw_input, &raw_output); |
| } else if (command == kCommandEncrypt) { |
| ret = EncryptByteString(raw_input, &raw_output); |
| } else { |
| LOG(ERROR) << "Invalid usage."; |
| return false; |
| } |
| if (!ret) { |
| LOG(ERROR) << "Last OpenSSL error: " |
| << ERR_reason_error_string(ERR_get_error()); |
| } |
| size_t total_bytes_written = 0; |
| while (total_bytes_written < raw_output.length()) { |
| const ssize_t bytes_written = HANDLE_EINTR(write( |
| STDOUT_FILENO, |
| raw_output.data() + total_bytes_written, |
| raw_output.length() - total_bytes_written)); |
| if (bytes_written < 0) { |
| LOG(ERROR) << "Result write failed with: " << errno; |
| return false; |
| } |
| total_bytes_written += bytes_written; |
| } |
| return ret; |
| } |
| |
| } // namespace |
| |
| int main(int argc, char **argv) { |
| CommandLine::Init(argc, argv); |
| chromeos::InitLog(chromeos::kLogToStderr | chromeos::kLogHeader); |
| LOG(INFO) << "crypto-util in action"; |
| |
| if (argc != 2) { |
| LOG(ERROR) << "Invalid usage"; |
| return EXIT_FAILURE; |
| } |
| const char *command = argv[1]; |
| if (strcmp(kCommandVerify, command) && strcmp(kCommandEncrypt, command)) { |
| LOG(ERROR) << "Invalid command"; |
| return EXIT_FAILURE; |
| } |
| |
| CRYPTO_malloc_init(); |
| ERR_load_crypto_strings(); |
| OpenSSL_add_all_algorithms(); |
| int return_code = EXIT_FAILURE; |
| if (ParseAndExecuteCommand(command)) { |
| return_code = EXIT_SUCCESS; |
| } |
| close(STDOUT_FILENO); |
| close(STDIN_FILENO); |
| |
| CONF_modules_unload(1); |
| OBJ_cleanup(); |
| EVP_cleanup(); |
| CRYPTO_cleanup_all_ex_data(); |
| ERR_remove_thread_state(NULL); |
| ERR_free_strings(); |
| |
| return return_code; |
| } |