Line data Source code
1 : /*
2 : * SPDX-License-Identifier: MPL-2.0
3 : *
4 : * This Source Code Form is subject to the terms of the Mozilla Public
5 : * License, v. 2.0. If a copy of the MPL was not distributed with this
6 : * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 : *
8 : * Copyright 2024 MonetDB Foundation;
9 : * Copyright August 2008 - 2023 MonetDB B.V.;
10 : * Copyright 1997 - July 2008 CWI.
11 : */
12 :
13 : /*
14 : * (authors) M. Kersten, F. Groffen
15 : * Authorisation adminstration management
16 : * Authorisation of users is a key concept in protecting the server from
17 : * malicious and unauthorised users. This file contains a number of
18 : * functions that administrate a set of BATs backing the authorisation
19 : * tables.
20 : *
21 : * The implementation is based on three persistent BATs, which keep the
22 : * usernames, passwords and allowed scenarios for users of the server.
23 : *
24 : */
25 : #include "monetdb_config.h"
26 : #include "mal_authorize.h"
27 : #include "mal_exception.h"
28 : #include "mal_private.h"
29 : #include "mcrypt.h"
30 : #include "msabaoth.h"
31 : #include "mal_scenario.h"
32 : #include "mal_interpreter.h"
33 :
34 : #ifdef HAVE_UNISTD_H
35 : #include <unistd.h>
36 : #endif
37 :
38 : /* yep, the vault key is just stored in memory */
39 : static str vaultKey = NULL;
40 : /* lock to protect the above */
41 : static MT_RWLock rt_lock = MT_RWLOCK_INITIALIZER(rt_lock);
42 :
43 : static str AUTHdecypherValueLocked(str *ret, const char *value);
44 :
45 : void
46 339 : AUTHreset(void)
47 : {
48 339 : MT_rwlock_wrlock(&rt_lock);
49 339 : GDKfree(vaultKey);
50 339 : vaultKey = NULL;
51 339 : MT_rwlock_wrunlock(&rt_lock);
52 339 : }
53 :
54 : /**
55 : * Requires the current client to be the admin user thread. If not the case,
56 : * this function returns an InvalidCredentialsException.
57 : */
58 : str
59 52 : AUTHrequireAdmin(Client cntxt)
60 : {
61 52 : assert(cntxt);
62 :
63 52 : if (cntxt->user != MAL_ADMIN)
64 0 : throw(MAL, "AUTHrequireAdmin", INVCRED_ACCESS_DENIED);
65 : return (MAL_SUCCEED);
66 : }
67 :
68 : /*=== the vault ===*/
69 :
70 : /**
71 : * Unlocks the vault with the given password. Since the password is
72 : * just the decypher key, it is not possible to directly check whether
73 : * the given password is correct. If incorrect, however, all decypher
74 : * operations will probably fail or return an incorrect decyphered
75 : * value.
76 : */
77 : str
78 341 : AUTHunlockVault(const char *password)
79 : {
80 341 : if (strNil(password))
81 0 : throw(ILLARG, "unlockVault", "password should not be nil");
82 :
83 : /* even though I think this function should be called only once, it
84 : * is not of real extra efforts to avoid a mem-leak if it is used
85 : * multiple times */
86 341 : MT_rwlock_wrlock(&rt_lock);
87 341 : GDKfree(vaultKey);
88 :
89 341 : vaultKey = GDKstrdup(password);
90 341 : if (vaultKey == NULL) {
91 0 : MT_rwlock_wrunlock(&rt_lock);
92 0 : throw(MAL, "unlockVault", SQLSTATE(HY013) MAL_MALLOC_FAIL " vault key");
93 : }
94 341 : MT_rwlock_wrunlock(&rt_lock);
95 341 : return (MAL_SUCCEED);
96 : }
97 :
98 : /**
99 : * Decyphers a given value, using the vaultKey. The returned value
100 : * might be incorrect if the vaultKey is incorrect or unset. If the
101 : * cypher algorithm fails or detects an invalid password, it might throw
102 : * an exception. The ret string is GDKmalloced, and should be GDKfreed
103 : * by the caller.
104 : */
105 : static str
106 37974 : AUTHdecypherValueLocked(str *ret, const char *value)
107 : {
108 : /* Cyphering and decyphering can be done using many algorithms.
109 : * Future requirements might want a stronger cypher than the XOR
110 : * cypher chosen here. It is left up to the implementor how to do
111 : * that once those algoritms become available. It could be
112 : * #ifdef-ed or on if-basis depending on whether the cypher
113 : * algorithm is a compile, or runtime option. When necessary, this
114 : * function could be extended with an extra argument that indicates
115 : * the cypher algorithm.
116 : */
117 :
118 : /* this is the XOR decypher implementation */
119 37974 : str r, w;
120 37974 : const char *s = value;
121 37974 : char t = '\0';
122 37974 : bool escaped = false;
123 : /* we default to some garbage key, just to make password unreadable
124 : * (a space would only uppercase the password) */
125 37974 : size_t keylen = 0;
126 :
127 37974 : if (vaultKey == NULL)
128 0 : throw(MAL, "decypherValue", "The vault is still locked!");
129 37974 : w = r = GDKmalloc(sizeof(char) * (strlen(value) + 1));
130 37974 : if (r == NULL)
131 0 : throw(MAL, "decypherValue", SQLSTATE(HY013) MAL_MALLOC_FAIL);
132 :
133 37974 : keylen = strlen(vaultKey);
134 :
135 : /* XOR all characters. If we encounter a 'one' char after the XOR
136 : * operation, it is an escape, so replace it with the next char. */
137 4930468 : for (; (t = *s) != '\0'; s++) {
138 4892494 : if ((t & 0xE0) == 0xC0) {
139 0 : assert((t & 0x1E) == 0x02);
140 0 : assert((s[1] & 0xC0) == 0x80);
141 0 : t = ((t & 0x1F) << 6) | (*++s & 0x3F);
142 : }
143 4892494 : if (t == '\1' && !escaped) {
144 52046 : escaped = true;
145 52046 : continue;
146 4840448 : } else if (escaped) {
147 52046 : t -= 1;
148 52046 : escaped = false;
149 : }
150 4840448 : *w = t ^ vaultKey[(w - r) % keylen];
151 4840448 : w++;
152 : }
153 37974 : *w = '\0';
154 :
155 37974 : *ret = r;
156 37974 : return (MAL_SUCCEED);
157 : }
158 :
159 : str
160 37973 : AUTHdecypherValue(str *ret, const char *value)
161 : {
162 37973 : MT_rwlock_rdlock(&rt_lock);
163 37974 : str err = AUTHdecypherValueLocked(ret, value);
164 37974 : MT_rwlock_rdunlock(&rt_lock);
165 37974 : return err;
166 : }
167 :
168 : /**
169 : * Cyphers the given string using the vaultKey. If the cypher algorithm
170 : * fails or detects an invalid password, it might throw an exception.
171 : * The ret string is GDKmalloced, and should be GDKfreed by the caller.
172 : */
173 : static str
174 631 : AUTHcypherValueLocked(str *ret, const char *value)
175 : {
176 : /* this is the XOR cypher implementation */
177 631 : str r, w;
178 631 : const char *s = value;
179 : /* we default to some garbage key, just to make password unreadable
180 : * (a space would only uppercase the password) */
181 631 : size_t keylen = 0;
182 :
183 631 : if (vaultKey == NULL)
184 0 : throw(MAL, "cypherValue", "The vault is still locked!");
185 631 : w = r = GDKmalloc(sizeof(char) * (strlen(value) * 2 + 1));
186 631 : if (r == NULL)
187 0 : throw(MAL, "cypherValue", SQLSTATE(HY013) MAL_MALLOC_FAIL);
188 :
189 631 : keylen = strlen(vaultKey);
190 :
191 : /* XOR all characters. If we encounter a 'zero' char after the XOR
192 : * operation, escape it with a 'one' char. */
193 81399 : for (; *s != '\0'; s++) {
194 80768 : char c = *s ^ vaultKey[(s - value) % keylen];
195 80768 : if (c == '\0') {
196 1881 : *w++ = '\1';
197 1881 : *w = '\1';
198 78887 : } else if (c == '\1') {
199 738 : *w++ = '\1';
200 738 : *w = '\2';
201 78149 : } else if (c & 0x80) {
202 0 : *w++ = 0xC0 | ((c >> 6) & 0x03);
203 0 : *w = 0x80 | (c & 0x3F);
204 : } else {
205 78149 : *w = c;
206 : }
207 80768 : w++;
208 : }
209 631 : *w = '\0';
210 :
211 631 : *ret = r;
212 631 : return (MAL_SUCCEED);
213 : }
214 :
215 : str
216 631 : AUTHcypherValue(str *ret, const char *value)
217 : {
218 631 : MT_rwlock_rdlock(&rt_lock);
219 631 : str err = AUTHcypherValueLocked(ret, value);
220 631 : MT_rwlock_rdunlock(&rt_lock);
221 631 : return err;
222 : }
223 :
224 : /**
225 : * Checks if the given string is a (hex represented) hash for the
226 : * current backend. This check allows to at least forbid storing
227 : * trivial plain text passwords by a simple check.
228 : */
229 : #define concat(x,y) x##y
230 : #define digestlength(h) concat(h, _DIGEST_LENGTH)
231 : str
232 3 : AUTHverifyPassword(const char *passwd)
233 : {
234 3 : const char *p = passwd;
235 3 : size_t len = strlen(p);
236 :
237 3 : if (len != digestlength(MONETDB5_PASSWDHASH_TOKEN) * 2) {
238 0 : throw(MAL, "verifyPassword",
239 : "password is not %d chars long, is it a hex "
240 : "representation of a %s password hash?",
241 : digestlength(MONETDB5_PASSWDHASH_TOKEN), MONETDB5_PASSWDHASH);
242 : }
243 387 : len++; // required in case all the checks above are false
244 387 : while (*p != '\0') {
245 384 : if (!((*p >= 'a' && *p <= 'z') || isdigit((unsigned char) *p)))
246 0 : throw(MAL, "verifyPassword",
247 : "password does contain invalid characters, is it a"
248 : "lowercase hex representation of a hash?");
249 384 : p++;
250 : }
251 :
252 : return (MAL_SUCCEED);
253 : }
254 :
255 : str
256 535 : AUTHGeneratePasswordHash(str *res, const char *value)
257 : {
258 535 : return AUTHcypherValue(res, value);
259 : }
|