blob: 65fd6685828036bd489d3d6fabf575b089d5d59e [file] [log] [blame]
// Copyright 2003-2010 Google Inc.
//
// 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.
// ========================================================================
//
// signatures.cpp
//
// Classes and functions related to crypto-hashes of buffers and digital
// signatures of buffers.
#include "omaha/common/signatures.h"
#include <wincrypt.h>
#include <memory.h>
#pragma warning(disable : 4245)
// C4245 : conversion from 'type1' to 'type2', signed/unsigned mismatch
#include <atlenc.h>
#pragma warning(default : 4245)
#include <vector>
#include "base/scoped_ptr.h"
#include "omaha/common/const_utils.h"
#include "omaha/common/debug.h"
#include "omaha/common/error.h"
#include "omaha/common/logging.h"
#include "omaha/common/scoped_any.h"
#include "omaha/common/sha.h"
#include "omaha/common/string.h"
#include "omaha/common/utils.h"
namespace omaha {
const ALG_ID kHashAlgorithm = CALG_SHA1;
const DWORD kEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
const DWORD kProviderType = PROV_RSA_FULL;
const DWORD kCertificateNameType = CERT_NAME_SIMPLE_DISPLAY_TYPE;
const DWORD kKeyPairType = AT_SIGNATURE;
// Maximum file size allowed for performing authentication.
const int kMaxFileSizeForAuthentication = 512 * 1000 * 1000; // 512MB
namespace CryptDetails {
// Useful scoped pointers for working with CryptoAPI objects
void crypt_release_context(HCRYPTPROV provider) {
UTIL_LOG(L3, (L"Releasing HCRYPTPROV 0x%08lx", provider));
BOOL b = ::CryptReleaseContext(provider, 0 /*flags*/);
ASSERT(b, (L""));
}
void crypt_close_store(HCERTSTORE store) {
UTIL_LOG(L3, (L"Releasing HCERTSTORE 0x%08lx", store));
BOOL b = ::CertCloseStore(store, 0 /*flags*/);
ASSERT(b, (L""));
ASSERT(::GetLastError() != CRYPT_E_PENDING_CLOSE, (L""));
}
void crypt_free_certificate(PCCERT_CONTEXT certificate) {
UTIL_LOG(L3, (L"Releasing PCCERT_CONTEXT 0x%08lx", certificate));
BOOL b = ::CertFreeCertificateContext(certificate);
ASSERT(b, (L""));
}
void crypt_destroy_key(HCRYPTKEY key) {
UTIL_LOG(L3, (L"Releasing HCRYPTKEY 0x%08lx", key));
BOOL b = ::CryptDestroyKey(key);
ASSERT(b, (L""));
}
void crypt_destroy_hash(HCRYPTHASH hash) {
UTIL_LOG(L3, (L"Releasing HCRYPTHASH 0x%08lx", hash));
BOOL b = ::CryptDestroyHash(hash);
ASSERT(b, (L""));
}
typedef close_fun<void (*)(HCRYPTHASH),
crypt_destroy_hash> smart_destroy_hash;
typedef scoped_any<HCRYPTHASH, smart_destroy_hash, null_t> scoped_crypt_hash;
}
// Base64 encode/decode functions are part of ATL Server
HRESULT Base64::Encode(const std::vector<byte>& buffer_in,
std::vector<byte>* encoded,
bool break_into_lines) {
ASSERT(encoded, (L""));
if (buffer_in.empty()) {
encoded->resize(0);
return S_OK;
}
int32 encoded_len =
Base64EncodeGetRequiredLength(
buffer_in.size(),
break_into_lines ? ATL_BASE64_FLAG_NONE : ATL_BASE64_FLAG_NOCRLF);
ASSERT(encoded_len > 0, (L""));
encoded->resize(encoded_len);
int32 str_out_len = encoded_len;
BOOL result = Base64Encode(
&buffer_in.front(),
buffer_in.size(),
reinterpret_cast<char*>(&encoded->front()),
&str_out_len,
break_into_lines ? ATL_BASE64_FLAG_NONE : ATL_BASE64_FLAG_NOCRLF);
if (!result)
return E_FAIL;
ASSERT(str_out_len <= encoded_len, (L""));
if (str_out_len < encoded_len)
encoded->resize(str_out_len);
return S_OK;
}
HRESULT Base64::Encode(const std::vector<byte>& buffer_in,
CStringA* encoded,
bool break_into_lines) {
ASSERT(encoded, (L""));
if (buffer_in.empty()) {
return S_OK;
}
std::vector<byte> buffer_out;
RET_IF_FAILED(Encode(buffer_in, &buffer_out, break_into_lines));
encoded->Append(reinterpret_cast<const char*>(&buffer_out.front()),
buffer_out.size());
return S_OK;
}
HRESULT Base64::Encode(const std::vector<byte>& buffer_in,
CString* encoded,
bool break_into_lines) {
ASSERT(encoded, (L""));
CStringA string_out;
RET_IF_FAILED(Encode(buffer_in, &string_out, break_into_lines));
*encoded = string_out;
return S_OK;
}
HRESULT Base64::Decode(const std::vector<byte>& encoded,
std::vector<byte>* buffer_out) {
ASSERT(buffer_out, (L""));
size_t encoded_len = encoded.size();
int32 required_len = Base64DecodeGetRequiredLength(encoded_len);
buffer_out->resize(required_len);
if (required_len == 0) {
return S_OK;
}
int32 bytes_written = required_len;
BOOL result = Base64Decode(reinterpret_cast<const char*>(&encoded.front()),
encoded_len,
&buffer_out->front(),
&bytes_written);
if (!result)
return E_FAIL;
ASSERT(bytes_written <= required_len, (L""));
if (bytes_written < required_len) {
buffer_out->resize(bytes_written);
}
return S_OK;
}
HRESULT Base64::Decode(const CStringA& encoded, std::vector<byte>* buffer_out) {
ASSERT(buffer_out, (L""));
size_t encoded_len = encoded.GetLength();
std::vector<byte> buffer_in(encoded_len);
if (encoded_len != 0) {
::memcpy(&buffer_in.front(), encoded.GetString(), encoded_len);
}
return Decode(buffer_in, buffer_out);
}
// Base64 in a CString -> binary
HRESULT Base64::Decode(const CString& encoded, std::vector<byte>* buffer_out) {
ASSERT(buffer_out, (L""));
CW2A encoded_a(encoded.GetString());
size_t encoded_len = ::strlen(encoded_a);
std::vector<byte> buffer_in(encoded_len);
if (encoded_len != 0) {
::memcpy(&buffer_in.front(), encoded_a, encoded_len);
}
return Decode(buffer_in, buffer_out);
}
// Using google SHA-1 algorithms rather than CryptoAPI SHA-1
// algorithms; saves the trouble of dealing with CSPs and the like.
CryptoHash::CryptoHash() {
}
CryptoHash::~CryptoHash() {
}
HRESULT CryptoHash::Compute(const TCHAR* filepath,
uint64 max_len,
std::vector<byte>* hash_out) {
ASSERT1(filepath);
ASSERT1(hash_out);
std::vector<CString> filepaths;
filepaths.push_back(filepath);
return Compute(filepaths, max_len, hash_out);
}
HRESULT CryptoHash::Compute(const std::vector<CString>& filepaths,
uint64 max_len,
std::vector<byte>* hash_out) {
ASSERT1(filepaths.size() > 0);
ASSERT1(hash_out);
return ComputeOrValidate(filepaths, max_len, NULL, hash_out);
}
HRESULT CryptoHash::Compute(const std::vector<byte>& buffer_in,
std::vector<byte>* hash_out) {
ASSERT1(buffer_in.size() > 0);
ASSERT1(hash_out);
return ComputeOrValidate(buffer_in, NULL, hash_out);
}
HRESULT CryptoHash::Validate(const TCHAR* filepath,
uint64 max_len,
const std::vector<byte>& hash_in) {
ASSERT1(filepath);
ASSERT1(hash_in.size() == kHashSize);
std::vector<CString> filepaths;
filepaths.push_back(filepath);
return Validate(filepaths, max_len, hash_in);
}
HRESULT CryptoHash::Validate(const std::vector<CString>& filepaths,
uint64 max_len,
const std::vector<byte>& hash_in) {
ASSERT1(hash_in.size() == kHashSize);
return ComputeOrValidate(filepaths, max_len, &hash_in, NULL);
}
HRESULT CryptoHash::Validate(const std::vector<byte>& buffer_in,
const std::vector<byte>& hash_in) {
ASSERT1(buffer_in.size() > 0);
ASSERT1(hash_in.size() == kHashSize);
return ComputeOrValidate(buffer_in, &hash_in, NULL);
}
HRESULT CryptoHash::ComputeOrValidate(const std::vector<CString>& filepaths,
uint64 max_len,
const std::vector<byte>* hash_in,
std::vector<byte>* hash_out) {
ASSERT1(filepaths.size() > 0);
ASSERT1(hash_in && !hash_out || !hash_in && hash_out);
byte buf[1024] = {0};
SecureHashAlgorithm sha;
uint64 curr_len = 0;
for (size_t i = 0; i < filepaths.size(); ++i) {
scoped_hfile file_handle(::CreateFile(filepaths[i],
FILE_READ_DATA,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL));
if (!file_handle) {
return HRESULTFromLastError();
}
if (max_len) {
LARGE_INTEGER file_size = {0};
if (!::GetFileSizeEx(get(file_handle), &file_size)) {
return HRESULTFromLastError();
}
curr_len += ((static_cast<uint64>(file_size.HighPart)) << 32) +
static_cast<uint64>(file_size.LowPart);
if (curr_len > max_len) {
UTIL_LOG(LE, (_T("[CryptoHash::ComputeOrValidate]")
_T(" exceed max len][curr_len=%lu][max_len=%lu]"),
curr_len, max_len));
return E_FAIL;
}
}
DWORD bytes_read = 0;
do {
if (!::ReadFile(get(file_handle),
buf,
arraysize(buf),
&bytes_read,
NULL)) {
return HRESULTFromLastError();
}
if (bytes_read > 0) {
sha.AddBytes(buf, bytes_read);
}
} while (bytes_read == arraysize(buf));
}
sha.Finished();
if (hash_in) {
int res = ::memcmp(&hash_in->front(), sha.Digest(), kHashSize);
if (res == 0) {
return S_OK;
}
std::vector<byte> calculated_hash(kHashSize);
memcpy(&calculated_hash.front(), sha.Digest(), kHashSize);
CStringA base64_encoded_hash;
Base64::Encode(calculated_hash, &base64_encoded_hash, false);
CString hash = AnsiToWideString(base64_encoded_hash,
base64_encoded_hash.GetLength());
REPORT_LOG(L1, (_T("[actual hash=%s]"), hash));
return SIGS_E_INVALID_SIGNATURE;
} else {
hash_out->resize(kHashSize);
::memcpy(&hash_out->front(), sha.Digest(), kHashSize);
return S_OK;
}
}
HRESULT CryptoHash::ComputeOrValidate(const std::vector<byte>& buffer_in,
const std::vector<byte>* hash_in,
std::vector<byte>* hash_out) {
ASSERT1(hash_in && !hash_out || !hash_in && hash_out);
SecureHashAlgorithm sha;
if (!buffer_in.empty()) {
sha.AddBytes(&buffer_in.front(), buffer_in.size());
}
sha.Finished();
if (hash_in) {
int res = ::memcmp(&hash_in->front(), sha.Digest(), kHashSize);
return (res == 0) ? S_OK : SIGS_E_INVALID_SIGNATURE;
} else {
hash_out->resize(kHashSize);
::memcpy(&hash_out->front(), sha.Digest(), kHashSize);
return S_OK;
}
}
// To sign data you need a CSP with the proper private key installed.
// To get a signing certificate you start with a PFX file. This file
// encodes a "certificate store" which can hold more than one
// certificate. (In general it can hold a certificate chain, but we
// only use the signing certificate.) There are special APIs to verify
// the format of a PFX file and read it into a new certificate store. A
// password must be specified to read the PFX file as it is encrypted.
// The password was set when the PFX file was exported or otherwise
// created. Then you search for the proper certificate in the store
// (using the subject_name which tells who the certificate was issued
// to). Finally, to get a CSP with the certificate's private key
// available there is a special API, CryptAcquireCertificatePrivateKey,
// that takes a CSP and a certificate and makes the private key of the
// certificate the private key of the CSP.
CryptoSigningCertificate::CryptoSigningCertificate() : key_spec_(0) {
}
CryptoSigningCertificate::~CryptoSigningCertificate() {
}
HRESULT CryptoSigningCertificate::ImportCertificate(
const TCHAR * filepath,
const TCHAR * password,
const TCHAR * subject_name) {
ASSERT(filepath, (L""));
ASSERT(password, (L""));
std::vector<byte> buffer;
HRESULT hr = ReadEntireFile(filepath, kMaxCertificateSize, &buffer);
if (FAILED(hr)) {
UTIL_LOG(LE, (L"[CryptoSigningCertificate::ImportCertificate]"
L"['%s' not read, hr 0x%08lx]", filepath, hr));
return hr;
}
return ImportCertificate(buffer, password, subject_name);
}
HRESULT CryptoSigningCertificate::ImportCertificate(
const std::vector<byte>& certificate_in,
const TCHAR * password,
const TCHAR * subject_name) {
ASSERT(password, (L""));
ASSERT1(!certificate_in.empty());
UTIL_LOG(L2, (L"[CryptoSigningCertificate::ImportCertificate]"
L"[%d bytes, subject_name '%s']",
certificate_in.size(), subject_name ? subject_name : L""));
// CryptoAPI treats the certificate as a "blob"
CRYPT_DATA_BLOB blob;
blob.cbData = certificate_in.size();
blob.pbData = const_cast<BYTE*>(&certificate_in.front());
// Ensure that it is PFX formatted
BOOL b = ::PFXIsPFXBlob(&blob);
if (!b) {
ASSERT(0, (L"Invalid PFX certificate, err 0x%08lx", ::GetLastError()));
return SIGS_E_INVALID_PFX_CERTIFICATE;
}
// Make sure the password checks out
b = ::PFXVerifyPassword(&blob, password, 0 /* flags */);
if (!b) {
UTIL_LOG(LE, (L"[CryptoSigningCertificate::ImportCertificate]"
L"[invalid password, err 0x%08lx]", ::GetLastError()));
return SIGS_E_INVALID_PASSWORD;
}
// Do the import from the certificate to a new certificate store
// TODO(omaha): Check that this is in fact a new certificate store, not an
// existing one. If it is an existing one we'll need to delete the
// certificate later.
// The last parameter to ::PFXImportCertStore() is 0, indicating that we want
// the CSP to be "silent"; i.e., not prompt.
reset(store_, ::PFXImportCertStore(&blob, password, 0));
if (!store_) {
DWORD err = ::GetLastError();
ASSERT(0, (L"Failed to import PFX certificate into a certificate store, "
L"err 0x%08lx", err));
return HRESULT_FROM_WIN32(err);
}
UTIL_LOG(L3, (L"[CryptoSigningCertificate::ImportCertificate]"
L"[new store 0x%08lx]", get(store_)));
// Now that we have a store, look for the correct certificate. (There may
// have been more than one in the PFX file, e.g., a certificate chain.)
PCCERT_CONTEXT certificate_context = NULL;
while ((certificate_context =
::CertEnumCertificatesInStore(get(store_),
certificate_context)) != NULL) {
// Have a certificate, does it look like the right one? Check the name
DWORD name_len = ::CertGetNameString(certificate_context,
kCertificateNameType,
0 /*flags*/,
NULL,
NULL,
0);
if (name_len <= 1) {
// Name attribute not found - should never happen
ASSERT(0, (L"CryptoSigningCertificate::ImportCertificate failed to get "
L"certificate name length, err 0x%08lx", ::GetLastError()));
continue;
}
// name_len includes the terminating null
std::vector<TCHAR> name;
name.resize(name_len);
ASSERT1(!name.empty());
DWORD name_len2 = ::CertGetNameString(certificate_context,
kCertificateNameType,
0,
NULL,
&name.front(),
name_len);
ASSERT(name_len2 == name_len, (L""));
UTIL_LOG(L3, (L"[CryptoSigningCertificate::ImportCertificate]"
L"[found '%s' in store]", &name.front()));
// Check the name if the user so desires. (If subject_name == NULL then
// the first certificate found is used.)
if (subject_name && (0 != String_StrNCmp(&name.front(),
subject_name,
::lstrlen(subject_name),
false))) {
// name mismatch
UTIL_LOG(L3, (L"[CryptoSigningCertificate::ImportCertificate]"
L"[not the right certificate, we're looking for '%s']",
subject_name));
continue;
}
// This is the right certificate
subject_name_ = &name.front();
reset(certificate_, certificate_context);
UTIL_LOG(L3, (L"[CryptoSigningCertificate::ImportCertificate]"
L"[new certificate 0x%08lx]", get(certificate_)));
break;
}
return S_OK;
}
HRESULT CryptoSigningCertificate::GetCSPContext(HCRYPTPROV* csp_context) {
ASSERT(csp_context, (L""));
ASSERT(get(certificate_), (L""));
// CSP may have already been used - reset it
reset(csp_);
// Create a CSP context using the private key of the certificate we imported
// earlier.
HCRYPTPROV csp = NULL;
BOOL must_free_csp = FALSE;
BOOL b = ::CryptAcquireCertificatePrivateKey(get(certificate_),
0 /*flags*/,
0 /*reserved*/,
&csp,
&key_spec_,
&must_free_csp);
if (!b) {
DWORD err = ::GetLastError();
ASSERT(0, (L"CryptoSigningCertificate::GetCSPContext "
L"CryptAcquireCertificatePrivateKey failed, err 0x%08lx", err));
return HRESULT_FROM_WIN32(err);
}
// (Funky API returns a boolean which tells you whether it is your
// responsibility to delete the CSP context or not.)
if (must_free_csp) {
reset(csp_, csp);
}
if (get(csp_)) {
UTIL_LOG(L3, (L"[CryptoSigningCertificate::GetCSPContext new CSP 0x%08lx]",
get(csp_)));
}
ASSERT(key_spec_ == AT_SIGNATURE || key_spec_ == AT_KEYEXCHANGE, (L""));
if (key_spec_ != kKeyPairType) {
UTIL_LOG(LE, (L"[CryptoSigningCertificate::GetCSPContext]"
L"[requires a AT_SIGNATURE type key]"));
return SIGS_E_INVALID_KEY_TYPE;
}
#ifdef _DEBUG
// Which CSP did we get?
char csp_name[256] = {0};
DWORD csp_name_len = arraysize(csp_name);
b = ::CryptGetProvParam(csp,
PP_NAME,
reinterpret_cast<BYTE*>(&csp_name[0]),
&csp_name_len,
0 /*flags*/);
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoSigningCertificate::GetCSPContext]"
L"[error getting CSP name, err 0x%08lx]", err));
}
DWORD csp_prov_type;
DWORD csp_prov_type_len = sizeof(csp_prov_type);
b = ::CryptGetProvParam(csp,
PP_PROVTYPE,
reinterpret_cast<BYTE*>(&csp_prov_type),
&csp_prov_type_len,
0 /*flags*/);
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoSigningCertificate::GetCSPContext]"
L"[error getting CSP provtype, err 0x%08lx]", err));
}
char csp_container[256] = {0};
DWORD csp_container_len = arraysize(csp_container);
b = ::CryptGetProvParam(csp,
PP_CONTAINER,
reinterpret_cast<BYTE*>(&csp_container[0]),
&csp_container_len,
0 /*flags*/);
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoSigningCertificate::GetCSPContext]"
L"[error getting CSP current container name, err 0x%08lx]",
err));
}
UTIL_LOG(L2, (L"[CryptoSigningCertificate::GetCSPContext]"
L"[have CSP '%S' (provtype %d) key container '%S']",
csp_name, csp_prov_type, csp_container));
// End of which CSP did we get
#endif
*csp_context = csp;
UTIL_LOG(L2, (L"[CryptoSigningCertificate::GetCSPContext]"
L"[getting CSP with private key from certificate]"
L"[HCRYPTPROV 0x%08lx]", csp));
return S_OK;
}
// To sign some data using CryptoAPI you first hash it into a hash
// object, then sign it using the CSP. The CSP needs to have the
// private key, of type AT_SIGNATURE, in it already, as it isn't a
// parameter of the CryptSignHash API. The CryptoSigningCertificate
// can provide such a CSP.
CryptoComputeSignature::CryptoComputeSignature(
CryptoSigningCertificate* certificate)
: certificate_(certificate) {
}
CryptoComputeSignature::~CryptoComputeSignature() {
}
HRESULT CryptoComputeSignature::Sign(TCHAR const * const filepath,
uint32 max_len,
std::vector<byte>* signature_out) {
ASSERT(filepath, (L""));
std::vector<byte> buffer;
HRESULT hr = ReadEntireFile(filepath, max_len, &buffer);
if (FAILED(hr)) {
UTIL_LOG(LE, (L"[CryptoComputeSignature::Sign]"
L"['%s not read, hr 0x%08lx]", filepath, hr));
return hr;
}
return Sign(buffer, signature_out);
}
HRESULT CryptoComputeSignature::Sign(const std::vector<byte>& buffer_in,
std::vector<byte>* signature_out) {
ASSERT(signature_out, (L""));
ASSERT1(!buffer_in.empty());
UTIL_LOG(L2, (L"[CryptoComputeSignature::Sign]"
L"[buffer of %d bytes]", buffer_in.size()));
// Get the proper CSP with the private key (certificate retains ownership)
HCRYPTPROV csp = NULL;
HRESULT hr = certificate_->GetCSPContext(&csp);
ASSERT(SUCCEEDED(hr) && csp, (L""));
// Hash the data
CryptDetails::scoped_crypt_hash hash;
BOOL b = ::CryptCreateHash(csp, kHashAlgorithm, 0, 0, address(hash));
if (!b) {
// hash is now invalid, but might not be NULL, so stomp on it
DWORD err = ::GetLastError();
ASSERT(!hash, (L""));
UTIL_LOG(LE, (L"[CryptoComputeSignature::Sign]"
L"[could not create hash, err 0x%08lx]", err));
return HRESULT_FROM_WIN32(err);
}
UTIL_LOG(L3, (L"CryptoComputeSignature::Sign new hash 0x%08lx", get(hash)));
b = ::CryptHashData(get(hash), &buffer_in.front(), buffer_in.size(), 0);
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoComputeSignature::Sign]"
L"[could not hash data, err 0x%08lx]", err));
return HRESULT_FROM_WIN32(err);
}
// Sign the hash (first get length, then allocate buffer and do real signing)
DWORD signature_len = 0;
b = ::CryptSignHash(get(hash),
kKeyPairType,
NULL,
0 /*flags*/,
NULL,
&signature_len);
if (!b && ::GetLastError() != ERROR_MORE_DATA) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoComputeSignature::Sign]"
L"[could not compute size of signature, err 0x%08lx]", err));
return HRESULT_FROM_WIN32(err);
}
signature_out->resize(signature_len);
b = ::CryptSignHash(get(hash),
kKeyPairType,
NULL,
0,
&signature_out->front(),
&signature_len);
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoComputeSignature::Sign]"
L"[could not compute signature, err 0x%08lx]", err));
return HRESULT_FROM_WIN32(err);
}
ASSERT(signature_len == signature_out->size(), (L""));
UTIL_LOG(L3, (L"[CryptoComputeSignature::Sign]"
L"[have %d byte signature]", signature_out->size()));
return S_OK;
}
// To verify signed data you need a CSP, and you also need the public
// key extracted from a certificate. The CSP can be any RSA CSP on the
// machine, the default one is fine. To get the public key you start
// by importing a certificate in standard "DER encoded" format. That
// returns a giant data structure, one field of which is the public key
// in a format that CryptoAPI understands. You import this public key
// into the CSP with the CryptImportPublicKey() API, and then create a
// key object from it suitable for use with the verification API.
CryptoSignatureVerificationCertificate::CryptoSignatureVerificationCertificate() { // NOLINT
}
CryptoSignatureVerificationCertificate::~CryptoSignatureVerificationCertificate() { // NOLINT
}
HRESULT CryptoSignatureVerificationCertificate::ImportCertificate(
const TCHAR * filepath,
const TCHAR * subject_name) {
ASSERT(filepath, (L""));
std::vector<byte> buffer;
HRESULT hr = ReadEntireFile(filepath, kMaxCertificateSize, &buffer);
if (FAILED(hr)) {
UTIL_LOG(LE, (L"[CryptoSignatureVerificationCertificate::ImportCertificate]"
L"['%s' not read, hr 0x%08lx]", filepath, hr));
return hr;
}
return ImportCertificate(buffer, subject_name);
}
HRESULT CryptoSignatureVerificationCertificate::ImportCertificate(
const std::vector<byte>& certificate_in,
const TCHAR * subject_name) {
// Import the certificate
ASSERT1(!certificate_in.empty());
reset(certificate_, ::CertCreateCertificateContext(kEncodingType,
&certificate_in.front(),
certificate_in.size()));
if (!certificate_) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoSignatureVerificationCertificate::ImportCertificate]"
L"[could not import certificate, err 0x%08lx]", err));
return SIGS_E_INVALID_DER_CERTIFICATE;
}
UTIL_LOG(L3, (L"[CryptoSignatureVerificationCertificate::ImportCertificate]"
L"[new certificate 0x%08lx]", get(certificate_)));
// Get certificate's subject name
DWORD name_len = ::CertGetNameString(get(certificate_),
kCertificateNameType,
0 /*flags*/,
NULL,
NULL,
0);
if (name_len <= 1) {
// Name attribute not found - should never happen
ASSERT(0, (L"CryptoSignatureVerificationCertificate failed to get "
L"certificate name length, err 0x%08lx", ::GetLastError()));
return E_FAIL;
}
// name_len includes the terminating NULL
std::vector <TCHAR> name;
name.resize(name_len);
ASSERT1(!name.empty());
DWORD name_len2 = ::CertGetNameString(get(certificate_),
kCertificateNameType,
0,
NULL,
&name.front(),
name_len);
ASSERT(name_len2 == name_len, (L""));
UTIL_LOG(L3, (L"[CryptoSignatureVerificationCertificate::ImportCertificate]"
L"['%s' is subject of certificate]", &name.front()));
subject_name_ = &name.front();
// Check the name if the user so desires.
if (subject_name && (0 != String_StrNCmp(&name.front(),
subject_name,
::lstrlen(subject_name), false))) {
// name mismatch
UTIL_LOG(L3, (L"[CryptoSignatureVerificationCertificate::ImportCertificate]"
L"[not the right certificate, we're looking for '%s']",
subject_name));
return E_FAIL;
}
return S_OK;
}
HRESULT CryptoSignatureVerificationCertificate::GetCSPContextAndKey(
HCRYPTPROV* csp_context,
HCRYPTKEY* public_key) {
ASSERT(csp_context, (L""));
ASSERT(public_key, (L""));
ASSERT(get(certificate_), (L""));
// Get the public key out of the certificate
PCERT_INFO cert_info = get(certificate_)->pCertInfo;
ASSERT(cert_info, (L""));
PCERT_PUBLIC_KEY_INFO public_key_info = &cert_info->SubjectPublicKeyInfo;
ASSERT(public_key_info, (L""));
// Reset the CSP and key in case it has been used already
reset(key_);
reset(csp_);
// Get the default CSP. With CRYPT_VERIFYCONTEXT don't need to worry
// about creating/destroying a key container.
// TODO(omaha): Why wasn't PROV_RSA_SIG available? Maybe looking for the
// default isn't a good idea?
BOOL b = ::CryptAcquireContext(address(csp_),
NULL,
NULL,
kProviderType,
CRYPT_VERIFYCONTEXT|CRYPT_SILENT);
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[GetCSPContextAndKey]"
L"[failed to acquire CSP, err 0x%08lx]", err));
return HRESULT_FROM_WIN32(err);
}
UTIL_LOG(L3, (L"[CryptoSignatureVerificationCertificate::GetCSPContextAndKey]"
L"[new CSP 0x%08lx]", get(csp_)));
// Convert the public key in encoded form into a CryptoAPI HCRYPTKEY
b = ::CryptImportPublicKeyInfo(get(csp_),
kEncodingType,
public_key_info,
address(key_));
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[GetCSPContextAndKey]"
L"[failed to import public key, err 0x%08lx]", err));
return HRESULT_FROM_WIN32(err);
}
UTIL_LOG(L3, (L"[CryptoSignatureVerificationCertificate::GetCSPContextAndKey]"
L"[new key 0x%08lx]", get(key_)));
*csp_context = get(csp_);
*public_key = get(key_);
return S_OK;
}
// To verify the signature of some data using CryptoAPI you first hash
// it into a hash object, then verify it using the CSP and a public key.
// In this case the CryptVerifySignature takes the key (of type
// AT_SIGNATURE) as a separate parameter. The
// CryptoSignatureVerificationCertificate can provide the proper CSP and
// the public key from the certificate.
CryptoVerifySignature::CryptoVerifySignature(
CryptoSignatureVerificationCertificate& certificate)
: certificate_(&certificate) {
}
CryptoVerifySignature::~CryptoVerifySignature() {
}
HRESULT CryptoVerifySignature::Validate(const TCHAR* filepath,
uint32 max_len,
const std::vector<byte>& signature_in) {
ASSERT(filepath, (L""));
std::vector<byte> buffer;
HRESULT hr = ReadEntireFile(filepath, max_len, &buffer);
if (FAILED(hr)) {
UTIL_LOG(LE, (L"[CryptoVerifySignature::Validate]"
L"['%s' not read, hr 0x%08lx]", filepath, hr));
return hr;
}
return Validate(buffer, signature_in);
}
HRESULT CryptoVerifySignature::Validate(const std::vector<byte>& buffer_in,
const std::vector<byte>& signature_in) {
ASSERT(certificate_, (L""));
ASSERT1(!buffer_in.empty());
ASSERT1(!signature_in.empty());
UTIL_LOG(L2, (L"[CryptoVerifySignature::Validate]"
L"[buffer of %d bytes, signature of %d bytes]",
buffer_in.size(), signature_in.size()));
// Get the CSP context and the public key from the certificate
HCRYPTPROV csp = NULL;
HCRYPTKEY key = NULL;
HRESULT hr = certificate_->GetCSPContextAndKey(&csp, &key);
ASSERT(SUCCEEDED(hr) && csp && key, (L""));
// Hash the data
CryptDetails::scoped_crypt_hash hash;
BOOL b = ::CryptCreateHash(csp, kHashAlgorithm, 0, 0, address(hash));
if (!b) {
// hash is now invalid, but might not be NULL, so stomp on it
DWORD err = ::GetLastError();
ASSERT(!hash, (L""));
UTIL_LOG(LE, (L"[CrypoVerifySignature::Validate]"
L"[could not create hash], err 0x%08lx", err));
return HRESULT_FROM_WIN32(err);
}
UTIL_LOG(L3, (L"CryptoVerifySignature::Validate new hash 0x%08lx", hash));
b = ::CryptHashData(get(hash),
&buffer_in.front(),
buffer_in.size(),
0 /*flags*/);
if (!b) {
DWORD err = ::GetLastError();
UTIL_LOG(LE, (L"[CryptoVerifySignature::Validate]"
L"[could not hash data, err 0x%08lx]", err));
return HRESULT_FROM_WIN32(err);
}
// Verify the hash
b = ::CryptVerifySignature(get(hash),
&signature_in.front(),
signature_in.size(),
key,
NULL,
0 /*flags*/);
if (!b) {
DWORD err = ::GetLastError();
#ifdef LOGGING
CString encoded_signature;
Base64::Encode(signature_in, &encoded_signature, false);
UTIL_LOG(LE, (_T("CryptoVerifySignature::Validate could not ")
_T("verify signature, err 0x%08lx with sig \"%s\""),
err, encoded_signature));
#endif
if (err == NTE_BAD_SIGNATURE)
return SIGS_E_INVALID_SIGNATURE;
else
return HRESULT_FROM_WIN32(err);
}
return S_OK;
}
HRESULT SignData(const TCHAR* certificate_path,
const TCHAR* certificate_password,
const TCHAR* certificate_subject_name,
const std::vector<byte>& data,
CString* signature_base64) {
ASSERT(certificate_path, (L""));
ASSERT(certificate_password, (L""));
// certificate_subject_name can be NULL
ASSERT(signature_base64, (L""));
CryptoSigningCertificate certificate;
RET_IF_FAILED(certificate.ImportCertificate(certificate_path,
certificate_password,
certificate_subject_name));
CryptoComputeSignature signer(&certificate);
std::vector<byte> signature;
RET_IF_FAILED(signer.Sign(data, &signature));
RET_IF_FAILED(Base64::Encode(signature, signature_base64, false));
return S_OK;
}
HRESULT VerifyData(const TCHAR* certificate_path,
const TCHAR* certificate_subject_name,
const std::vector<byte>& data,
const TCHAR* signature_base64) {
ASSERT(certificate_path, (L""));
// certificate_subject_name can be NULL
ASSERT(signature_base64, (L""));
std::vector<byte> signature;
RET_IF_FAILED(Base64::Decode(CString(signature_base64), &signature));
CryptoSignatureVerificationCertificate certificate;
RET_IF_FAILED(certificate.ImportCertificate(certificate_path,
certificate_subject_name));
CryptoVerifySignature verifier(certificate);
RET_IF_FAILED(verifier.Validate(data, signature));
return S_OK;
}
HRESULT VerifyData(const std::vector<byte>& certificate_buffer,
const TCHAR* certificate_subject_name,
const std::vector<byte>& data,
const TCHAR* signature_base64) {
// certificate_subject_name can be NULL
ASSERT(signature_base64, (L""));
std::vector<byte> signature;
RET_IF_FAILED(Base64::Decode(CString(signature_base64), &signature));
CryptoSignatureVerificationCertificate certificate;
RET_IF_FAILED(certificate.ImportCertificate(certificate_buffer,
certificate_subject_name));
CryptoVerifySignature verifier(certificate);
RET_IF_FAILED(verifier.Validate(data, signature));
return S_OK;
}
// Authenticate files
HRESULT AuthenticateFiles(const std::vector<CString>& files,
const CString& hash) {
ASSERT1(files.size() > 0);
ASSERT1(!hash.IsEmpty());
// Test the bytes against its hash
std::vector<byte> hash_vector;
RET_IF_FAILED(Base64::Decode(hash, &hash_vector));
ASSERT1(hash_vector.size() == CryptoHash::kHashSize);
CryptoHash crypto;
return crypto.Validate(files, kMaxFileSizeForAuthentication, hash_vector);
}
} // namespace omaha