xref: /openbsd/usr.sbin/smtpd/ssl.c (revision 3bef86f7)
1 /*	$OpenBSD: ssl.c,v 1.100 2023/06/25 08:08:03 op Exp $	*/
2 
3 /*
4  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
5  * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
6  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
7  *
8  * Permission to use, copy, modify, and distribute this software for any
9  * purpose with or without fee is hereby granted, provided that the above
10  * copyright notice and this permission notice appear in all copies.
11  *
12  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
19  */
20 
21 #include <sys/stat.h>
22 
23 #include <fcntl.h>
24 #include <limits.h>
25 #include <openssl/err.h>
26 #include <openssl/ssl.h>
27 #include <string.h>
28 #include <unistd.h>
29 
30 #include "log.h"
31 #include "ssl.h"
32 
33 static char *
34 ssl_load_file(const char *name, off_t *len, mode_t perm)
35 {
36 	struct stat	 st;
37 	off_t		 size;
38 	char		*buf = NULL;
39 	int		 fd, saved_errno;
40 	char		 mode[12];
41 
42 	if ((fd = open(name, O_RDONLY)) == -1)
43 		return (NULL);
44 	if (fstat(fd, &st) != 0)
45 		goto fail;
46 	if (st.st_uid != 0) {
47 		log_warnx("warn:  %s: not owned by uid 0", name);
48 		errno = EACCES;
49 		goto fail;
50 	}
51 	if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) {
52 		strmode(perm, mode);
53 		log_warnx("warn:  %s: insecure permissions: must be at most %s",
54 		    name, &mode[1]);
55 		errno = EACCES;
56 		goto fail;
57 	}
58 	size = st.st_size;
59 	if ((buf = calloc(1, size + 1)) == NULL)
60 		goto fail;
61 	if (read(fd, buf, size) != size)
62 		goto fail;
63 	close(fd);
64 
65 	*len = size + 1;
66 	return (buf);
67 
68 fail:
69 	free(buf);
70 	saved_errno = errno;
71 	close(fd);
72 	errno = saved_errno;
73 	return (NULL);
74 }
75 
76 #if 0
77 static int
78 ssl_password_cb(char *buf, int size, int rwflag, void *u)
79 {
80 	size_t	len;
81 	if (u == NULL) {
82 		explicit_bzero(buf, size);
83 		return (0);
84 	}
85 	if ((len = strlcpy(buf, u, size)) >= (size_t)size)
86 		return (0);
87 	return (len);
88 }
89 #endif
90 
91 static int
92 ssl_password_cb(char *buf, int size, int rwflag, void *u)
93 {
94 	int	ret = 0;
95 	size_t	len;
96 	char	*pass;
97 
98 	pass = getpass((const char *)u);
99 	if (pass == NULL)
100 		return 0;
101 	len = strlen(pass);
102 	if (strlcpy(buf, pass, size) >= (size_t)size)
103 		goto end;
104 	ret = len;
105 end:
106 	if (len)
107 		explicit_bzero(pass, len);
108 	return ret;
109 }
110 
111 static char *
112 ssl_load_key(const char *name, off_t *len, char *pass, mode_t perm, const char *pkiname)
113 {
114 	FILE		*fp = NULL;
115 	EVP_PKEY	*key = NULL;
116 	BIO		*bio = NULL;
117 	long		 size;
118 	char		*data, *buf, *filebuf;
119 	struct stat	 st;
120 	char		 mode[12];
121 	char		 prompt[2048];
122 
123 	/*
124 	 * Read (possibly) encrypted key from file
125 	 */
126 	if ((fp = fopen(name, "r")) == NULL)
127 		return (NULL);
128 	if ((filebuf = malloc_conceal(BUFSIZ)) == NULL)
129 		goto fail;
130 	setvbuf(fp, filebuf, _IOFBF, BUFSIZ);
131 
132 	if (fstat(fileno(fp), &st) != 0)
133 		goto fail;
134 	if (st.st_uid != 0) {
135 		log_warnx("warn:  %s: not owned by uid 0", name);
136 		errno = EACCES;
137 		goto fail;
138 	}
139 	if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) {
140 		strmode(perm, mode);
141 		log_warnx("warn:  %s: insecure permissions: must be at most %s",
142 		    name, &mode[1]);
143 		errno = EACCES;
144 		goto fail;
145 	}
146 
147 	(void)snprintf(prompt, sizeof prompt, "passphrase for %s: ", pkiname);
148 	key = PEM_read_PrivateKey(fp, NULL, ssl_password_cb, prompt);
149 	fclose(fp);
150 	fp = NULL;
151 	freezero(filebuf, BUFSIZ);
152 	filebuf = NULL;
153 	if (key == NULL)
154 		goto fail;
155 	/*
156 	 * Write unencrypted key to memory buffer
157 	 */
158 	if ((bio = BIO_new(BIO_s_mem())) == NULL)
159 		goto fail;
160 	if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL))
161 		goto fail;
162 	if ((size = BIO_get_mem_data(bio, &data)) <= 0)
163 		goto fail;
164 	if ((buf = calloc_conceal(1, size + 1)) == NULL)
165 		goto fail;
166 	memcpy(buf, data, size);
167 
168 	BIO_free_all(bio);
169 	EVP_PKEY_free(key);
170 
171 	*len = (off_t)size + 1;
172 	return (buf);
173 
174 fail:
175 	ssl_error("ssl_load_key");
176 	BIO_free_all(bio);
177 	EVP_PKEY_free(key);
178 	if (fp)
179 		fclose(fp);
180 	freezero(filebuf, BUFSIZ);
181 	return (NULL);
182 }
183 
184 int
185 ssl_load_certificate(struct pki *p, const char *pathname)
186 {
187 	p->pki_cert = ssl_load_file(pathname, &p->pki_cert_len, 0755);
188 	if (p->pki_cert == NULL)
189 		return 0;
190 	return 1;
191 }
192 
193 int
194 ssl_load_keyfile(struct pki *p, const char *pathname, const char *pkiname)
195 {
196 	char	pass[1024];
197 
198 	p->pki_key = ssl_load_key(pathname, &p->pki_key_len, pass, 0740, pkiname);
199 	if (p->pki_key == NULL)
200 		return 0;
201 	return 1;
202 }
203 
204 int
205 ssl_load_cafile(struct ca *c, const char *pathname)
206 {
207 	c->ca_cert = ssl_load_file(pathname, &c->ca_cert_len, 0755);
208 	if (c->ca_cert == NULL)
209 		return 0;
210 	return 1;
211 }
212 
213 void
214 ssl_error(const char *where)
215 {
216 	unsigned long	code;
217 	char		errbuf[128];
218 
219 	for (; (code = ERR_get_error()) != 0 ;) {
220 		ERR_error_string_n(code, errbuf, sizeof(errbuf));
221 		log_debug("debug: SSL library error: %s: %s", where, errbuf);
222 	}
223 }
224 
225 static void
226 hash_x509(X509 *cert, char *hash, size_t hashlen)
227 {
228 	static const char	hex[] = "0123456789abcdef";
229 	size_t			off;
230 	char			digest[EVP_MAX_MD_SIZE];
231 	int		 	dlen, i;
232 
233 	if (X509_pubkey_digest(cert, EVP_sha256(), digest, &dlen) != 1)
234 		fatalx("%s: X509_pubkey_digest failed", __func__);
235 
236 	if (hashlen < 2 * dlen + sizeof("SHA256:"))
237 		fatalx("%s: hash buffer too small", __func__);
238 
239 	off = strlcpy(hash, "SHA256:", hashlen);
240 
241 	for (i = 0; i < dlen; i++) {
242 		hash[off++] = hex[(digest[i] >> 4) & 0x0f];
243 		hash[off++] = hex[digest[i] & 0x0f];
244 	}
245 	hash[off] = 0;
246 }
247 
248 char *
249 ssl_pubkey_hash(const char *buf, off_t len)
250 {
251 #define TLS_CERT_HASH_SIZE	128
252 	BIO		*in;
253 	X509		*x509 = NULL;
254 	char		*hash = NULL;
255 
256 	if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
257 		log_warnx("%s: BIO_new_mem_buf failed", __func__);
258 		return NULL;
259 	}
260 
261 	if ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) == NULL) {
262 		log_warnx("%s: PEM_read_bio_X509 failed", __func__);
263 		goto fail;
264 	}
265 
266 	if ((hash = malloc(TLS_CERT_HASH_SIZE)) == NULL) {
267 		log_warn("%s: malloc", __func__);
268 		goto fail;
269 	}
270 	hash_x509(x509, hash, TLS_CERT_HASH_SIZE);
271 
272 fail:
273 	BIO_free(in);
274 
275 	if (x509)
276 		X509_free(x509);
277 
278 	return hash;
279 }
280