1/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2/*
3 *     Copyright 2017 Couchbase, Inc.
4 *
5 *   Licensed under the Apache License, Version 2.0 (the "License");
6 *   you may not use this file except in compliance with the License.
7 *   You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *   Unless required by applicable law or agreed to in writing, software
12 *   distributed under the License is distributed on an "AS IS" BASIS,
13 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *   See the License for the specific language governing permissions and
15 *   limitations under the License.
16 */
17
18#include "config.h"
19
20#include <ctype.h>
21#include <errno.h>
22#include <evutil.h>
23#include <fcntl.h>
24#include <getopt.h>
25#include <snappy-c.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <time.h>
30#include <string>
31
32#include <gtest/gtest.h>
33#include <platform/dirutils.h>
34
35#include "memcached/openssl.h"
36#include "testapp.h"
37#include "utilities.h"
38#include "utilities/protocol2text.h"
39
40/**
41 * This test suite tests the various settings for authenticating over SSL
42 * with client certificates. It does not test all of the various paths one
43 * may configure as that is tested in the unit tests for the parsing of the
44 * configuration.
45 */
46
47class SslCertTest : public TestappTest {
48};
49
50/**
51 * Setting the control to "disable" simply means that the server don't
52 * even ask the client to provide a certificate, and if it end up providing
53 * one it'll be ignored.
54 *
55 * Setting the control to "enable" means that we ask the client to provide
56 * a certificate, and if it does it must be:
57 *   * valid
58 *   * contain a user-mapping which maps to a user defined in the system
59 *
60 * Setting the control to "mandatory" means that the client _MUST_ provide
61 * a valid certificate and it must contain a user mapping which maps to a
62 * user defined in the system.
63 *
64 * Connections which is authenticated via certificate cannot perform SASL
65 * to change their identity, and they are not automatically connected to
66 * a bucket with the same name as the user.
67 */
68
69/**
70 * When disabled we don't look at the certificate so it should be possible
71 * to connect without one
72 */
73TEST_F(SslCertTest, LoginWhenDiabledWithoutCert) {
74    reconfigure_client_cert_auth("disable", "", "", "");
75
76    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
77    connection.connect();
78    connection.authenticate("@admin", "password", "PLAIN");
79}
80
81/**
82 * When disabled we don't look at the certificate so it should be possible
83 * to connect with one even if it doesn't map to a user (we've not defined
84 * a user mapping).
85 */
86TEST_F(SslCertTest, LoginWhenDiabledWithCert) {
87    reconfigure_client_cert_auth("disable", "", "", "");
88
89    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
90    setClientCertData(connection);
91    connection.connect();
92    connection.authenticate("@admin", "password", "PLAIN");
93}
94
95/**
96 * When set to enabled we allow the user to connect even if it no certificate
97 * is provided
98 */
99TEST_F(SslCertTest, LoginEnabledWithoutCert) {
100    reconfigure_client_cert_auth("enable", "subject.cn", "", " ");
101
102    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
103    connection.connect();
104    connection.authenticate("@admin", "password", "PLAIN");
105}
106
107/**
108 * It should be possible to connect with a certificate when there is no
109 * mapping defined on the system (only the client certificate is validated)
110 */
111TEST_F(SslCertTest, LoginEnabledWithCertNoMapping) {
112    reconfigure_client_cert_auth("enable", "", "", " ");
113
114    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
115    setClientCertData(connection);
116    connection.connect();
117    connection.authenticate("@admin", "password", "PLAIN");
118}
119
120/**
121 * It should be possible to connect with a certificate which maps to a user.
122 * The connection is not bound to a bucket so the client needs to explicitly
123 * run select bucket to in order to perform operations.
124 */
125TEST_F(SslCertTest, LoginEnabledWithCert) {
126    reconfigure_client_cert_auth("enable", "subject.cn", "", " ");
127
128    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
129    setClientCertData(connection);
130    connection.connect();
131    connection.setXerrorSupport(true);
132
133    try {
134        connection.get("foo", 0);
135        FAIL() << "Should not be associated with a bucket";
136    } catch (const ConnectionError& error) {
137        EXPECT_TRUE(error.isAccessDenied())
138                << "Received: 0x" << std::hex << error.getReason();
139    }
140
141    connection.selectBucket("default");
142    try {
143        connection.get("foo", 0);
144        FAIL() << "document should not exists";
145    } catch (const ConnectionError& error) {
146        EXPECT_TRUE(error.isNotFound())
147                << "Received: 0x" << std::hex << error.getReason();
148    }
149}
150
151/**
152 * When the setting is set to mandatory a client certificate _HAS_ to be
153 * provided in order to allow the connection to succeed.
154 */
155TEST_F(SslCertTest, LoginWhenMandatoryWithoutCert) {
156    reconfigure_client_cert_auth("mandatory", "subject.cn", "", " ");
157
158    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
159    try {
160        connection.connect();
161        FAIL() << "It should not be possible to connect without certificate";
162    } catch (const std::exception&) {
163    }
164}
165
166/**
167 * Verify that we may log into the system when we provide a certificate,
168 * and that we're not automatically bound to a bucket (an explicit select
169 * bucket is needed).
170 */
171TEST_F(SslCertTest, LoginWhenMandatoryWithCert) {
172    reconfigure_client_cert_auth("mandatory", "subject.cn", "", " ");
173
174    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
175    setClientCertData(connection);
176    connection.connect();
177    connection.setXerrorSupport(true);
178
179    try {
180        connection.get("foo", 0);
181        FAIL() << "Should not be associated with a bucket";
182    } catch (const ConnectionError& error) {
183        EXPECT_TRUE(error.isAccessDenied())
184                << "Received: 0x" << std::hex << error.getReason();
185    }
186
187    connection.selectBucket("default");
188    try {
189        connection.get("foo", 0);
190        FAIL() << "document should not exists";
191    } catch (const ConnectionError& error) {
192        EXPECT_TRUE(error.isNotFound())
193                << "Received: 0x" << std::hex << error.getReason();
194    }
195}
196
197/**
198 * The system should _only_ allow users into the system where the information
199 * in the certificate map to a user defined in the system.
200 */
201TEST_F(SslCertTest, LoginWhenMandatoryWithCertIncorrectMapping) {
202    reconfigure_client_cert_auth("mandatory", "subject.cn", "Tr", "");
203
204    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
205    setClientCertData(connection);
206
207    // The certificate will be accepted, so the connection is established
208    // but the server will disconnect the client immediately
209    connection.connect();
210
211    // Try to run a hello (should NOT work)
212    try {
213        connection.setXerrorSupport(true);
214        FAIL() << "The server should disconnect the client due to missing RBAC lookup";
215    } catch (const std::exception&) {
216
217    }
218}
219
220/**
221 * A client who authenticated itself by using a certificate should not
222 * be able to change it's identity by running SASL.
223 */
224TEST_F(SslCertTest, LoginWhenMandatoryWithCertShouldNotSupportSASL) {
225    reconfigure_client_cert_auth("mandatory", "subject.cn", "", " ");
226
227    MemcachedConnection connection("127.0.0.1", ssl_port, AF_INET, true);
228    setClientCertData(connection);
229    connection.connect();
230    connection.setXerrorSupport(true);
231
232    try {
233        connection.authenticate("@admin", "password", "PLAIN");
234        FAIL() << "SASL Auth should be disabled for cert auth'd connections";
235    } catch (const ConnectionError& error) {
236        EXPECT_TRUE(error.isNotSupported())
237                    << "Received: 0x" << std::hex << error.getReason();
238    }
239}
240