269 lines
5.8 KiB
C
269 lines
5.8 KiB
C
/* Copyright (C) 2012,2016 Phil Pennock.
|
|
* Copyright (c) The Exim Maintainers 2021
|
|
* This is distributed as part of Exim and licensed under the GPL.
|
|
* See the file "NOTICE" for more details.
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
/* Build with:
|
|
* c99 $(pkg-config --cflags openssl) gen_pkcs3.c $(pkg-config --libs openssl)
|
|
*/
|
|
|
|
/*
|
|
* Rationale:
|
|
* The Diffie-Hellman primes which are embedded into Exim as named primes for
|
|
* the tls_dhparam option are in the std-crypto.c file. The source for those
|
|
* comes from various RFCs, where they are given in hexadecimal form.
|
|
*
|
|
* This tool provides convenient conversion, to reduce the risk of human
|
|
* error in transcription.
|
|
*/
|
|
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
#include <openssl/bio.h>
|
|
#include <openssl/bn.h>
|
|
#include <openssl/dh.h>
|
|
#include <openssl/err.h>
|
|
#include <openssl/pem.h>
|
|
|
|
extern const char *__progname;
|
|
|
|
|
|
void __attribute__((__noreturn__)) __attribute__((__format__(printf, 1, 2)))
|
|
die(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fflush(NULL);
|
|
fprintf(stderr, "%s: ", __progname);
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
fprintf(stderr, "\n");
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
|
|
void __attribute__((__noreturn__))
|
|
die_openssl_err(const char *msg)
|
|
{
|
|
char err_string[250];
|
|
|
|
ERR_error_string_n(ERR_get_error(), err_string, sizeof(err_string));
|
|
die("%s: %s", msg, err_string);
|
|
}
|
|
|
|
|
|
static BIGNUM *
|
|
bn_from_text(const char *text)
|
|
{
|
|
BIGNUM *b;
|
|
char *p, *spaceless;
|
|
const char *q, *end;
|
|
size_t len;
|
|
int rc;
|
|
|
|
len = strlen(text);
|
|
spaceless = malloc(len + 1);
|
|
if (!spaceless)
|
|
die("malloc(%zu) failed: %s", len + 1, strerror(errno));
|
|
|
|
for (p = spaceless, q = text, end = text + len;
|
|
q < end;
|
|
++q) {
|
|
if (!isspace(*q))
|
|
*p++ = *q;
|
|
}
|
|
len = p - spaceless;
|
|
*p++ = '\0';
|
|
|
|
b = NULL;
|
|
rc = BN_hex2bn(&b, spaceless);
|
|
|
|
if (rc != (int)len)
|
|
die("BN_hex2bn did not convert entire input; took %d of %zu bytes",
|
|
rc, len);
|
|
|
|
return b;
|
|
}
|
|
|
|
|
|
static void
|
|
our_dh_check(DH *dh)
|
|
{
|
|
int rc, errflags = 0;
|
|
|
|
rc = DH_check(dh, &errflags);
|
|
if (!rc) die_openssl_err("DH_check() could not be performed");;
|
|
|
|
/* We ignore DH_UNABLE_TO_CHECK_GENERATOR because some of the invocations
|
|
* deliberately provide generators other than 2 or 5. */
|
|
|
|
if (errflags & DH_CHECK_P_NOT_SAFE_PRIME)
|
|
die("DH_check(): p not a safe prime");
|
|
if (errflags & DH_NOT_SUITABLE_GENERATOR)
|
|
die("DH_check(): g not suitable as generator");
|
|
}
|
|
|
|
|
|
static void
|
|
emit_c_format_dh(FILE *stream, DH *dh)
|
|
{
|
|
BIO *bio;
|
|
long length;
|
|
char *data, *end, *p, *nl;
|
|
|
|
bio = BIO_new(BIO_s_mem());
|
|
PEM_write_bio_DHparams(bio, dh);
|
|
length = BIO_get_mem_data(bio, &data);
|
|
if (!length)
|
|
die("no data in memory BIO to format for printing");
|
|
if (length < 0)
|
|
die("grr, negative length memory not supported");
|
|
end = data + length;
|
|
|
|
for (p = data; p < end; /**/) {
|
|
nl = strchr(p, '\n');
|
|
if (!nl) {
|
|
fprintf(stream, "\"%s\\n\"\n/* missing final newline */\n", p);
|
|
break;
|
|
}
|
|
*nl = '\0';
|
|
fprintf(stream, "\"%s\\n\"%s\n", p, (nl == end - 1 ? ";" : ""));
|
|
p = nl + 1;
|
|
}
|
|
}
|
|
|
|
|
|
void __attribute__((__noreturn__))
|
|
usage(FILE *stream, int exitcode)
|
|
{
|
|
fprintf(stream, "Usage: %s [-CPcst] <dh_p> <dh_g> [<dh_q>]\n"
|
|
"Both dh_p and dh_g should be hex strings representing the numbers\n"
|
|
"The same applies to the optional dh_q (prime-order subgroup).\n"
|
|
"They may contain whitespace.\n"
|
|
"Older values, dh_g is often just '2', not a long string.\n"
|
|
"\n"
|
|
" -C show C string form of PEM result\n"
|
|
" -P do not show PEM\n"
|
|
" -c run OpenSSL DH_check() on the DH object\n"
|
|
" -s show the parsed p and g\n"
|
|
" -t show text form of certificate\n"
|
|
|
|
, __progname);
|
|
exit(exitcode);
|
|
}
|
|
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
BIGNUM *p, *g, *q;
|
|
DH *dh;
|
|
int ch;
|
|
bool perform_dh_check = false;
|
|
bool show_c_form = false;
|
|
bool show_numbers = false;
|
|
bool show_pem = true;
|
|
bool show_text = false;
|
|
bool given_q = false;
|
|
|
|
while ((ch = getopt(argc, argv, "CPcsth")) != -1) {
|
|
switch (ch) {
|
|
case 'C':
|
|
show_c_form = true;
|
|
break;
|
|
case 'P':
|
|
show_pem = false;
|
|
break;
|
|
case 'c':
|
|
perform_dh_check = true;
|
|
break;
|
|
case 's':
|
|
show_numbers = true;
|
|
break;
|
|
case 't':
|
|
show_text = true;
|
|
break;
|
|
|
|
case 'h':
|
|
usage(stdout, 0);
|
|
case '?':
|
|
die("Unknown option or missing argument -%c", optopt);
|
|
default:
|
|
die("Unhandled option -%c", ch);
|
|
}
|
|
}
|
|
|
|
optind -= 1;
|
|
argc -= optind;
|
|
argv += optind;
|
|
|
|
if ((argc < 3) || (argc > 4)) {
|
|
fprintf(stderr, "argc: %d\n", argc);
|
|
usage(stderr, 1);
|
|
}
|
|
|
|
// If we use DH_set0_pqg instead of setting dh fields directly; the q value
|
|
// is optional and may be NULL.
|
|
// Just blank them all.
|
|
p = g = q = NULL;
|
|
|
|
p = bn_from_text(argv[1]);
|
|
g = bn_from_text(argv[2]);
|
|
if (argc >= 4) {
|
|
q = bn_from_text(argv[3]);
|
|
given_q = true;
|
|
}
|
|
|
|
if (show_numbers) {
|
|
printf("p = ");
|
|
BN_print_fp(stdout, p);
|
|
printf("\ng = ");
|
|
BN_print_fp(stdout, g);
|
|
if (given_q) {
|
|
printf("\nq = ");
|
|
BN_print_fp(stdout, q);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
dh = DH_new();
|
|
// The documented method for setting q appeared in OpenSSL 1.1.0.
|
|
#if OPENSSL_VERSION_NUMBER >= 0x1010000f
|
|
// NULL okay for q; yes, the optional value is in the middle.
|
|
if (DH_set0_pqg(dh, p, q, g) != 1) {
|
|
die_openssl_err("initialising DH pqg values failed");
|
|
}
|
|
#else
|
|
dh->p = p;
|
|
dh->g = g;
|
|
if (given_q) {
|
|
dh->q = q;
|
|
}
|
|
#endif
|
|
|
|
if (perform_dh_check)
|
|
our_dh_check(dh);
|
|
|
|
if (show_text)
|
|
DHparams_print_fp(stdout, dh);
|
|
|
|
if (show_pem) {
|
|
if (show_c_form)
|
|
emit_c_format_dh(stdout, dh);
|
|
else
|
|
PEM_write_DHparams(stdout, dh);
|
|
}
|
|
|
|
DH_free(dh); /* should free p,g (& q if non-NULL) too */
|
|
return 0;
|
|
}
|