kmscng/main/bridge.cc (428 lines of code) (raw):

// Copyright 2023 Google LLC // // 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 "kmscng/main/bridge.h" #include <string_view> #include "absl/status/status.h" #include "absl/status/statusor.h" #include "absl/strings/str_format.h" #include "common/status_macros.h" #include "kmscng/algorithm_details.h" #include "kmscng/config/config.h" #include "kmscng/config/config.pb.h" #include "kmscng/object.h" #include "kmscng/object_loader.h" #include "kmscng/operation/sign_utils.h" #include "kmscng/provider.h" #include "kmscng/util/errors.h" #include "kmscng/util/logging.h" #include "kmscng/util/status_utils.h" #include "kmscng/util/string_utils.h" namespace cloud_kms::kmscng { namespace { absl::Status ValidateFlags(uint32_t flags) { if (flags != 0 && flags != NCRYPT_SILENT_FLAG && flags != BCRYPT_PAD_PKCS1) { return NewInvalidArgumentError( absl::StrFormat("unsupported flag specified: %u", flags), NTE_BAD_FLAGS, SOURCE_LOCATION); } return absl::OkStatus(); } } // namespace // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptopenstorageprovider absl::Status OpenProvider(__out NCRYPT_PROV_HANDLE* phProvider, __in LPCWSTR pszProviderName, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "OpenProvider invoked\n" << "Process id: " << GetCurrentProcessId() << "Provider name: " << WideToString(std::wstring(pszProviderName)) << "\n" << "Flags: " << dwFlags << "\n\n"; if (phProvider == nullptr) { return NewInvalidArgumentError("the provider handle cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } // Check that the user is actually trying to open our provider, and not a // default / different provider. if (!pszProviderName || std::wstring_view(pszProviderName) != kProviderName) { return NewInvalidArgumentError("unexpected provider name", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); *phProvider = reinterpret_cast<NCRYPT_PROV_HANDLE>(new Provider()); return absl::OkStatus(); } // This function is called by NCryptFreeObject: // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptfreeobject absl::Status FreeProvider(__in NCRYPT_PROV_HANDLE hProvider) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "FreeProvider invoked\n" << "Provider: " << hProvider << "\n\n"; ASSIGN_OR_RETURN(Provider * prov, ValidateProviderHandle(hProvider)); delete prov; return absl::OkStatus(); } // This function is called by NCryptGetProperty: // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptgetproperty absl::Status GetProviderProperty(__in NCRYPT_PROV_HANDLE hProvider, __in LPCWSTR pszProperty, __out_bcount_part_opt(cbOutput, *pcbResult) PBYTE pbOutput, __in DWORD cbOutput, __out DWORD* pcbResult, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "GetProviderProperty invoked\n" << "Provider: " << hProvider << "\n" << "Property name: " << WideToString(std::wstring(pszProperty)) << "\n" << "Output: " << uintptr_t(pbOutput) << "\n" << "Output size: " << cbOutput << "\n" << "Output result size: " << uintptr_t(pcbResult) << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Provider * prov, ValidateProviderHandle(hProvider)); if (!pszProperty) { return NewInvalidArgumentError("pszProperty cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!pcbResult) { return NewInvalidArgumentError("pcbResult cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); ASSIGN_OR_RETURN(std::string_view property_value, prov->GetProperty(pszProperty)); *pcbResult = property_value.size(); // Return size required to hold the property value if output buffer is null. if (!pbOutput) { return absl::OkStatus(); } // Check provided buffer size to ensure the property value fits. if (cbOutput < property_value.size()) { return NewOutOfRangeError( absl::StrFormat("cbOutput size=%u not large enough to fit " "property value of size %u", cbOutput, property_value.size()), SOURCE_LOCATION); } property_value.copy(reinterpret_cast<char*>(pbOutput), property_value.size()); return absl::OkStatus(); } // This function is called by NCryptSetProperty: // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptsetproperty absl::Status SetProviderProperty(__in NCRYPT_PROV_HANDLE hProvider, __in LPCWSTR pszProperty, __in_bcount(cbInput) PBYTE pbInput, __in DWORD cbInput, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "SetProviderProperty invoked\n" << "Provider: " << hProvider << "\n" << "Property name: " << WideToString(std::wstring(pszProperty)) << "\n" << "Input: " << uintptr_t(pbInput) << "\n" << "Input size: " << cbInput << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Provider * prov, ValidateProviderHandle(hProvider)); if (!pszProperty) { return NewInvalidArgumentError("pszProperty cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!pbInput) { return NewInvalidArgumentError("pbInput cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); return prov->SetProperty( pszProperty, std::string(reinterpret_cast<char*>(pbInput), cbInput)); } // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptopenkey absl::Status OpenKey(__inout NCRYPT_PROV_HANDLE hProvider, __out NCRYPT_KEY_HANDLE* phKey, __in LPCWSTR pszKeyName, __in_opt DWORD dwLegacyKeySpec, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "OpenKey invoked\n" << "Provider: " << hProvider << "\n" << "Key name: " << WideToString(std::wstring(pszKeyName)) << "\n" << "LegacyKeySpec: " << dwLegacyKeySpec << "\n" << "Flags: " << dwFlags << "\n\n"; if (hProvider == 0) { return NewInvalidArgumentError("The provider handle cannot be null", NTE_INVALID_HANDLE, SOURCE_LOCATION); } if (phKey == nullptr) { return NewInvalidArgumentError("the key handle cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!pszKeyName) { return NewInvalidArgumentError("the key name cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (dwLegacyKeySpec != AT_KEYEXCHANGE && dwLegacyKeySpec != AT_SIGNATURE) { return NewInvalidArgumentError( absl::StrFormat("unsupported legacy key spec specified: %u", dwLegacyKeySpec), NTE_INVALID_PARAMETER, SOURCE_LOCATION); } dwFlags = dwFlags & ~NCRYPT_SILENT_FLAG; dwFlags = dwFlags & ~NCRYPT_MACHINE_KEY_FLAG; if (dwFlags != 0) { return NewInvalidArgumentError( absl::StrFormat("unsupported flag specified: %u", dwFlags), NTE_BAD_FLAGS, SOURCE_LOCATION); } ASSIGN_OR_RETURN(Object * object, Object::New(hProvider, WideToString(pszKeyName))); *phKey = reinterpret_cast<NCRYPT_KEY_HANDLE>(object); return absl::OkStatus(); } // This function is called by NCryptFreeObject: // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptfreeobject absl::Status FreeKey(__in NCRYPT_PROV_HANDLE hProvider, __in NCRYPT_KEY_HANDLE hKey) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "FreeKey invoked\n" << "Provider: " << hProvider << "\n" << "Key: " << hKey << "\n\n"; ASSIGN_OR_RETURN(Object * obj, ValidateKeyHandle(hProvider, hKey)); if (obj) { delete obj; } return absl::OkStatus(); } // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptexportkey absl::Status ExportKey( __in NCRYPT_PROV_HANDLE hProvider, __in NCRYPT_KEY_HANDLE hKey, __in_opt NCRYPT_KEY_HANDLE hExportKey, __in LPCWSTR pszBlobType, __in_opt NCryptBufferDesc* pParameterList, __out_bcount_part_opt(cbOutput, *pcbResult) PBYTE pbOutput, __in DWORD cbOutput, __out DWORD* pcbResult, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "ExportKey invoked\n" << "Provider: " << hProvider << "\n" << "Key: " << hKey << "\n" << "Export Key: " << hExportKey << "\n" << "Blob type: " << WideToString(std::wstring(pszBlobType)) << "\n" << "Parameter list: " << uintptr_t(pParameterList) << "\n" << "Output: " << uintptr_t(pbOutput) << "\n" << "Output size: " << cbOutput << "\n" << "Output result size: " << uintptr_t(pcbResult) << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Object * object, ValidateKeyHandle(hProvider, hKey)); if (hExportKey) { return NewInvalidArgumentError("hExportKey is not supported", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } constexpr std::wstring_view kEccPublicKeyType(BCRYPT_ECCPUBLIC_BLOB); constexpr std::wstring_view kRsaPublicKeyType(BCRYPT_RSAPUBLIC_BLOB); if (pszBlobType != kEccPublicKeyType && pszBlobType != kRsaPublicKeyType) { return NewInvalidArgumentError( absl::StrFormat("blob type not supported: %s", WideToString(pszBlobType)), NTE_BAD_TYPE, SOURCE_LOCATION); } if (!pcbResult) { return NewInvalidArgumentError("pcbResult cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); ASSIGN_OR_RETURN(std::vector<uint8_t> serialized_pub_key, SerializePublicKey(object)); *pcbResult = serialized_pub_key.size(); // Return size required to hold the property value if output buffer is null. if (!pbOutput) { return absl::OkStatus(); } // Check provided buffer size to ensure the property value fits. if (cbOutput < serialized_pub_key.size()) { return NewOutOfRangeError( absl::StrFormat("cbOutput size=%u not large enough to fit " "property value of size %u", cbOutput, serialized_pub_key.size()), SOURCE_LOCATION); } std::copy(serialized_pub_key.begin(), serialized_pub_key.end(), pbOutput); return absl::OkStatus(); } // This function is called by NCryptGetProperty: // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptgetproperty absl::Status GetKeyProperty(__in NCRYPT_PROV_HANDLE hProvider, __in NCRYPT_KEY_HANDLE hKey, __in LPCWSTR pszProperty, __out_bcount_part_opt(cbOutput, *pcbResult) PBYTE pbOutput, __in DWORD cbOutput, __out DWORD* pcbResult, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "GetKeyProperty invoked\n" << "Provider: " << hProvider << "\n" << "Key: " << hKey << "\n" << "Property name: " << WideToString(std::wstring(pszProperty)) << "\n" << "Output: " << uintptr_t(pbOutput) << "\n" << "Output size: " << cbOutput << "\n" << "Output result size: " << uintptr_t(pcbResult) << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Object * object, ValidateKeyHandle(hProvider, hKey)); if (!pszProperty) { return NewInvalidArgumentError("pszProperty cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!pcbResult) { return NewInvalidArgumentError("pcbResult cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); ASSIGN_OR_RETURN(std::string_view property_value, object->GetProperty(pszProperty)); *pcbResult = property_value.size(); // Return size required to hold the property value if output buffer is null. if (!pbOutput) { return absl::OkStatus(); } // Check provided buffer size to ensure the property value fits. if (cbOutput < property_value.size()) { return NewOutOfRangeError( absl::StrFormat("cbOutput size=%u not large enough to fit " "property value of size %u", cbOutput, property_value.size()), SOURCE_LOCATION); } property_value.copy(reinterpret_cast<char*>(pbOutput), property_value.size()); return absl::OkStatus(); } // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptenumkeys absl::Status EnumKeys(__in NCRYPT_PROV_HANDLE hProvider, __in_opt LPCWSTR pszScope, __deref_out NCryptKeyName** ppKeyName, __inout PVOID* ppEnumState, __in DWORD dwFlags, __in_opt std::string test_config_path) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "EnumKeys invoked\n" << "Provider: " << hProvider << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Provider * prov, ValidateProviderHandle(hProvider)); if (pszScope) { return NewInvalidArgumentError("pszScope should be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!ppKeyName) { return NewInvalidArgumentError("ppKeyName cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } dwFlags = dwFlags & ~NCRYPT_SILENT_FLAG; dwFlags = dwFlags & ~NCRYPT_MACHINE_KEY_FLAG; if (dwFlags != 0) { return NewInvalidArgumentError( absl::StrFormat("unsupported flag specified: %u", dwFlags), NTE_BAD_FLAGS, SOURCE_LOCATION); } EnumState* enum_state; ProviderConfig config; // Generate CKV list if this is the first call to EnumKeys. if (!*ppEnumState) { // If test_config_path exists, load config from there. This is only used // for internal testing. if (!test_config_path.empty()) { ASSIGN_OR_RETURN(config, LoadConfigFromFile(test_config_path)); } else { // Load config from well-known system path. ASSIGN_OR_RETURN(config, LoadConfigFromFile("C:\\Windows\\KMSCNG\\config.yaml")); } // Load CKV list from config file. ASSIGN_OR_RETURN(std::vector<HeapAllocatedKeyDetails> ckv_list, BuildCkvList(hProvider, config)); *ppEnumState = new EnumState{ .key_details = ckv_list, .current = 0, }; enum_state = reinterpret_cast<EnumState*>(*ppEnumState); } else { // Check ppEnumState value to make sure it's a valid vector index. enum_state = reinterpret_cast<EnumState*>(*ppEnumState); if (enum_state->current > enum_state->key_details.size()) { return NewInvalidArgumentError( absl::StrFormat("unrecognized ppEnumState value: %u", enum_state->current), NTE_INVALID_PARAMETER, SOURCE_LOCATION); } } if (enum_state->current == enum_state->key_details.size()) { return NewInvalidArgumentError( absl::StrFormat("end of enumeration reached: %u", enum_state->current), NTE_NO_MORE_ITEMS, SOURCE_LOCATION); } *ppKeyName = enum_state->key_details[enum_state->current].NewNCryptKeyName().release(); enum_state->current += 1; return absl::OkStatus(); } // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptsignhash absl::Status SignHash(__in NCRYPT_PROV_HANDLE hProvider, __in NCRYPT_KEY_HANDLE hKey, __in_opt VOID* pPaddingInfo, __in_bcount(cbHashValue) PBYTE pbHashValue, __in DWORD cbHashValue, __out_bcount_part_opt(cbSignature, *pcbResult) PBYTE pbSignature, __in DWORD cbSignature, __out DWORD* pcbResult, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "SignHash invoked\n" << "Provider: " << hProvider << "\n" << "Key: " << hKey << "\n" << "Padding info: " << uintptr_t(pPaddingInfo) << "\n" << "Hash value: " << uintptr_t(pbHashValue) << "\n" << "Hash value size: " << cbHashValue << "\n" << "Signature: " << uintptr_t(pbSignature) << "\n" << "Signature size: " << cbSignature << "\n" << "Signature result size: " << uintptr_t(pcbResult) << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Object * object, ValidateKeyHandle(hProvider, hKey)); if (!pbHashValue) { return NewInvalidArgumentError("pbHashValue cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!pcbResult) { return NewInvalidArgumentError("pcbResult cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); // Check key properties against the expected AlgorithmDetails. RETURN_IF_ERROR(ValidateKeyPreconditions(object)); ASSIGN_OR_RETURN(size_t signature_length, SignatureLength(object)); *pcbResult = static_cast<uint32_t>(signature_length); // Return size required to hold the signature if output buffer is null. if (!pbSignature) { return absl::OkStatus(); } // Check provided buffer size to ensure the property value fits. if (cbSignature < *pcbResult) { return NewOutOfRangeError( absl::StrFormat("cbSignature size=%u not large enough to fit " "property value of size %u", cbSignature, *pcbResult), SOURCE_LOCATION); } RETURN_IF_ERROR( SignDigest(object, absl::Span<const uint8_t>(pbHashValue, cbHashValue), absl::Span<uint8_t>(pbSignature, cbSignature))); return absl::OkStatus(); } // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptisalgsupported absl::Status IsAlgSupported(__in NCRYPT_PROV_HANDLE hProvider, __in LPCWSTR pszAlgId, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "IsAlgSupported invoked\n" << "Provider: " << hProvider << "\n" << "Alg Id: " << WideToString(std::wstring(pszAlgId)) << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Provider * prov, ValidateProviderHandle(hProvider)); if (!pszAlgId) { return NewInvalidArgumentError("pszAlgId cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); return IsSupportedAlgorithmIdentifier(pszAlgId); } // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptenumalgorithms absl::Status EnumAlgorithms(__in NCRYPT_PROV_HANDLE hProvider, __in DWORD dwAlgOperations, __out DWORD* pdwAlgCount, __deref_out_ecount(*pdwAlgCount) NCryptAlgorithmName** ppAlgList, __in DWORD dwFlags) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "EnumAlgorithms invoked\n" << "Provider: " << hProvider << "\n" << "Alg Operations: " << dwAlgOperations << "\n" << "Flags: " << dwFlags << "\n\n"; ASSIGN_OR_RETURN(Provider * prov, ValidateProviderHandle(hProvider)); dwAlgOperations = dwAlgOperations & ~NCRYPT_SIGNATURE_OPERATION; if (dwAlgOperations) { return NewInvalidArgumentError("invalid dwAlgOperations specified", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!pdwAlgCount) { return NewInvalidArgumentError("pdwAlgCount cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } if (!ppAlgList) { return NewInvalidArgumentError("ppAlgList cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } RETURN_IF_ERROR(ValidateFlags(dwFlags)); *ppAlgList = new NCryptAlgorithmName[kAlgorithmNames.size()]; std::copy_n(kAlgorithmNames.data(), kAlgorithmNames.size(), *ppAlgList); *pdwAlgCount = kAlgorithmNames.size(); return absl::OkStatus(); } // https://learn.microsoft.com/en-us/windows/win32/api/ncrypt/nf-ncrypt-ncryptfreebuffer absl::Status FreeBuffer(__deref PVOID pvInput) { LOG_IF(INFO, std::getenv(kVerboseLoggingEnvVariable)) << "FreeBuffer invoked\n\n"; if (!pvInput) { return NewInvalidArgumentError("pvInput cannot be null", NTE_INVALID_PARAMETER, SOURCE_LOCATION); } delete pvInput; return absl::OkStatus(); } } // namespace cloud_kms::kmscng