xref: /openbsd-src/usr.sbin/smtpd/ssl.c (revision 9702801129f0090124ffb1b6626ffa79ab8bef79)
1*97028011Sop /*	$OpenBSD: ssl.c,v 1.100 2023/06/25 08:08:03 op Exp $	*/
21f3c2bcfSsobrado 
33ef9cbf7Sgilles /*
43ef9cbf7Sgilles  * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org>
53ef9cbf7Sgilles  * Copyright (c) 2008 Reyk Floeter <reyk@openbsd.org>
665c4fdfbSgilles  * Copyright (c) 2012 Gilles Chehade <gilles@poolp.org>
73ef9cbf7Sgilles  *
83ef9cbf7Sgilles  * Permission to use, copy, modify, and distribute this software for any
93ef9cbf7Sgilles  * purpose with or without fee is hereby granted, provided that the above
103ef9cbf7Sgilles  * copyright notice and this permission notice appear in all copies.
113ef9cbf7Sgilles  *
123ef9cbf7Sgilles  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
133ef9cbf7Sgilles  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
143ef9cbf7Sgilles  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
153ef9cbf7Sgilles  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
163ef9cbf7Sgilles  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
173ef9cbf7Sgilles  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
183ef9cbf7Sgilles  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
193ef9cbf7Sgilles  */
203ef9cbf7Sgilles 
213ef9cbf7Sgilles #include <sys/stat.h>
223ef9cbf7Sgilles 
233ef9cbf7Sgilles #include <fcntl.h>
24299c4efeSeric #include <limits.h>
2541b8cf0bSmillert #include <openssl/err.h>
26d3140113Seric #include <openssl/ssl.h>
27d3140113Seric #include <string.h>
28d3140113Seric #include <unistd.h>
293ef9cbf7Sgilles 
305eb8dddaSgilles #include "log.h"
3165c4fdfbSgilles #include "ssl.h"
323ef9cbf7Sgilles 
334889cc2aSeric static char *
ssl_load_file(const char * name,off_t * len,mode_t perm)34bbdba859Shalex ssl_load_file(const char *name, off_t *len, mode_t perm)
353ef9cbf7Sgilles {
363ef9cbf7Sgilles 	struct stat	 st;
373ef9cbf7Sgilles 	off_t		 size;
383ef9cbf7Sgilles 	char		*buf = NULL;
393677b057Seric 	int		 fd, saved_errno;
40bbdba859Shalex 	char		 mode[12];
413ef9cbf7Sgilles 
423ef9cbf7Sgilles 	if ((fd = open(name, O_RDONLY)) == -1)
433ef9cbf7Sgilles 		return (NULL);
443ef9cbf7Sgilles 	if (fstat(fd, &st) != 0)
453ef9cbf7Sgilles 		goto fail;
463677b057Seric 	if (st.st_uid != 0) {
4782614934Seric 		log_warnx("warn:  %s: not owned by uid 0", name);
483677b057Seric 		errno = EACCES;
493677b057Seric 		goto fail;
503677b057Seric 	}
51bbdba859Shalex 	if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) {
52bbdba859Shalex 		strmode(perm, mode);
5382614934Seric 		log_warnx("warn:  %s: insecure permissions: must be at most %s",
54bbdba859Shalex 		    name, &mode[1]);
553677b057Seric 		errno = EACCES;
563677b057Seric 		goto fail;
573677b057Seric 	}
583ef9cbf7Sgilles 	size = st.st_size;
593ef9cbf7Sgilles 	if ((buf = calloc(1, size + 1)) == NULL)
603ef9cbf7Sgilles 		goto fail;
613ef9cbf7Sgilles 	if (read(fd, buf, size) != size)
623ef9cbf7Sgilles 		goto fail;
633ef9cbf7Sgilles 	close(fd);
643ef9cbf7Sgilles 
653ef9cbf7Sgilles 	*len = size + 1;
663ef9cbf7Sgilles 	return (buf);
673ef9cbf7Sgilles 
683ef9cbf7Sgilles fail:
693ef9cbf7Sgilles 	free(buf);
703677b057Seric 	saved_errno = errno;
713ef9cbf7Sgilles 	close(fd);
723677b057Seric 	errno = saved_errno;
733ef9cbf7Sgilles 	return (NULL);
743ef9cbf7Sgilles }
753ef9cbf7Sgilles 
7676a0ecd1Seric #if 0
7765c4fdfbSgilles static int
7865c4fdfbSgilles ssl_password_cb(char *buf, int size, int rwflag, void *u)
7965c4fdfbSgilles {
8065c4fdfbSgilles 	size_t	len;
8165c4fdfbSgilles 	if (u == NULL) {
828fbd7fcbSdoug 		explicit_bzero(buf, size);
8365c4fdfbSgilles 		return (0);
8465c4fdfbSgilles 	}
8565c4fdfbSgilles 	if ((len = strlcpy(buf, u, size)) >= (size_t)size)
8665c4fdfbSgilles 		return (0);
8765c4fdfbSgilles 	return (len);
8865c4fdfbSgilles }
8976a0ecd1Seric #endif
9076a0ecd1Seric 
9176a0ecd1Seric static int
ssl_password_cb(char * buf,int size,int rwflag,void * u)92d5cc82f9Sreyk ssl_password_cb(char *buf, int size, int rwflag, void *u)
9376a0ecd1Seric {
9476a0ecd1Seric 	int	ret = 0;
9576a0ecd1Seric 	size_t	len;
9676a0ecd1Seric 	char	*pass;
9776a0ecd1Seric 
9876a0ecd1Seric 	pass = getpass((const char *)u);
9976a0ecd1Seric 	if (pass == NULL)
10076a0ecd1Seric 		return 0;
10176a0ecd1Seric 	len = strlen(pass);
10276a0ecd1Seric 	if (strlcpy(buf, pass, size) >= (size_t)size)
10376a0ecd1Seric 		goto end;
10476a0ecd1Seric 	ret = len;
10576a0ecd1Seric end:
10676a0ecd1Seric 	if (len)
1078fbd7fcbSdoug 		explicit_bzero(pass, len);
10876a0ecd1Seric 	return ret;
10976a0ecd1Seric }
11065c4fdfbSgilles 
1114889cc2aSeric static char *
ssl_load_key(const char * name,off_t * len,char * pass,mode_t perm,const char * pkiname)11276a0ecd1Seric ssl_load_key(const char *name, off_t *len, char *pass, mode_t perm, const char *pkiname)
11365c4fdfbSgilles {
1141c3ac238Seric 	FILE		*fp = NULL;
11565c4fdfbSgilles 	EVP_PKEY	*key = NULL;
11665c4fdfbSgilles 	BIO		*bio = NULL;
11765c4fdfbSgilles 	long		 size;
118cdaf00a5Sotto 	char		*data, *buf, *filebuf;
11976a0ecd1Seric 	struct stat	 st;
12076a0ecd1Seric 	char		 mode[12];
12176a0ecd1Seric 	char		 prompt[2048];
12265c4fdfbSgilles 
12365c4fdfbSgilles 	/*
12465c4fdfbSgilles 	 * Read (possibly) encrypted key from file
12565c4fdfbSgilles 	 */
12665c4fdfbSgilles 	if ((fp = fopen(name, "r")) == NULL)
12765c4fdfbSgilles 		return (NULL);
128cdaf00a5Sotto 	if ((filebuf = malloc_conceal(BUFSIZ)) == NULL)
129cdaf00a5Sotto 		goto fail;
130cdaf00a5Sotto 	setvbuf(fp, filebuf, _IOFBF, BUFSIZ);
13165c4fdfbSgilles 
13276a0ecd1Seric 	if (fstat(fileno(fp), &st) != 0)
13376a0ecd1Seric 		goto fail;
13476a0ecd1Seric 	if (st.st_uid != 0) {
13576a0ecd1Seric 		log_warnx("warn:  %s: not owned by uid 0", name);
13676a0ecd1Seric 		errno = EACCES;
13776a0ecd1Seric 		goto fail;
13876a0ecd1Seric 	}
13976a0ecd1Seric 	if (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO) & ~perm) {
14076a0ecd1Seric 		strmode(perm, mode);
14176a0ecd1Seric 		log_warnx("warn:  %s: insecure permissions: must be at most %s",
14276a0ecd1Seric 		    name, &mode[1]);
14376a0ecd1Seric 		errno = EACCES;
14476a0ecd1Seric 		goto fail;
14576a0ecd1Seric 	}
14676a0ecd1Seric 
14776a0ecd1Seric 	(void)snprintf(prompt, sizeof prompt, "passphrase for %s: ", pkiname);
148d5cc82f9Sreyk 	key = PEM_read_PrivateKey(fp, NULL, ssl_password_cb, prompt);
14965c4fdfbSgilles 	fclose(fp);
1501c3ac238Seric 	fp = NULL;
151cdaf00a5Sotto 	freezero(filebuf, BUFSIZ);
152cdaf00a5Sotto 	filebuf = NULL;
15365c4fdfbSgilles 	if (key == NULL)
15465c4fdfbSgilles 		goto fail;
15565c4fdfbSgilles 	/*
15665c4fdfbSgilles 	 * Write unencrypted key to memory buffer
15765c4fdfbSgilles 	 */
15865c4fdfbSgilles 	if ((bio = BIO_new(BIO_s_mem())) == NULL)
15965c4fdfbSgilles 		goto fail;
16065c4fdfbSgilles 	if (!PEM_write_bio_PrivateKey(bio, key, NULL, NULL, 0, NULL, NULL))
16165c4fdfbSgilles 		goto fail;
16265c4fdfbSgilles 	if ((size = BIO_get_mem_data(bio, &data)) <= 0)
16365c4fdfbSgilles 		goto fail;
164cdaf00a5Sotto 	if ((buf = calloc_conceal(1, size + 1)) == NULL)
16565c4fdfbSgilles 		goto fail;
16665c4fdfbSgilles 	memcpy(buf, data, size);
16765c4fdfbSgilles 
16865c4fdfbSgilles 	BIO_free_all(bio);
1695a039c14Sreyk 	EVP_PKEY_free(key);
1705a039c14Sreyk 
17176a0ecd1Seric 	*len = (off_t)size + 1;
17265c4fdfbSgilles 	return (buf);
17365c4fdfbSgilles 
17465c4fdfbSgilles fail:
17565c4fdfbSgilles 	ssl_error("ssl_load_key");
17665c4fdfbSgilles 	BIO_free_all(bio);
1775a039c14Sreyk 	EVP_PKEY_free(key);
17867aa17dcSeric 	if (fp)
1791c3ac238Seric 		fclose(fp);
180cdaf00a5Sotto 	freezero(filebuf, BUFSIZ);
18165c4fdfbSgilles 	return (NULL);
18265c4fdfbSgilles }
18365c4fdfbSgilles 
1843ef9cbf7Sgilles int
ssl_load_certificate(struct pki * p,const char * pathname)1851c3ac238Seric ssl_load_certificate(struct pki *p, const char *pathname)
1863ef9cbf7Sgilles {
1871c3ac238Seric 	p->pki_cert = ssl_load_file(pathname, &p->pki_cert_len, 0755);
1881c3ac238Seric 	if (p->pki_cert == NULL)
189cc81b7c6Seric 		return 0;
190cc81b7c6Seric 	return 1;
19155dc621fSgilles }
19255dc621fSgilles 
193cc81b7c6Seric int
ssl_load_keyfile(struct pki * p,const char * pathname,const char * pkiname)1941c3ac238Seric ssl_load_keyfile(struct pki *p, const char *pathname, const char *pkiname)
195cc81b7c6Seric {
19676a0ecd1Seric 	char	pass[1024];
19776a0ecd1Seric 
1985f122c69Sgilles 	p->pki_key = ssl_load_key(pathname, &p->pki_key_len, pass, 0740, pkiname);
1991c3ac238Seric 	if (p->pki_key == NULL)
200cc81b7c6Seric 		return 0;
201cc81b7c6Seric 	return 1;
202cc81b7c6Seric }
203cc81b7c6Seric 
204cc81b7c6Seric int
ssl_load_cafile(struct ca * c,const char * pathname)205f7aa1c30Sgilles ssl_load_cafile(struct ca *c, const char *pathname)
206cc81b7c6Seric {
207f7aa1c30Sgilles 	c->ca_cert = ssl_load_file(pathname, &c->ca_cert_len, 0755);
208f7aa1c30Sgilles 	if (c->ca_cert == NULL)
209cc81b7c6Seric 		return 0;
210cc81b7c6Seric 	return 1;
211cc81b7c6Seric }
212cc81b7c6Seric 
2133ef9cbf7Sgilles void
ssl_error(const char * where)2143ef9cbf7Sgilles ssl_error(const char *where)
2153ef9cbf7Sgilles {
2163ef9cbf7Sgilles 	unsigned long	code;
2173ef9cbf7Sgilles 	char		errbuf[128];
2183ef9cbf7Sgilles 
2193ef9cbf7Sgilles 	for (; (code = ERR_get_error()) != 0 ;) {
2203ef9cbf7Sgilles 		ERR_error_string_n(code, errbuf, sizeof(errbuf));
22182614934Seric 		log_debug("debug: SSL library error: %s: %s", where, errbuf);
2223ef9cbf7Sgilles 	}
2233ef9cbf7Sgilles }
22441b8cf0bSmillert 
22541b8cf0bSmillert static void
hash_x509(X509 * cert,char * hash,size_t hashlen)22641b8cf0bSmillert hash_x509(X509 *cert, char *hash, size_t hashlen)
22741b8cf0bSmillert {
22841b8cf0bSmillert 	static const char	hex[] = "0123456789abcdef";
22941b8cf0bSmillert 	size_t			off;
23041b8cf0bSmillert 	char			digest[EVP_MAX_MD_SIZE];
23141b8cf0bSmillert 	int		 	dlen, i;
23241b8cf0bSmillert 
23341b8cf0bSmillert 	if (X509_pubkey_digest(cert, EVP_sha256(), digest, &dlen) != 1)
23441b8cf0bSmillert 		fatalx("%s: X509_pubkey_digest failed", __func__);
23541b8cf0bSmillert 
23641b8cf0bSmillert 	if (hashlen < 2 * dlen + sizeof("SHA256:"))
2377078bf6aSop 		fatalx("%s: hash buffer too small", __func__);
23841b8cf0bSmillert 
23941b8cf0bSmillert 	off = strlcpy(hash, "SHA256:", hashlen);
24041b8cf0bSmillert 
24141b8cf0bSmillert 	for (i = 0; i < dlen; i++) {
24241b8cf0bSmillert 		hash[off++] = hex[(digest[i] >> 4) & 0x0f];
24341b8cf0bSmillert 		hash[off++] = hex[digest[i] & 0x0f];
24441b8cf0bSmillert 	}
24541b8cf0bSmillert 	hash[off] = 0;
24641b8cf0bSmillert }
24741b8cf0bSmillert 
24841b8cf0bSmillert char *
ssl_pubkey_hash(const char * buf,off_t len)24941b8cf0bSmillert ssl_pubkey_hash(const char *buf, off_t len)
25041b8cf0bSmillert {
25141b8cf0bSmillert #define TLS_CERT_HASH_SIZE	128
25241b8cf0bSmillert 	BIO		*in;
25341b8cf0bSmillert 	X509		*x509 = NULL;
25441b8cf0bSmillert 	char		*hash = NULL;
25541b8cf0bSmillert 
25641b8cf0bSmillert 	if ((in = BIO_new_mem_buf(buf, len)) == NULL) {
25741b8cf0bSmillert 		log_warnx("%s: BIO_new_mem_buf failed", __func__);
25841b8cf0bSmillert 		return NULL;
25941b8cf0bSmillert 	}
26041b8cf0bSmillert 
26141b8cf0bSmillert 	if ((x509 = PEM_read_bio_X509(in, NULL, NULL, NULL)) == NULL) {
26241b8cf0bSmillert 		log_warnx("%s: PEM_read_bio_X509 failed", __func__);
26341b8cf0bSmillert 		goto fail;
26441b8cf0bSmillert 	}
26541b8cf0bSmillert 
26641b8cf0bSmillert 	if ((hash = malloc(TLS_CERT_HASH_SIZE)) == NULL) {
26741b8cf0bSmillert 		log_warn("%s: malloc", __func__);
26841b8cf0bSmillert 		goto fail;
26941b8cf0bSmillert 	}
27041b8cf0bSmillert 	hash_x509(x509, hash, TLS_CERT_HASH_SIZE);
27141b8cf0bSmillert 
27241b8cf0bSmillert fail:
27341b8cf0bSmillert 	BIO_free(in);
27441b8cf0bSmillert 
27541b8cf0bSmillert 	if (x509)
27641b8cf0bSmillert 		X509_free(x509);
27741b8cf0bSmillert 
27841b8cf0bSmillert 	return hash;
27941b8cf0bSmillert }
280