Skip to content

Commit a6c1b92

Browse files
committed
WL#15135 patch #2: Class TlsKeyManager
Part of WL#15135 Certificate Architecture This patch introduces class TlsKeyManager, containing all TLS authentication and key management logic. A single instance of TlsKeyManager in each node owns the local NodeCertificate, an SSL_CTX, and a table holding the serial numbers and expiration dates of all peer certificates. A large set of TLS-related error codes is introduced in the file TlsKeyErrors.h. The unit test testTlsKeyManager-t tests TLS authentication over client/server connections on localhost. Change-Id: I2ee42efc268219639691f73a1d7638a336844d88
1 parent 977a264 commit a6c1b92

File tree

6 files changed

+1615
-3
lines changed

6 files changed

+1615
-3
lines changed

storage/ndb/include/util/NdbSocket.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,10 @@ class NdbSocket {
129129
*/
130130
bool key_update_pending() const;
131131

132-
/* Get peer's TLS certificate
132+
/* Get peer's TLS certificate, and update the certificate reference count.
133133
*
134-
* Returns nullptr if socket does not have TLS enabled.
134+
* The caller should free the returned pointer using Certificate::free().
135+
* Returns nullptr if no certificate is available.
135136
*/
136137
struct x509_st * peer_certificate() const;
137138

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
Copyright (c) 2022, 2023, Oracle and/or its affiliates.
3+
4+
This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License, version 2.0,
6+
as published by the Free Software Foundation.
7+
8+
This program is also distributed with certain software (including
9+
but not limited to OpenSSL) that is licensed under separate terms,
10+
as designated in a particular file or component or in included license
11+
documentation. The authors of MySQL hereby grant you an additional
12+
permission to link the program and your derivative works with the
13+
separately licensed software that they have included with MySQL.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU General Public License, version 2.0, for more details.
19+
20+
You should have received a copy of the GNU General Public License
21+
along with this program; if not, write to the Free Software
22+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23+
*/
24+
25+
#ifndef NDB_UTIL_TLS_KEY_ERRORS_H
26+
#define NDB_UTIL_TLS_KEY_ERRORS_H
27+
28+
class TlsKeyError {
29+
public:
30+
enum code {
31+
no_error = 0,
32+
negotiation_failure = 1,
33+
openssl_error = 2,
34+
no_local_cert = 3,
35+
authentication_failure = 4,
36+
auth2_bad_socket = 5,
37+
auth2_no_cert = 6,
38+
auth2_bad_common_name = 7,
39+
auth2_bad_hostname = 8,
40+
auth2_resolver_error = 9,
41+
/* space for 3 more auth errors */
42+
verification_error = 13,
43+
signing_error = 14,
44+
lifetime_error = 15,
45+
password_error = 16,
46+
cannot_store_ca_key = 17,
47+
cannot_store_ca_cert = 18,
48+
failed_to_init_ca = 19,
49+
ca_key_not_found = 20,
50+
ca_cert_not_found = 21,
51+
cannot_read_ca_key = 22,
52+
cannot_read_ca_cert = 23,
53+
/* space for 3 more generic errors */
54+
END_GENERIC_ERRORS = 27,
55+
cannot_store_pending_key = 28,
56+
pending_key_not_found = 29,
57+
active_key_not_found = 30,
58+
cannot_read_pending_key = 31,
59+
cannot_read_active_key = 32,
60+
cannot_promote_key = 33,
61+
cannot_store_signing_req = 34,
62+
cannot_read_signing_req = 35,
63+
cannot_remove_signing_req = 36,
64+
pending_cert_fails_auth = 37,
65+
cannot_store_pending_cert = 38,
66+
pending_cert_not_found = 39,
67+
active_cert_not_found = 40,
68+
cannot_read_pending_cert = 41,
69+
cannot_read_active_cert = 42,
70+
cannot_promote_cert = 43,
71+
active_cert_mismatch = 44,
72+
active_cert_invalid = 45,
73+
active_cert_expired = 46,
74+
no_writable_dir = 47,
75+
END_ERRORS
76+
};
77+
78+
static const char * message(int n) {
79+
if(n > 0 && n < code::END_ERRORS)
80+
return _message[n];
81+
return "(unknown error code)";
82+
}
83+
84+
private:
85+
static constexpr const char * _message[code::END_ERRORS] =
86+
{
87+
"(no error)",
88+
"protocol negotiation failure",
89+
"openssl error",
90+
"no certificate",
91+
"authentication failure",
92+
"authorization failure: socket error",
93+
"authorization failure: no peer certificate",
94+
"authorization failure: subject common name is non-conforming",
95+
"authorization failure: bad hostname",
96+
"authorization failure: resolver error",
97+
"(unused code 10)",
98+
"(unused code 11)",
99+
"(unused code 12)",
100+
101+
"signature verification error",
102+
"signing error",
103+
"invalid certificate lifetime parameters",
104+
"password error",
105+
"failed to store CA key file",
106+
"failed to store CA certificate file",
107+
"failed to initialize Certification Authority",
108+
"CA key file not found",
109+
"CA certificate file not found",
110+
"cannot read CA key file",
111+
"cannot read CA certificate file",
112+
"(unused code 24)",
113+
"(unused code 25)",
114+
"(unused code 26)",
115+
"(unused code 27)", // END_GENERIC_ERRORS
116+
117+
"cannot store pending key file",
118+
"pending key not found",
119+
"active key not found",
120+
"cannot read pending key file",
121+
"cannot read active key file",
122+
"error promoting pending key to active",
123+
"cannot store signing request",
124+
"cannot read signing request",
125+
"cannot remove signing request",
126+
"pending certificate fails authentication against active CA chain",
127+
"cannot store pending certificate file",
128+
"pending certificate not found",
129+
"active certificate not found",
130+
"cannot read pending certificate file",
131+
"cannot read active certificate file",
132+
"error promoting pending certificate to active",
133+
"node certificate has wrong public key",
134+
"active node certificate is not valid",
135+
"active node certificate has expired",
136+
"no writable directory found in TLS search path"
137+
};
138+
};
139+
140+
#endif
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
/* Copyright (c) 2022, 2023, Oracle and/or its affiliates.
2+
3+
This program is free software; you can redistribute it and/or modify
4+
it under the terms of the GNU General Public License, version 2.0,
5+
as published by the Free Software Foundation.
6+
7+
This program is also distributed with certain software (including
8+
but not limited to OpenSSL) that is licensed under separate terms,
9+
as designated in a particular file or component or in included license
10+
documentation. The authors of MySQL hereby grant you an additional
11+
permission to link the program and your derivative works with the
12+
separately licensed software that they have included with MySQL.
13+
14+
This program is distributed in the hope that it will be useful,
15+
but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
GNU General Public License, version 2.0, for more details.
18+
19+
You should have received a copy of the GNU General Public License
20+
along with this program; if not, write to the Free Software
21+
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22+
*/
23+
24+
#ifndef NDB_UTIL_TLS_KEY_MANAGER_H
25+
#define NDB_UTIL_TLS_KEY_MANAGER_H
26+
27+
#include "ndb_limits.h" // MAX_NODES
28+
29+
#include "portlib/NdbMutex.h"
30+
#include "util/NodeCertificate.hpp"
31+
#include "util/NdbSocket.h"
32+
#include "util/TlsKeyErrors.h"
33+
34+
struct cert_table_entry {
35+
time_t expires;
36+
const char * name;
37+
const char * serial;
38+
};
39+
40+
class ClientAuthorization;
41+
42+
class TlsKeyManager {
43+
public:
44+
enum TlsVersion { Tls12, Tls13 };
45+
46+
TlsKeyManager();
47+
~TlsKeyManager();
48+
49+
/* TlsKeyManager::init() for NDB nodes.
50+
All error and info messages are logged to g_EventLogger.
51+
You can test whether init() has succeeded by calling ctx().
52+
*/
53+
void init(const char * tls_search_path,
54+
int node_id, int node_type, bool is_primary);
55+
56+
/* init() for MGM Clients that will not have a node ID */
57+
void init_mgm_client(const char * tls_search_path,
58+
Node::Type type = Node::Type::Client);
59+
60+
/* Alternate versions of init() used for authentication testing */
61+
void init(int node_id, const NodeCertificate *);
62+
void init(int node_id, struct stack_st_X509 *, struct evp_pkey_st *);
63+
64+
/* Get SSL_CTX */
65+
struct ssl_ctx_st * ctx() const { return m_ctx; }
66+
67+
void cert_table_set(int node_id, struct x509_st *);
68+
void cert_table_clear(int node_id);
69+
bool iterate_cert_table(int & node_id, cert_table_entry *);
70+
71+
// Check replacement date of our own node certificate
72+
// pct should be a number between 0.0 and 1.0, where 0 represents the
73+
// not-valid-before date 1 represents the not-valid-after date.
74+
// Returns true if the current time is strictly less than pct.
75+
bool check_replace_date(float pct);
76+
77+
/* Class method: TLS verification callback */
78+
static int on_verify(int result, struct x509_store_ctx_st *);
79+
80+
/* Class methods: certificate hostname authorization checks.
81+
82+
The check of a server's certificate is a simple comparison between the
83+
hostnames in the cert and the name the client used in order to reach the
84+
server.
85+
86+
The check of a client's certificate requires a DNS lookup. It is divided
87+
into a "fast" part, in check_socket_for_auth(), and a "slow" (blocking)
88+
part, in perform_client_host_auth(). The API is designed to allow the
89+
slow part to run asynchronously if needed.
90+
*/
91+
92+
// Client-side checks of server cert:
93+
static int check_server_host_auth(const NdbSocket &, const char *name);
94+
static int check_server_host_auth(struct x509_st *, const char *name);
95+
static int check_server_host_auth(const NodeCertificate &, const char *);
96+
97+
/* Server-side check of client cert:
98+
99+
check_socket_for_auth() can return a non-zero TlsKeyError error code:
100+
auth2_no_cert if socket has no client certificate;
101+
auth2_bad_common_name if cert CN is not valid for NDB;
102+
auth2_bad_socket if getpeername() fails.
103+
104+
Otherwise it returns zero, and the caller should check the contents
105+
of pAuth. If *pAuth is null, the certificate is not bound to a
106+
hostname, so authorization is complete. If *pAuth is non-null,
107+
hostname authorization is required, and the user should call
108+
perform_client_host_auth(*pAuth).
109+
*/
110+
static int check_socket_for_auth(const NdbSocket & socket,
111+
ClientAuthorization ** pAuth);
112+
113+
/* test harness */
114+
static ClientAuthorization * test_client_auth(struct x509_st *,
115+
const struct addrinfo *);
116+
117+
/* perform_client_host_auth() checks the socket peer against the certificate
118+
hostname, using DNS lookup. It will block, synchronously waiting for
119+
DNS. On return, the supplied ClientAuthorization will have been
120+
deleted. Returns a TlsKeyError code.
121+
*/
122+
static int perform_client_host_auth(ClientAuthorization *);
123+
124+
125+
protected:
126+
void initialize_context();
127+
128+
void log_error(TlsKeyError::code);
129+
void log_error() const;
130+
131+
bool open_active_cert();
132+
133+
static constexpr Node::Type cert_type[3] = {
134+
/* indexed to NODE_TYPE_DB, NODE_TYPE_API, NODE_TYPE_MGM */
135+
Node::Type::DB, Node::Type::Client, Node::Type::MGMD
136+
};
137+
138+
private:
139+
enum UserType { Primary, SecondaryApi, MgmClient };
140+
141+
PkiFile::PathName m_key_file, m_cert_file;
142+
char * m_path_string {nullptr};
143+
TlsSearchPath * m_search_path {nullptr};
144+
static constexpr size_t SN_buf_len = 30;
145+
static constexpr size_t CN_buf_len = 65;
146+
struct private_cert_table_entry {
147+
char serial[SN_buf_len];
148+
char name[CN_buf_len];
149+
time_t expires {0};
150+
bool active {false};
151+
} m_cert_table[MAX_NODES];
152+
NodeCertificate m_node_cert;
153+
NdbMutex m_cert_table_mutex;
154+
int m_error {0};
155+
int m_node_id;
156+
Node::Type m_type;
157+
struct ssl_ctx_st * m_ctx {nullptr};
158+
159+
void init(const char *, int, Node::Type, UserType);
160+
161+
bool cert_table_get(const private_cert_table_entry &,
162+
cert_table_entry *) const;
163+
void free_path_strings();
164+
};
165+
166+
inline void TlsKeyManager::init_mgm_client(const char * tls_search_path,
167+
Node::Type type)
168+
{
169+
init(tls_search_path, 0, type, MgmClient);
170+
}
171+
172+
#endif

storage/ndb/src/common/util/CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ ADD_CONVENIENCE_LIBRARY(ndbgeneral
6161
SocketAuthenticator.cpp
6262
SocketClient.cpp
6363
SocketServer.cpp
64+
TlsKeyManager.cpp
6465
Vector.cpp
6566
basename.cpp
6667
decimal_utils.cpp
@@ -81,7 +82,7 @@ ADD_CONVENIENCE_LIBRARY(ndbgeneral
8182
SET_TARGET_PROPERTIES(ndbgeneral PROPERTIES LINK_INTERFACE_MULTIPLICITY 3)
8283

8384
FOREACH(tests BaseString Bitmask SparseBitmask Parser HashMap2 LinkedStack
84-
NodeCertificate ndb_zlib cstrbuf span)
85+
NodeCertificate TlsKeyManager ndb_zlib cstrbuf span)
8586
NDB_ADD_TEST("${tests}-t" "${tests}.cpp" LIBS ndbgeneral)
8687
ENDFOREACH(tests)
8788

@@ -106,6 +107,7 @@ FOREACH(tests
106107
testProp
107108
testSecureSocket
108109
testConfigValues
110+
testTlsKeyManager
109111
)
110112
NDB_ADD_TEST("${tests}-t" "${tests}.cpp" LIBS ndbmgmapi ndbgeneral ndbportlib)
111113
ENDFOREACH(tests)

0 commit comments

Comments
 (0)