Commit db0dcf92 authored by Asanka Herath's avatar Asanka Herath Committed by Commit Bot

[net/auth] Change diagnostics from strings to base::Value

Some of the diagnostics generated during a GSSAPI handshake are not
straightforward. The existing code constructs strings describing the
errors and logs them via LOG. In order to convert these to NetLogs, this
CL changes the logic to construct base::Values instead of freeform
strings.

R=mmenke@chromium.org

Bug: 884313
Change-Id: I084ab5eaf74df787f3f3c839c030e7c9fc5da3b5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1650628
Commit-Queue: Asanka Herath <asanka@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#670676}
parent be1d1023
This diff is collapsed.
......@@ -10,6 +10,8 @@
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/native_library.h"
#include "base/strings/string_piece_forward.h"
#include "base/values.h"
#include "net/base/completion_once_callback.h"
#include "net/base/net_export.h"
#include "net/http/http_auth.h"
......@@ -247,6 +249,117 @@ class NET_EXPORT_PRIVATE HttpAuthGSSAPI : public HttpNegotiateAuthSystem {
HttpAuth::DelegationType delegation_type_ = HttpAuth::DelegationType::kNone;
};
// Diagnostics
// GetGssStatusCodeValue constructs a base::Value containing a status code and a
// message.
//
// {
// "status" : <status value as a number>,
// "message": [
// <list of strings explaining what that number means>
// ]
// }
//
// Messages are looked up via gss_display_status() exposed by |gssapi_lib|. The
// type of status code should be indicated by setting |status_code_type| to
// either |GSS_C_MECH_CODE| or |GSS_C_GSS_CODE|.
//
// Mechanism specific codes aren't unique, so the mechanism needs to be
// identified to look up messages if |status_code_type| is |GSS_C_MECH_CODE|.
// Since no mechanism OIDs are passed in, mechanism specific status codes will
// likely not have messages.
NET_EXPORT_PRIVATE base::Value GetGssStatusCodeValue(
GSSAPILibrary* gssapi_lib,
OM_uint32 status,
OM_uint32 status_code_type);
// Given major and minor GSSAPI status codes, returns a base::Value
// encapsulating the codes as well as their meanings as expanded via
// gss_display_status().
//
// The base::Value has the following structure:
// {
// "function": <name of GSSAPI function that returned the error>
// "major_status": {
// "status" : <status value as a number>,
// "message": [
// <list of strings hopefully explaining what that number means>
// ]
// },
// "minor_status": {
// "status" : <status value as a number>,
// "message": [
// <list of strings hopefully explaining what that number means>
// ]
// }
// }
//
// Passing nullptr to |gssapi_lib| will skip the message lookups. Thus the
// returned value will be missing the "message" fields. The same is true if the
// message lookup failed for some reason, or if the lookups succeeded but
// yielded an empty message.
NET_EXPORT_PRIVATE base::Value GetGssStatusValue(GSSAPILibrary* gssapi_lib,
base::StringPiece method,
OM_uint32 major_status,
OM_uint32 minor_status);
// OidToValue returns a base::Value representing an OID. The structure of the
// value is:
// {
// "oid": <symbolic name of OID if it is known>
// "length": <length in bytes of serialized OID>,
// "bytes": <hexdump of up to 1024 bytes of serialized OID>
// }
NET_EXPORT_PRIVATE base::Value OidToValue(const gss_OID oid);
// GetDisplayNameValue returns a base::Value representing a gss_name_t. It
// invokes |gss_display_name()| via |gssapi_lib| to determine the display name
// associated with |gss_name|.
//
// The structure of the returned value is:
// {
// "gss_name": <display name as returned by gss_display_name()>,
// "type": <OID indicating type. See OidToValue() for structure of this
// field>
// }
//
// If the lookup failed, then the structure is:
// {
// "error": <error. See GetGssStatusValue() for structure.>
// }
//
// Note that |gss_name_t| is platform dependent. If |gss_display_name| fails,
// there's no good value to display in its stead.
NET_EXPORT_PRIVATE base::Value GetDisplayNameValue(GSSAPILibrary* gssapi_lib,
const gss_name_t gss_name);
// GetContextStateAsValue returns a base::Value that describes the state of a
// GSSAPI context. The structure of the value is:
//
// {
// "source": {
// "name": <GSSAPI principal name of source (e.g. the user)>,
// "type": <OID of name type>
// },
// "target": {
// "name": <GSSAPI principal name of target (e.g. the server)>,
// "type": <OID of name type>
// },
// "lifetime": <Lifetime of the negotiated context in seconds.>,
// "mechanism": <OID of negotiated mechanism>,
// "flags": <Context flags. See documentation for gss_inquire_context for
// flag values>
// "open": <True if the context has finished the handshake>
// }
//
// If the inquiry fails, the following is returned:
// {
// "error": <error. See GetGssStatusValue() for structure.>
// }
NET_EXPORT_PRIVATE base::Value GetContextStateAsValue(
GSSAPILibrary* gssapi_lib,
const gss_ctx_id_t context_handle);
} // namespace net
#endif // NET_HTTP_HTTP_AUTH_GSSAPI_POSIX_H_
......@@ -8,6 +8,7 @@
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/native_library.h"
#include "base/path_service.h"
......@@ -318,4 +319,282 @@ TEST(HttpAuthGSSAPITest, ParseChallenge_NonBase64EncodedToken) {
auth_gssapi.ParseChallenge(&second_challenge));
}
TEST(HttpAuthGSSAPITest, OidToValue_NIL) {
auto actual = OidToValue(GSS_C_NO_OID);
auto expected = base::JSONReader::Read(R"({ "oid": "<Empty OID>" })");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, OidToValue_Known) {
gss_OID_desc known = {6, const_cast<char*>("\x2b\x06\01\x05\x06\x03")};
auto actual = OidToValue(const_cast<const gss_OID>(&known));
auto expected = base::JSONReader::Read(R"(
{
"oid" : "GSS_C_NT_ANONYMOUS",
"length": 6,
"bytes" : "0x0000: 2b06 0105 0603 +.....\n"
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, OidToValue_Unknown) {
gss_OID_desc unknown = {6, const_cast<char*>("\x2b\x06\01\x05\x06\x05")};
auto actual = OidToValue(const_cast<const gss_OID>(&unknown));
auto expected = base::JSONReader::Read(R"(
{
"length": 6,
"bytes" : "0x0000: 2b06 0105 0605 +.....\n"
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_NoLibrary) {
auto actual = GetGssStatusValue(nullptr, "my_method", GSS_S_BAD_NAME, 1);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 131072
},
"minor_status": {
"status": 1
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_WithLibrary) {
test::MockGSSAPILibrary library;
auto actual = GetGssStatusValue(&library, "my_method", GSS_S_BAD_NAME, 1);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 131072,
"message": [ "Value: 131072, Type 1" ]
},
"minor_status": {
"status": 1,
"message": [ "Value: 1, Type 2" ]
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_Multiline) {
test::MockGSSAPILibrary library;
auto actual = GetGssStatusValue(
&library, "my_method",
static_cast<OM_uint32>(
test::MockGSSAPILibrary::DisplayStatusSpecials::MultiLine),
0);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 128,
"message": [
"Line 1 for status 128",
"Line 2 for status 128",
"Line 3 for status 128",
"Line 4 for status 128",
"Line 5 for status 128"
]
},
"minor_status": {
"status": 0
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_InfiniteLines) {
test::MockGSSAPILibrary library;
auto actual = GetGssStatusValue(
&library, "my_method",
static_cast<OM_uint32>(
test::MockGSSAPILibrary::DisplayStatusSpecials::InfiniteLines),
0);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 129,
"message": [
"Line 1 for status 129",
"Line 2 for status 129",
"Line 3 for status 129",
"Line 4 for status 129",
"Line 5 for status 129",
"Line 6 for status 129",
"Line 7 for status 129",
"Line 8 for status 129"
]
},
"minor_status": {
"status": 0
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_Failure) {
test::MockGSSAPILibrary library;
auto actual = GetGssStatusValue(
&library, "my_method",
static_cast<OM_uint32>(
test::MockGSSAPILibrary::DisplayStatusSpecials::Fail),
0);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 130
},
"minor_status": {
"status": 0
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_EmptyMessage) {
test::MockGSSAPILibrary library;
auto actual = GetGssStatusValue(
&library, "my_method",
static_cast<OM_uint32>(
test::MockGSSAPILibrary::DisplayStatusSpecials::EmptyMessage),
0);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 131
},
"minor_status": {
"status": 0
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_Misbehave) {
test::MockGSSAPILibrary library;
auto actual = GetGssStatusValue(
&library, "my_method",
static_cast<OM_uint32>(
test::MockGSSAPILibrary::DisplayStatusSpecials::UninitalizedBuffer),
0);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 132
},
"minor_status": {
"status": 0
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetGssStatusValue_NotUtf8) {
test::MockGSSAPILibrary library;
auto actual = GetGssStatusValue(
&library, "my_method",
static_cast<OM_uint32>(
test::MockGSSAPILibrary::DisplayStatusSpecials::InvalidUtf8),
0);
auto expected = base::JSONReader::Read(R"(
{
"function": "my_method",
"major_status": {
"status": 133
},
"minor_status": {
"status": 0
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetContextStateAsValue_ValidContext) {
test::GssContextMockImpl context{"source_spn@somewhere",
"target_spn@somewhere.else",
/* lifetime_rec= */ 100,
*CHROME_GSS_SPNEGO_MECH_OID_DESC,
/* ctx_flags= */ 0,
/* locally_initiated= */ 1,
/* open= */ 0};
test::MockGSSAPILibrary library;
auto actual = GetContextStateAsValue(
&library, reinterpret_cast<const gss_ctx_id_t>(&context));
auto expected = base::JSONReader::Read(R"(
{
"source": {
"name": "source_spn@somewhere",
"type": {
"oid" : "<Empty OID>"
}
},
"target": {
"name": "target_spn@somewhere.else",
"type": {
"oid" : "<Empty OID>"
}
},
"lifetime": "100",
"mechanism": {
"oid": "<Empty OID>"
},
"flags": "00000000",
"open": false
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
TEST(HttpAuthGSSAPITest, GetContextStateAsValue_NoContext) {
test::MockGSSAPILibrary library;
auto actual = GetContextStateAsValue(&library, GSS_C_NO_CONTEXT);
auto expected = base::JSONReader::Read(R"(
{
"error": {
"function": "<none>",
"major_status": {
"status": 524288
},
"minor_status": {
"status": 0
}
}
}
)");
ASSERT_TRUE(expected.has_value());
EXPECT_EQ(actual, expected);
}
} // namespace net
......@@ -342,15 +342,47 @@ OM_uint32 MockGSSAPILibrary::display_status(
const gss_OID mech_type,
OM_uint32* message_context,
gss_buffer_t status_string) {
if (minor_status)
*minor_status = 0;
std::string msg = base::StringPrintf("Value: %u, Type %u",
status_value,
status_type);
if (message_context)
*message_context = 0;
OM_uint32 rv = GSS_S_COMPLETE;
*minor_status = 0;
std::string msg;
switch (static_cast<DisplayStatusSpecials>(status_value)) {
case DisplayStatusSpecials::MultiLine:
msg = base::StringPrintf("Line %u for status %u", ++*message_context,
status_value);
if (*message_context >= 5u)
*message_context = 0u;
break;
case DisplayStatusSpecials::InfiniteLines:
msg = base::StringPrintf("Line %u for status %u", ++*message_context,
status_value);
break;
case DisplayStatusSpecials::Fail:
rv = GSS_S_BAD_MECH;
msg = "You should not see this";
EXPECT_EQ(*message_context, 0u);
break;
case DisplayStatusSpecials::EmptyMessage:
EXPECT_EQ(*message_context, 0u);
break;
case DisplayStatusSpecials::UninitalizedBuffer:
EXPECT_EQ(*message_context, 0u);
return GSS_S_COMPLETE;
case DisplayStatusSpecials::InvalidUtf8:
msg = "\xff\xff\xff";
EXPECT_EQ(*message_context, 0u);
break;
default:
msg = base::StringPrintf("Value: %u, Type %u", status_value, status_type);
EXPECT_EQ(*message_context, 0u);
}
BufferFromString(msg, status_string);
return GSS_S_COMPLETE;
return rv;
}
OM_uint32 MockGSSAPILibrary::init_sec_context(
......
......@@ -128,6 +128,28 @@ class MockGSSAPILibrary : public GSSAPILibrary {
const gss_name_t input_name,
gss_buffer_t output_name_buffer,
gss_OID* output_name_type) override;
// These special status values can be used to trigger specific behavior in
// |display_status()|.
enum class DisplayStatusSpecials : OM_uint32 {
// A multiline status message.
MultiLine = 128,
// Multiline, execept there's no ending message.
InfiniteLines,
// Causes |display_status()| to fail.
Fail,
// Returns an empty message.
EmptyMessage,
// Returns successfully without modifying |status_string|.
UninitalizedBuffer,
// Returns a message that's invalid UTF-8.
InvalidUtf8
};
OM_uint32 display_status(OM_uint32* minor_status,
OM_uint32 status_value,
int status_type,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment