1 /*
2 Copyright (c) 2020 Roger Light <roger@atchoo.org>
3 
4 All rights reserved. This program and the accompanying materials
5 are made available under the terms of the Eclipse Public License 2.0
6 and Eclipse Distribution License v1.0 which accompany this distribution.
7 
8 The Eclipse Public License is available at
9    https://www.eclipse.org/legal/epl-2.0/
10 and the Eclipse Distribution License is available at
11   http://www.eclipse.org/org/documents/edl-v10.php.
12 
13 SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
14 
15 Contributors:
16    Roger Light - initial implementation and documentation.
17 */
18 
19 #include "config.h"
20 
21 #include <openssl/bio.h>
22 #include <openssl/buffer.h>
23 #include <openssl/evp.h>
24 #include <openssl/rand.h>
25 
26 #include "dynamic_security.h"
27 #include "mosquitto.h"
28 #include "mosquitto_broker.h"
29 
30 
31 /* ################################################################
32  * #
33  * # Base64 encoding/decoding
34  * #
35  * ################################################################ */
36 
dynsec_auth__base64_encode(unsigned char * in,int in_len,char ** encoded)37 int dynsec_auth__base64_encode(unsigned char *in, int in_len, char **encoded)
38 {
39 	BIO *bmem, *b64;
40 	BUF_MEM *bptr = NULL;
41 
42 	if(in_len < 0) return 1;
43 
44 	b64 = BIO_new(BIO_f_base64());
45 	if(b64 == NULL) return 1;
46 
47 	BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
48 	bmem = BIO_new(BIO_s_mem());
49 	if(bmem == NULL){
50 		BIO_free_all(b64);
51 		return 1;
52 	}
53 	b64 = BIO_push(b64, bmem);
54 	BIO_write(b64, in, in_len);
55 	if(BIO_flush(b64) != 1){
56 		BIO_free_all(b64);
57 		return 1;
58 	}
59 	BIO_get_mem_ptr(b64, &bptr);
60 	*encoded = mosquitto_malloc(bptr->length+1);
61 	if(!(*encoded)){
62 		BIO_free_all(b64);
63 		return 1;
64 	}
65 	memcpy(*encoded, bptr->data, bptr->length);
66 	(*encoded)[bptr->length] = '\0';
67 	BIO_free_all(b64);
68 
69 	return 0;
70 }
71 
72 
dynsec_auth__base64_decode(char * in,unsigned char ** decoded,int * decoded_len)73 int dynsec_auth__base64_decode(char *in, unsigned char **decoded, int *decoded_len)
74 {
75 	BIO *bmem, *b64;
76 	size_t slen;
77 
78 	slen = strlen(in);
79 
80 	b64 = BIO_new(BIO_f_base64());
81 	if(!b64){
82 		return 1;
83 	}
84 	BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
85 
86 	bmem = BIO_new(BIO_s_mem());
87 	if(!bmem){
88 		BIO_free_all(b64);
89 		return 1;
90 	}
91 	b64 = BIO_push(b64, bmem);
92 	BIO_write(bmem, in, (int)slen);
93 
94 	if(BIO_flush(bmem) != 1){
95 		BIO_free_all(b64);
96 		return 1;
97 	}
98 	*decoded = mosquitto_calloc(slen, 1);
99 	if(!(*decoded)){
100 		BIO_free_all(b64);
101 		return 1;
102 	}
103 	*decoded_len =  BIO_read(b64, *decoded, (int)slen);
104 	BIO_free_all(b64);
105 
106 	if(*decoded_len <= 0){
107 		mosquitto_free(*decoded);
108 		*decoded = NULL;
109 		*decoded_len = 0;
110 		return 1;
111 	}
112 
113 	return 0;
114 }
115 
116 
117 /* ################################################################
118  * #
119  * # Password functions
120  * #
121  * ################################################################ */
122 
dynsec_auth__pw_hash(struct dynsec__client * client,const char * password,unsigned char * password_hash,int password_hash_len,bool new_password)123 int dynsec_auth__pw_hash(struct dynsec__client *client, const char *password, unsigned char *password_hash, int password_hash_len, bool new_password)
124 {
125 	const EVP_MD *digest;
126 	int iterations;
127 
128 	if(new_password){
129 		if(RAND_bytes(client->pw.salt, sizeof(client->pw.salt)) != 1){
130 			return MOSQ_ERR_UNKNOWN;
131 		}
132 		iterations = PW_DEFAULT_ITERATIONS;
133 	}else{
134 		iterations = client->pw.iterations;
135 	}
136 	if(iterations < 1){
137 		return MOSQ_ERR_INVAL;
138 	}
139 	client->pw.iterations = iterations;
140 
141 	digest = EVP_get_digestbyname("sha512");
142 	if(!digest){
143 		return MOSQ_ERR_UNKNOWN;
144 	}
145 
146 	return !PKCS5_PBKDF2_HMAC(password, (int)strlen(password),
147 			client->pw.salt, sizeof(client->pw.salt), iterations,
148 			digest, password_hash_len, password_hash);
149 }
150 
151 
152 /* ################################################################
153  * #
154  * # Username/password check
155  * #
156  * ################################################################ */
157 
memcmp_const(const void * a,const void * b,size_t len)158 static int memcmp_const(const void *a, const void *b, size_t len)
159 {
160 	size_t i;
161 	int rc = 0;
162 
163 	if(!a || !b) return 1;
164 
165 	for(i=0; i<len; i++){
166 		if( ((char *)a)[i] != ((char *)b)[i] ){
167 			rc = 1;
168 		}
169 	}
170 	return rc;
171 }
172 
173 
dynsec_auth__basic_auth_callback(int event,void * event_data,void * userdata)174 int dynsec_auth__basic_auth_callback(int event, void *event_data, void *userdata)
175 {
176 	struct mosquitto_evt_basic_auth *ed = event_data;
177 	struct dynsec__client *client;
178 	unsigned char password_hash[64]; /* For SHA512 */
179 	const char *clientid;
180 
181 	UNUSED(event);
182 	UNUSED(userdata);
183 
184 	if(ed->username == NULL || ed->password == NULL) return MOSQ_ERR_PLUGIN_DEFER;
185 
186 	client = dynsec_clients__find(ed->username);
187 	if(client){
188 		if(client->disabled){
189 			return MOSQ_ERR_AUTH;
190 		}
191 		if(client->clientid){
192 			clientid = mosquitto_client_id(ed->client);
193 			if(clientid == NULL || strcmp(client->clientid, clientid)){
194 				return MOSQ_ERR_AUTH;
195 			}
196 		}
197 		if(client->pw.valid && dynsec_auth__pw_hash(client, ed->password, password_hash, sizeof(password_hash), false) == MOSQ_ERR_SUCCESS){
198 			if(memcmp_const(client->pw.password_hash, password_hash, sizeof(password_hash)) == 0){
199 				return MOSQ_ERR_SUCCESS;
200 			}else{
201 				return MOSQ_ERR_AUTH;
202 			}
203 		}else{
204 			return MOSQ_ERR_PLUGIN_DEFER;
205 		}
206 	}else{
207 		return MOSQ_ERR_PLUGIN_DEFER;
208 	}
209 }
210