blob: 95a3deb27f421c03417e4e82778c73344f0f10a7 [file] [log] [blame]
// Copyright (c) 2010 The Chromium 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 "net/http/mock_sspi_library_win.h"
#include <algorithm>
#include <cstring>
#include <memory>
#include "base/logging.h"
#include "base/strings/string16.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
// Comparator so we can use CredHandle and CtxtHandle with std::set. Both of
// those classes are typedefs for _SecHandle.
bool operator<(const _SecHandle left, const _SecHandle right) {
return left.dwUpper < right.dwUpper || left.dwLower < right.dwLower;
}
namespace net {
namespace {
int uniquifier_ = 0;
struct MockCredential {
base::string16 source_principal;
base::string16 package;
bool has_explicit_credentials = false;
int uniquifier = ++uniquifier_;
// CredHandle and CtxtHandle both shared the following definition:
//
// typedef struct _SecHandle {
// ULONG_PTR dwLower;
// ULONG_PTR dwUpper;
// } SecHandle, * PSecHandle;
//
// ULONG_PTR type can hold a pointer. This function stuffs |this| into dwUpper
// and adds a uniquifier to dwLower. This ensures that all PCredHandles issued
// by this method during the lifetime of this process is unique.
void StoreInHandle(PCredHandle handle) {
DCHECK(uniquifier > 0);
EXPECT_FALSE(SecIsValidHandle(handle));
handle->dwLower = uniquifier;
handle->dwUpper = reinterpret_cast<ULONG_PTR>(this);
DCHECK(SecIsValidHandle(handle));
}
static MockCredential* FromHandle(PCredHandle handle) {
return reinterpret_cast<MockCredential*>(handle->dwUpper);
}
};
struct MockContext {
MockCredential* credential = nullptr;
base::string16 target_principal;
int uniquifier = ++uniquifier_;
int rounds = 0;
// CredHandle and CtxtHandle both shared the following definition:
//
// typedef struct _SecHandle {
// ULONG_PTR dwLower;
// ULONG_PTR dwUpper;
// } SecHandle, * PSecHandle;
//
// ULONG_PTR type can hold a pointer. This function stuffs |this| into dwUpper
// and adds a uniquifier to dwLower. This ensures that all PCredHandles issued
// by this method during the lifetime of this process is unique.
void StoreInHandle(PCtxtHandle handle) {
EXPECT_FALSE(SecIsValidHandle(handle));
DCHECK(uniquifier > 0);
handle->dwLower = uniquifier;
handle->dwUpper = reinterpret_cast<ULONG_PTR>(this);
DCHECK(SecIsValidHandle(handle));
}
std::string ToString() const {
return base::StringPrintf(
"%s's token #%d for %S",
base::UTF16ToUTF8(credential->source_principal).c_str(), rounds + 1,
base::as_wcstr(target_principal));
}
static MockContext* FromHandle(PCtxtHandle handle) {
return reinterpret_cast<MockContext*>(handle->dwUpper);
}
};
} // namespace
MockSSPILibrary::MockSSPILibrary() {
}
MockSSPILibrary::~MockSSPILibrary() {
EXPECT_TRUE(expected_package_queries_.empty());
EXPECT_TRUE(expected_freed_packages_.empty());
EXPECT_TRUE(active_credentials_.empty());
EXPECT_TRUE(active_contexts_.empty());
}
SECURITY_STATUS MockSSPILibrary::AcquireCredentialsHandle(
LPWSTR pszPrincipal,
LPWSTR pszPackage,
unsigned long fCredentialUse,
void* pvLogonId,
void* pvAuthData,
SEC_GET_KEY_FN pGetKeyFn,
void* pvGetKeyArgument,
PCredHandle phCredential,
PTimeStamp ptsExpiry) {
DCHECK(!SecIsValidHandle(phCredential));
auto* credential = new MockCredential;
credential->source_principal = pszPrincipal ? base::as_u16cstr(pszPrincipal)
: STRING16_LITERAL("<Default>");
credential->package = base::as_u16cstr(pszPackage);
credential->has_explicit_credentials = !!pvAuthData;
credential->StoreInHandle(phCredential);
if (ptsExpiry) {
ptsExpiry->LowPart = 0xBAA5B780;
ptsExpiry->HighPart = 0x01D54E17;
}
active_credentials_.insert(*phCredential);
return SEC_E_OK;
}
SECURITY_STATUS MockSSPILibrary::InitializeSecurityContext(
PCredHandle phCredential,
PCtxtHandle phContext,
SEC_WCHAR* pszTargetName,
unsigned long fContextReq,
unsigned long Reserved1,
unsigned long TargetDataRep,
PSecBufferDesc pInput,
unsigned long Reserved2,
PCtxtHandle phNewContext,
PSecBufferDesc pOutput,
unsigned long* contextAttr,
PTimeStamp ptsExpiry) {
MockContext* new_context = new MockContext;
new_context->credential = MockCredential::FromHandle(phCredential);
new_context->target_principal = base::as_u16cstr(pszTargetName);
new_context->rounds = 0;
// Always rotate contexts. That way tests will fail if the caller's context
// management is broken.
if (phContext && SecIsValidHandle(phContext)) {
std::unique_ptr<MockContext> old_context{
MockContext::FromHandle(phContext)};
EXPECT_EQ(old_context->credential, new_context->credential);
EXPECT_EQ(1u, active_contexts_.erase(*phContext));
new_context->rounds = old_context->rounds + 1;
SecInvalidateHandle(phContext);
}
new_context->StoreInHandle(phNewContext);
active_contexts_.insert(*phNewContext);
auto token = new_context->ToString();
PSecBuffer out_buffer = pOutput->pBuffers;
out_buffer->cbBuffer = std::min<ULONG>(out_buffer->cbBuffer, token.size());
std::memcpy(out_buffer->pvBuffer, token.data(), out_buffer->cbBuffer);
if (ptsExpiry) {
ptsExpiry->LowPart = 0xBAA5B780;
ptsExpiry->HighPart = 0x01D54E15;
}
return SEC_E_OK;
}
SECURITY_STATUS MockSSPILibrary::QueryContextAttributesEx(PCtxtHandle phContext,
ULONG ulAttribute,
PVOID pBuffer,
ULONG cbBuffer) {
static const SecPkgInfoW kNegotiatedPackage = {
0,
0,
0,
0,
const_cast<SEC_WCHAR*>(L"Itsa me Kerberos!!"),
const_cast<SEC_WCHAR*>(L"I like turtles")};
auto* context = MockContext::FromHandle(phContext);
switch (ulAttribute) {
case SECPKG_ATTR_NATIVE_NAMES: {
auto* native_names =
reinterpret_cast<SecPkgContext_NativeNames*>(pBuffer);
DCHECK_EQ(sizeof(*native_names), cbBuffer);
native_names->sClientName =
base::as_writable_wcstr(context->credential->source_principal);
native_names->sServerName =
base::as_writable_wcstr(context->target_principal);
return SEC_E_OK;
}
case SECPKG_ATTR_NEGOTIATION_INFO: {
auto* negotiation_info =
reinterpret_cast<SecPkgContext_NegotiationInfo*>(pBuffer);
DCHECK_EQ(sizeof(*negotiation_info), cbBuffer);
negotiation_info->PackageInfo =
const_cast<SecPkgInfoW*>(&kNegotiatedPackage);
negotiation_info->NegotiationState = (context->rounds == 1)
? SECPKG_NEGOTIATION_COMPLETE
: SECPKG_NEGOTIATION_IN_PROGRESS;
return SEC_E_OK;
}
case SECPKG_ATTR_AUTHORITY: {
auto* authority = reinterpret_cast<SecPkgContext_Authority*>(pBuffer);
DCHECK_EQ(sizeof(*authority), cbBuffer);
authority->sAuthorityName = const_cast<SEC_WCHAR*>(L"Dodgy Server");
return SEC_E_OK;
}
default:
return SEC_E_UNSUPPORTED_FUNCTION;
}
}
SECURITY_STATUS MockSSPILibrary::QuerySecurityPackageInfo(
LPWSTR pszPackageName, PSecPkgInfoW *pkgInfo) {
EXPECT_TRUE(!expected_package_queries_.empty());
PackageQuery package_query = expected_package_queries_.front();
expected_package_queries_.pop_front();
std::wstring actual_package(pszPackageName);
EXPECT_EQ(package_query.expected_package, actual_package);
*pkgInfo = package_query.package_info;
if (package_query.response_code == SEC_E_OK)
expected_freed_packages_.insert(package_query.package_info);
return package_query.response_code;
}
SECURITY_STATUS MockSSPILibrary::FreeCredentialsHandle(
PCredHandle phCredential) {
DCHECK(SecIsValidHandle(phCredential));
EXPECT_EQ(1u, active_credentials_.erase(*phCredential));
std::unique_ptr<MockCredential> owned{
MockCredential::FromHandle(phCredential)};
SecInvalidateHandle(phCredential);
return SEC_E_OK;
}
SECURITY_STATUS MockSSPILibrary::DeleteSecurityContext(PCtxtHandle phContext) {
std::unique_ptr<MockContext> context{MockContext::FromHandle(phContext)};
EXPECT_EQ(1u, active_contexts_.erase(*phContext));
SecInvalidateHandle(phContext);
return SEC_E_OK;
}
SECURITY_STATUS MockSSPILibrary::FreeContextBuffer(PVOID pvContextBuffer) {
PSecPkgInfoW package_info = static_cast<PSecPkgInfoW>(pvContextBuffer);
std::set<PSecPkgInfoW>::iterator it = expected_freed_packages_.find(
package_info);
EXPECT_TRUE(it != expected_freed_packages_.end());
expected_freed_packages_.erase(it);
return SEC_E_OK;
}
void MockSSPILibrary::ExpectQuerySecurityPackageInfo(
const std::wstring& expected_package,
SECURITY_STATUS response_code,
PSecPkgInfoW package_info) {
PackageQuery package_query = {expected_package, response_code,
package_info};
expected_package_queries_.push_back(package_query);
}
} // namespace net