443 lines
14 KiB
C++
443 lines
14 KiB
C++
//
|
|
// Copyright (C) 2013 The Android Open Source Project
|
|
//
|
|
// 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
|
|
//
|
|
// http://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 <unistd.h>
|
|
|
|
#include <limits>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <base/command_line.h>
|
|
#include <base/logging.h>
|
|
#include <base/posix/eintr_wrapper.h>
|
|
#include <brillo/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/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;
|
|
}
|
|
|
|
vector<unsigned char> rsa_output(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.data(),
|
|
rsa,
|
|
RSA_PKCS1_PADDING);
|
|
if (encrypted_length <= 0) {
|
|
LOG(ERROR) << "Error during encryption.";
|
|
return false;
|
|
}
|
|
|
|
encrypted_output->assign(reinterpret_cast<char*>(rsa_output.data()),
|
|
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()) {
|
|
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) {
|
|
base::CommandLine::Init(argc, argv);
|
|
brillo::InitLog(brillo::kLogToStderr | brillo::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;
|
|
}
|