blob: 29cc6d07904e84df030178e9d07a16b334be2276 [file] [log] [blame]
Jake Farrellb95b0ff2012-03-22 21:49:10 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19module thrift.internal.ssl;
20
21import core.memory : GC;
22import core.stdc.config;
Jake Farrellc02efe22012-08-18 03:31:28 +000023import core.stdc.errno : errno;
Jake Farrellb95b0ff2012-03-22 21:49:10 +000024import core.stdc.string : strerror;
25import deimos.openssl.err;
26import deimos.openssl.ssl;
27import deimos.openssl.x509v3;
28import std.array : empty, appender;
29import std.conv : to;
30import std.socket : Address;
31import thrift.transport.ssl;
32
33/**
34 * Checks if the peer is authorized after the SSL handshake has been
35 * completed on the given conncetion and throws an TSSLException if not.
36 *
37 * Params:
38 * ssl = The SSL connection to check.
39 * accessManager = The access manager to check the peer againts.
40 * peerAddress = The (IP) address of the peer.
41 * hostName = The host name of the peer.
42 */
43void authorize(SSL* ssl, TAccessManager accessManager,
44 Address peerAddress, lazy string hostName
45) {
46 alias TAccessManager.Decision Decision;
47
48 auto rc = SSL_get_verify_result(ssl);
49 if (rc != X509_V_OK) {
50 throw new TSSLException("SSL_get_verify_result(): " ~
51 to!string(X509_verify_cert_error_string(rc)));
52 }
53
54 auto cert = SSL_get_peer_certificate(ssl);
55 if (cert is null) {
56 // Certificate is not present.
57 if (SSL_get_verify_mode(ssl) & SSL_VERIFY_FAIL_IF_NO_PEER_CERT) {
58 throw new TSSLException(
59 "Authorize: Required certificate not present.");
60 }
61
62 // If we don't have an access manager set, we don't intend to authorize
63 // the client, so everything's fine.
64 if (accessManager) {
65 throw new TSSLException(
66 "Authorize: Certificate required for authorization.");
67 }
68 return;
69 }
70
71 if (accessManager is null) {
72 // No access manager set, can return immediately as the cert is valid
73 // and all peers are authorized.
74 X509_free(cert);
75 return;
76 }
77
78 // both certificate and access manager are present
79 auto decision = accessManager.verify(peerAddress);
80
81 if (decision != Decision.SKIP) {
82 X509_free(cert);
83 if (decision != Decision.ALLOW) {
84 throw new TSSLException("Authorize: Access denied based on remote IP.");
85 }
86 return;
87 }
88
89 // Check subjectAltName(s), if present.
90 auto alternatives = cast(STACK_OF!(GENERAL_NAME)*)
91 X509_get_ext_d2i(cert, NID_subject_alt_name, null, null);
mingwugmail4abc5cf2021-05-26 00:38:22 +020092
93 version(use_openssl_1_0_x) {
94 enum _GEN_DNS = GENERAL_NAME.GEN_DNS;
95 enum _GEN_IPADD = GENERAL_NAME.GEN_IPADD;
96 } else version(use_openssl_1_1_x) {
97 enum _GEN_DNS = GEN_DNS;
98 enum _GEN_IPADD = GEN_IPADD;
99 } else {
100 static assert(false, `Must have version either use_openssl_1_0_x or use_openssl_1_1_x defined, e.g.
101 "subConfigurations": {
102 "apache-thrift": "use_openssl_1_0"
103 }`);
104 }
105
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000106 if (alternatives != null) {
107 auto count = sk_GENERAL_NAME_num(alternatives);
108 for (int i = 0; decision == Decision.SKIP && i < count; i++) {
109 auto name = sk_GENERAL_NAME_value(alternatives, i);
110 if (name is null) {
111 continue;
112 }
113 auto data = ASN1_STRING_data(name.d.ia5);
114 auto length = ASN1_STRING_length(name.d.ia5);
mingwugmail4abc5cf2021-05-26 00:38:22 +0200115
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000116 switch (name.type) {
mingwugmail4abc5cf2021-05-26 00:38:22 +0200117 case _GEN_DNS:
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000118 decision = accessManager.verify(hostName, cast(char[])data[0 .. length]);
119 break;
mingwugmail4abc5cf2021-05-26 00:38:22 +0200120 case _GEN_IPADD:
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000121 decision = accessManager.verify(peerAddress, data[0 .. length]);
122 break;
123 default:
124 // Do nothing.
125 }
126 }
127
128 // DMD @@BUG@@: Empty template arguments parens should not be needed.
129 sk_GENERAL_NAME_pop_free!()(alternatives, &GENERAL_NAME_free);
130 }
131
132 // If we are alredy done, return.
133 if (decision != Decision.SKIP) {
134 X509_free(cert);
135 if (decision != Decision.ALLOW) {
136 throw new TSSLException("Authorize: Access denied.");
137 }
138 return;
139 }
140
141 // Check commonName.
142 auto name = X509_get_subject_name(cert);
143 if (name !is null) {
144 X509_NAME_ENTRY* entry;
145 char* utf8;
146 int last = -1;
147 while (decision == Decision.SKIP) {
148 last = X509_NAME_get_index_by_NID(name, NID_commonName, last);
149 if (last == -1)
150 break;
151 entry = X509_NAME_get_entry(name, last);
152 if (entry is null)
153 continue;
154 auto common = X509_NAME_ENTRY_get_data(entry);
155 auto size = ASN1_STRING_to_UTF8(&utf8, common);
156 decision = accessManager.verify(hostName, utf8[0 .. size]);
157 CRYPTO_free(utf8);
158 }
159 }
160 X509_free(cert);
161 if (decision != Decision.ALLOW) {
162 throw new TSSLException("Authorize: Could not authorize peer.");
163 }
164}
165
166/*
167 * OpenSSL error information used for storing D exceptions on the OpenSSL
168 * error stack.
169 */
170enum ERR_LIB_D_EXCEPTION = ERR_LIB_USER;
171enum ERR_F_D_EXCEPTION = 0; // function id - what to use here?
172enum ERR_R_D_EXCEPTION = 1234; // 99 and above are reserved for applications
173enum ERR_FILE_D_EXCEPTION = "d_exception";
174enum ERR_LINE_D_EXCEPTION = 0;
175enum ERR_FLAGS_D_EXCEPTION = 0;
176
177/**
178 * Returns an exception for the last.
179 *
180 * Params:
181 * location = An optional "location" to add to the error message (typically
182 * the last SSL API call).
183 */
184Exception getSSLException(string location = null, string clientFile = __FILE__,
185 size_t clientLine = __LINE__
186) {
187 // We can return either an exception saved from D BIO code, or a "true"
188 // OpenSSL error. Because there can possibly be more than one error on the
189 // error stack, we have to fetch all of them, and pick the last, i.e. newest
190 // one. We concatenate multiple successive OpenSSL error messages into a
191 // single one, but always just return the last D expcetion.
192 string message; // Probably better use an Appender here.
193 bool hadMessage;
194 Exception exception;
195
196 void initMessage() {
Jens Geyer855cf7f2015-10-08 21:12:57 +0200197 message.destroy();
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000198 hadMessage = false;
199 if (!location.empty) {
200 message ~= location;
201 message ~= ": ";
202 }
203 }
204 initMessage();
205
Jake Farrellc02efe22012-08-18 03:31:28 +0000206 auto errn = errno;
Jake Farrellb95b0ff2012-03-22 21:49:10 +0000207
208 const(char)* file = void;
209 int line = void;
210 const(char)* data = void;
211 int flags = void;
212 c_ulong code = void;
213 while ((code = ERR_get_error_line_data(&file, &line, &data, &flags)) != 0) {
214 if (ERR_GET_REASON(code) == ERR_R_D_EXCEPTION) {
215 initMessage();
216 GC.removeRoot(cast(void*)data);
217 exception = cast(Exception)data;
218 } else {
219 exception = null;
220
221 if (hadMessage) {
222 message ~= ", ";
223 }
224
225 auto reason = ERR_reason_error_string(code);
226 if (reason) {
227 message ~= "SSL error: " ~ to!string(reason);
228 } else {
229 message ~= "SSL error #" ~ to!string(code);
230 }
231
232 hadMessage = true;
233 }
234 }
235
236 // If the last item from the stack was a D exception, throw it.
237 if (exception) return exception;
238
239 // We are dealing with an OpenSSL error that doesn't root in a D exception.
240 if (!hadMessage) {
241 // If we didn't get an actual error from the stack yet, try errno.
242 string errnString;
243 if (errn != 0) {
244 errnString = to!string(strerror(errn));
245 }
246 if (errnString.empty) {
247 message ~= "Unknown error";
248 } else {
249 message ~= errnString;
250 }
251 }
252
253 message ~= ".";
254 return new TSSLException(message, clientFile, clientLine);
255}