NetBSD/dist/ntp/ntpd/ntp_crypto.c

1162 lines
30 KiB
C

/* $NetBSD: ntp_crypto.c,v 1.2 2003/05/17 01:14:33 itojun Exp $ */
/*
* ntp_crypto.c - NTP version 4 public key routines
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#ifdef AUTOKEY
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include "ntpd.h"
#include "ntp_stdlib.h"
#include "ntp_string.h"
#include "ntp_crypto.h"
/*
* Extension field message formats
*
* +-------+-------+ +-------+-------+ +-------+-------+
* 0 | 3 | len | | 4 | len | | 5 | len |
* +-------+-------+ +-------+-------+ +-------+-------+
* 1 | assoc ID | | assoc ID | | assoc ID |
* +---------------+ +---------------+ +---------------+
* 2 | timestamp | | timestamp | | timestamp |
* +---------------+ +---------------+ +---------------+
* 3 | final seq | | cookie | | value len |
* +---------------+ +---------------+ +---------------+
* 4 | final key | | signature len | | |
* +---------------+ +---------------+ = value =
* 5 | signature len | | | | |
* +---------------+ = signature = +---------------+
* 6 | | | | | signature len |
* = signature = +---------------+ +---------------+
* 7 | | CRYPTO_PRIV rsp | |
* +---------------+ = signature =
* CRYPTO_AUTO rsp | |
* +---------------+
* CRYPTO_DH rsp
* CRYPTO_NAME rsp
*
* CRYPTO_PUBL 1 request/respond for public key
* CRYPTO_ASSOC 2 request/respond association ID
* CRYPTO_AUTO 3 request/respond autokey values
* CRYPTO_PRIV 4 request/respond cookie
* CRYPTO_DH 5 request public value/respond signature
* CRYPTO_NAME 6 request/respond host name
*
* Note: requests carry the association ID of the receiver; responses
* carry the association ID of the sender.
*/
#ifdef PUBKEY
/*
* Cryptodefines
*/
#define MAX_KEYLEN 1024 /* maximum key length */
/*
* Cryptodata
*/
static R_DH_PARAMS dh_params; /* Diffie-Hellman parameters */
static u_int dh_keyLen; /* Diffie-Hellman key length */
static u_char *dh_public; /* Diffie-Hellman public value */
static u_char *dh_private; /* Diffie-Hellman private value */
static R_RSA_PRIVATE_KEY private_key; /* RSA private key */
static R_RSA_PUBLIC_KEY public_key; /* RSA public key */
static u_char *dh_sign = NULL; /* Diffie-Hellman public signature */
static struct value host; /* host name, timestamp and signature */
int crypto_enable; /* master switch */
int crypto_flags; /* flags that wave cryptically */
char *private_key_file = NULL; /* private key file */
char *public_key_file = NULL; /* public key file */
char *dh_params_file = NULL; /* D-H parameters file */
char *keysdir = "/usr/local/etc/"; /* crypto keys directory */
/*
* Cryptotypes
*/
static int crypto_read P((u_char *, u_char *, u_int));
static void crypto_line P((FILE *, u_char **, u_int *));
#endif /* PUBKEY */
/*
* session_key - generate session key
*
* This routine generates a session key from the source address,
* destination address, key ID and private value. The value of the
* session key is the MD5 hash of these values, while the next key ID is
* the first four octets of the hash.
*/
keyid_t /* returns next key ID */
session_key(
struct sockaddr_in *srcadr, /* source address */
struct sockaddr_in *dstadr, /* destination address */
keyid_t keyno, /* key ID */
keyid_t private, /* private value */
u_long lifetime /* key lifetime */
)
{
MD5_CTX ctx; /* MD5 context */
keyid_t keyid; /* key identifer */
u_int32 header[4]; /* data in network byte order */
u_char digest[16]; /* message digest */
/*
* Generate the session key and key ID. If the lifetime is
* greater than zero, install the key and call it trusted.
*/
header[0] = srcadr->sin_addr.s_addr;
header[1] = dstadr->sin_addr.s_addr;
header[2] = htonl(keyno);
header[3] = htonl(private);
MD5Init(&ctx);
MD5Update(&ctx, (u_char *)header, sizeof(header));
MD5Final(digest, &ctx);
memcpy(&keyid, digest, 4);
keyid = ntohl(keyid);
if (lifetime != 0) {
MD5auth_setkey(keyno, digest, 16);
authtrust(keyno, lifetime);
}
#ifdef DEBUG
if (debug > 1)
printf(
"session_key: %s > %s %08x %08x hash %08x life %lu\n",
numtoa(header[0]), numtoa(header[1]), keyno,
private, keyid, lifetime);
#endif
return (keyid);
}
/*
* make_keylist - generate key list
*
* This routine constructs a pseudo-random sequence by repeatedly
* hashing the session key starting from a given source address,
* destination address, private value and the next key ID of the
* preceeding session key. The last entry on the list is saved along
* with its sequence number and public signature.
*/
void
make_keylist(
struct peer *peer /* peer structure pointer */
)
{
struct autokey *ap; /* autokey pointer */
keyid_t keyid; /* next key ID */
keyid_t cookie; /* private value */
l_fp tstamp; /* NTP timestamp */
u_long ltemp;
int i;
#ifdef PUBKEY
R_SIGNATURE_CTX ctx; /* signature context */
int rval; /* return value */
u_int len;
#endif /* PUBKEY */
/*
* Allocate the key list if necessary.
*/
L_CLR(&tstamp);
if (sys_leap != LEAP_NOTINSYNC)
get_systime(&tstamp);
if (peer->keylist == NULL)
peer->keylist = (keyid_t *)emalloc(sizeof(keyid_t) *
NTP_MAXSESSION);
/*
* Generate an initial key ID which is unique and greater than
* NTP_MAXKEY.
*/
while (1) {
keyid = (u_long)RANDOM & 0xffffffff;
if (keyid <= NTP_MAXKEY)
continue;
if (authhavekey(keyid))
continue;
break;
}
/*
* Generate up to NTP_MAXSESSION session keys. Stop if the
* next one would not be unique or not a session key ID or if
* it would expire before the next poll. The private value
* included in the hash is zero if broadcast mode, the peer
* cookie if client mode or the host cookie if symmetric modes.
*/
ltemp = sys_automax;
peer->hcookie = session_key(&peer->dstadr->sin, &peer->srcadr,
0, sys_private, 0);
if (peer->hmode == MODE_BROADCAST) {
cookie = 0;
} else {
#ifdef PUBKEY
cookie = peer->pcookie.key;
#else
if (peer->hmode == MODE_CLIENT)
cookie = peer->pcookie.key;
else
cookie = peer->hcookie ^ peer->pcookie.key;
#endif /* PUBKEY */
}
for (i = 0; i < NTP_MAXSESSION; i++) {
peer->keylist[i] = keyid;
peer->keynumber = i;
keyid = session_key(&peer->dstadr->sin, (peer->hmode ==
MODE_BROADCAST) ? &peer->dstadr->bcast :
&peer->srcadr, keyid, cookie, ltemp);
ltemp -= 1 << peer->hpoll;
if (auth_havekey(keyid) || keyid <= NTP_MAXKEY ||
ltemp <= (1 << (peer->hpoll + 1)))
break;
}
/*
* Save the last session key ID, sequence number and timestamp,
* then sign these values for later retrieval by the clients. Be
* careful not to use invalid key media.
*/
ap = &peer->sndauto;
ap->tstamp = htonl(tstamp.l_ui);
ap->seq = htonl(peer->keynumber);
ap->key = htonl(keyid);
ap->siglen = 0;
#if DEBUG
if (debug)
printf("make_keys: %d %08x %08x ts %u\n", ntohl(ap->seq),
ntohl(ap->key), cookie, ntohl(ap->tstamp));
#endif
#ifdef PUBKEY
if(!crypto_enable)
return;
if (private_key.bits < MIN_RSA_MODULUS_BITS ||
private_key.bits > MAX_RSA_MODULUS_BITS) {
rval = -1;
} else {
if (ap->sig == NULL)
ap->sig = emalloc(MAX_SIGNATURE_LEN);
R_SignInit(&ctx, DA_MD5);
R_SignUpdate(&ctx, (u_char *)ap, 12);
rval = R_SignFinal(&ctx, ap->sig, &len, &private_key);
ap->siglen = htonl(len);
}
if (rval != 0)
msyslog(LOG_ERR, "make_keylist: signature fails %x",
rval);
#endif /* PUBKEY */
}
/*
* crypto_recv - parse extension fields
*
* This routine is called when the packet has been matched to an
* association and passed sanity, format and authentication checks. We
* believe the extension field values only if the public key is valid
* and the signature has valid length and is verified.
*/
void
crypto_recv(
struct peer *peer, /* peer structure pointer */
struct recvbuf *rbufp /* packet buffer pointer */
)
{
u_int32 *pkt; /* packet pointer */
struct autokey *ap; /* autokey pointer */
struct cookie *cp; /* cookie pointer */
int has_mac; /* length of MAC field */
int authlen; /* offset of MAC field */
int len; /* extension field length */
u_int code; /* extension field opcode */
u_int tstamp; /* extension field timestamp */
int i;
u_int temp;
#ifdef PUBKEY
R_SIGNATURE_CTX ctx; /* signature context */
u_char dh_key[MAX_DH_LEN]; /* Diffie-Hellman agreed key */
u_int modulus;
int rval;
int j;
#endif /* PUBKEY */
/*
* Initialize. Note that the packet has already been checked for
* valid format and extension field lengths.
*/
pkt = (u_int32 *)&rbufp->recv_pkt;
authlen = LEN_PKT_NOMAC;
while ((has_mac = rbufp->recv_length - authlen) > MAX_MAC_LEN) {
i = authlen / 4;
len = ntohl(pkt[i]) & 0xffff;
code = (ntohl(pkt[i]) >> 16) & 0xffff;
tstamp = ntohl(pkt[i + 2]);
if (code & CRYPTO_RESP)
peer->assoc = ntohl(pkt[i + 1]);
#ifdef DEBUG
if (debug)
printf(
"crypto_recv: ext offset %d len %d code %x assoc ID %d\n",
authlen, len, code, (u_int32)ntohl(pkt[i +
1]));
#endif
switch (code) {
/*
* Exchange association IDs. This is used in multicast
* server mode and is a NOP here.
*/
case CRYPTO_ASSOC | CRYPTO_RESP:
break;
/*
* Install autokey values in broadcast client and
* symmetric modes. We believe the values only if the
* public key is valid and the signature has valid
* length and is verified. However, we mark as authentic
* only if the timestamp is nonzero.
*/
case CRYPTO_AUTO | CRYPTO_RESP:
ap = (struct autokey *)&pkt[i + 2];
#ifdef PUBKEY
temp = public_key.bits / 8;
if (!crypto_enable) {
rval = 0;
} else if (tstamp < peer->recauto.tstamp) {
break;
} else if (peer->pubkey == NULL || temp !=
ntohl(ap->siglen)) {
rval = -1;
} else {
R_VerifyInit(&ctx, DA_MD5);
R_VerifyUpdate(&ctx, (u_char *)ap, 12);
rval = R_VerifyFinal(&ctx,
(u_char *)&ap->sig, temp,
(R_RSA_PUBLIC_KEY *)peer->pubkey);
}
#ifdef DEBUG
if (debug)
printf(
"crypto_recv: verify %x autokey %d %08x ts %u (%u)\n",
rval, ntohl(ap->seq),
ntohl(ap->key), tstamp,
peer->recauto.tstamp);
#endif
if (rval != 0) {
peer->flags &= ~FLAG_AUTOKEY;
break;
}
if (tstamp != 0)
peer->flags |= FLAG_AUTOKEY;
#endif /* PUBKEY */
peer->flash &= ~TEST10;
peer->recauto.tstamp = ntohl(ap->tstamp);
peer->recauto.seq = ntohl(ap->seq);
peer->recauto.key = ntohl(ap->key);
peer->pkeyid = peer->recauto.key;
break;
/*
* Install session cookie in client mode. We believe the
* value only if the public key is valid and the
* signature has valid length and is verified. However,
* we mark as authentic only if the value is nonzero.
*/
case CRYPTO_PRIV | CRYPTO_RESP:
cp = (struct cookie *)&pkt[i + 2];
#ifdef PUBKEY
temp = public_key.bits / 8;
if (!crypto_enable) {
rval = 0;
} else if (tstamp < peer->pcookie.tstamp) {
break;
} else if (peer->pubkey == NULL || temp !=
ntohl(cp->siglen)) {
rval = -1;
} else {
R_VerifyInit(&ctx, DA_MD5);
R_VerifyUpdate(&ctx, (u_char *)cp, 8);
rval = R_VerifyFinal(&ctx,
(u_char *)&cp->sig, temp,
(R_RSA_PUBLIC_KEY *)peer->pubkey);
}
temp = cp->key;
#ifdef DEBUG
if (debug)
printf(
"crypto_recv: verify %x cookie %08x ts %u\n",
rval, temp, tstamp);
#endif
if (rval != 0) {
peer->flags &= ~FLAG_AUTOKEY;
break;
}
if (tstamp != 0)
peer->flags |= FLAG_AUTOKEY;
#else
temp = cp->key;
#endif /* PUBKEY */
peer->flash &= ~TEST10;
if (temp != peer->pcookie.key) {
key_expire(peer);
peer->pcookie.tstamp = tstamp;
peer->pcookie.key = temp;
}
break;
#ifdef PUBKEY
/*
* Verify Diffie-Hellman public value and compute key
* agreement in symmetric modes. We believe the
* value only if the public key is valid and the
* signature has valid length and is verified.
*/
case CRYPTO_DH:
peer->cmmd = ntohl(pkt[i]);
/* fall through */
case CRYPTO_DH | CRYPTO_RESP:
temp = ntohl(pkt[i + 3]);
j = i + 4 + temp / 4;
if (tstamp < peer->pcookie.tstamp) {
break;
} else if (peer->pubkey == NULL ||
ntohl(pkt[j]) != public_key.bits / 8) {
rval = -1;
} else if (temp != dh_params.primeLen ||
dh_public == NULL) {
rval = -2;
} else {
R_VerifyInit(&ctx, DA_MD5);
R_VerifyUpdate(&ctx, (u_char *)&pkt[i +
2], temp + 8);
rval = R_VerifyFinal(&ctx,
(u_char *)&pkt[j + 1],
public_key.bits / 8,
(R_RSA_PUBLIC_KEY *)peer->pubkey);
}
/*
* Run the agreement algorithm and stash the key
* value. We use only the first u_int32 for the
* host cookie. Wasteful.
*/
if (rval != 0) {
temp = 0;
} else {
rval = R_ComputeDHAgreedKey(dh_key,
(u_char *)&pkt[i + 4], dh_private,
dh_keyLen, &dh_params);
temp = ntohl(*(u_int32 *)dh_key);
}
#ifdef DEBUG
if (debug)
printf(
"crypto_recv: verify %x d-h %08x ts %u\n",
rval, temp, tstamp);
#endif
if (rval != 0) {
peer->flags &= ~FLAG_AUTOKEY;
peer->cmmd = 0;
break;
}
peer->flash &= ~TEST10;
if (temp != peer->pcookie.key) {
key_expire(peer);
peer->pcookie.tstamp = tstamp;
peer->pcookie.key = temp;
}
break;
/*
* Receive remote host name and install public key from
* file.
*/
case CRYPTO_NAME | CRYPTO_RESP:
temp = ntohl(pkt[i + 3]);
j = i + 4 + temp / 4;
if (tstamp < peer->pcookie.tstamp) {
break;
} else if (ntohl(pkt[j]) != public_key.bits / 8)
{
rval = -1;
} else if (crypto_public(peer, (char *)&pkt[i +
4])) {
R_VerifyInit(&ctx, DA_MD5);
R_VerifyUpdate(&ctx, (char *)&pkt[i +
2], 8 + temp);
rval = R_VerifyFinal(&ctx,
(u_char *)&pkt[j + 1],
public_key.bits / 8,
(R_RSA_PUBLIC_KEY *)peer->pubkey);
if (rval != 0) {
free(peer->pubkey);
peer->pubkey = NULL;
}
} else {
rval = -2;
}
#ifdef DEBUG
if (debug)
printf(
"crypto_recv: verify %x host %s ts %u\n",
rval, (char *)&pkt[i + 4], tstamp);
#endif
break;
/*
* Install peer public key. This is rather raucus, since
* the extension field is in network order and the first
* word is a u_int32; however, the corresponding word of
* the key is a u_int, which can be 32 or 64 bits
* depending on architecture. We don't do anything
* unless the length and modulus are valid. Picky,
* picky.
*/
case CRYPTO_PUBL | CRYPTO_RESP:
temp = sizeof(R_RSA_PUBLIC_KEY) - sizeof(u_int);
if (ntohl(pkt[i + 2]) != temp + 4)
break;
modulus = ntohl(pkt[i + 3]);
if ( modulus < MIN_RSA_MODULUS_BITS ||
modulus > MAX_RSA_MODULUS_BITS)
break;
if (peer->pubkey == NULL)
peer->pubkey =
emalloc(sizeof(R_RSA_PUBLIC_KEY));
((R_RSA_PUBLIC_KEY *)peer->pubkey)->bits =
modulus;
memcpy(
((R_RSA_PUBLIC_KEY *)peer->pubkey)->modulus,
(u_char *)&(pkt[i + 4]), temp);
break;
#endif /* PUBKEY */
/*
* For other requests, save the request code for later;
* for unknown responses or errors, just ignore for now.
*/
default:
if (code & (CRYPTO_RESP | CRYPTO_ERROR))
break;
peer->cmmd = ntohl(pkt[i]);
break;
}
authlen += len;
}
return;
}
/*
* crypto_xmit - construct extension fields
*
* This routine is called both when an association is configured and
* when one is not. The only case where this matters now is to retrieve
* the autokey information, in which case the caller has to provide the
* association ID to match the association.
*/
int /* return length of extension field */
crypto_xmit(
u_int32 *xpkt, /* packet pointer */
int start, /* offset to extension field */
u_int code, /* extension field code */
keyid_t cookie, /* session cookie */
int associd /* association ID */
)
{
struct peer *peer; /* peer structure pointer */
struct autokey *ap; /* autokey pointer */
struct cookie *cp; /* cookie pointer */
int len; /* extension field length */
u_int opcode; /* extension field opcode */
int i;
#ifdef PUBKEY
R_SIGNATURE_CTX ctx; /* signature context */
struct value *vp; /* value pointer */
int rval; /* return value */
u_int temp;
int j;
#endif /* PUBKEY */
/*
* Generate the requested extension field request code, length
* and association ID.
*/
i = start / 4;
opcode = code;
xpkt[i + 1] = htonl(associd);
len = 8;
switch (opcode) {
/*
* Exchange association IDs. This is used in multicast server
* mode and is a NOP here.
*/
case CRYPTO_ASSOC | CRYPTO_RESP:
break;
/*
* Find peer and send autokey data and signature in broadcast
* server and symmetric modes.
*/
case CRYPTO_AUTO | CRYPTO_RESP:
peer = findpeerbyassoc(associd);
if (peer == NULL) {
opcode |= CRYPTO_ERROR;
break;
}
ap = (struct autokey *)&xpkt[i + 2];
ap->tstamp = peer->sndauto.tstamp;
ap->seq = peer->sndauto.seq;
ap->key = peer->sndauto.key;
ap->siglen = 0;
len += 16;
#ifdef PUBKEY
if (!crypto_enable)
break;
ap->siglen = peer->sndauto.siglen;
temp = ntohl(peer->sndauto.siglen);
if (temp > 0)
memcpy(&ap->sig, peer->sndauto.sig, temp);
len += temp;
#endif /* PUBKEY */
break;
/*
* Send peer cookie and signature in server mode.
*/
case CRYPTO_PRIV | CRYPTO_RESP:
cp = (struct cookie *)&xpkt[i + 2];
cp->tstamp = htonl(sys_revoketime.l_ui);
cp->key = htonl(cookie);
cp->siglen = 0;
len += 12;
#ifdef PUBKEY
if (!crypto_enable)
break;
cp->siglen = htonl(public_key.bits / 8);
if (private_key.bits < MIN_RSA_MODULUS_BITS ||
private_key.bits > MAX_RSA_MODULUS_BITS) {
rval = -1;
} else {
R_SignInit(&ctx, DA_MD5);
R_SignUpdate(&ctx, (u_char *)cp, 8);
rval = R_SignFinal(&ctx, (u_char *)&cp->sig,
&temp, &private_key);
}
if (rval != 0) {
cp->siglen = 0;
msyslog(LOG_ERR,
"crypto_xmit: cookie signature fails %x",
rval);
break;
}
len += temp;
#endif /* PUBKEY */
break;
#ifdef PUBKEY
/*
* Send Diffie-Hellman public value, timestamp and signature.
*/
case CRYPTO_DH:
case CRYPTO_DH | CRYPTO_RESP:
vp = (struct value *)&xpkt[i + 2];
vp->tstamp = htonl(sys_revoketime.l_ui);
vp->vallen = 0;
len += 8;
temp = dh_params.primeLen;
if (dh_sign == NULL)
break;
vp->vallen = htonl(temp);
memcpy((u_char *)&vp->val, dh_public, temp);
len += temp;
j = i + 4 + temp / 4;
temp = public_key.bits / 8;
xpkt[j++] = htonl(temp);
memcpy((u_char *)&xpkt[j], dh_sign, temp);
len += temp + 4;
break;
/*
* Send host name, timestamp and signature.
*/
case CRYPTO_NAME | CRYPTO_RESP:
vp = (struct value *)&xpkt[i + 2];
vp->tstamp = htonl(sys_revoketime.l_ui);
vp->vallen = host.vallen;
len += 8;
temp = ntohl(host.vallen);
if (temp == 0)
break;
memcpy((u_char *)&vp->val, host.val, temp);
len += temp;
j = i + 4 + temp / 4;
temp = public_key.bits / 8;
xpkt[j++] = htonl(temp);
memcpy((u_char *)&xpkt[j], host.sig, temp);
len += temp + 4;
break;
/*
* Send public key. We send the public key only if it exists and
* is valid. This is used primarily for testing.
*/
case CRYPTO_PUBL | CRYPTO_RESP:
xpkt[i + 2] = 0;
len += 4;
if (public_key.bits < MIN_RSA_MODULUS_BITS ||
public_key.bits > MAX_RSA_MODULUS_BITS)
break;
temp = sizeof(R_RSA_PUBLIC_KEY) - sizeof(u_int);
xpkt[i + 2] = htonl(temp + 4);
xpkt[i + 3] = htonl(public_key.bits);
memcpy((u_char *)&xpkt[i + 4],
(u_char *)&public_key.modulus, temp);
len += temp + 4;
break;
#endif /* PUBKEY */
/*
* Default - Fall through for requests; for unknown responses,
* flag as error.
*/
default:
if (opcode & CRYPTO_RESP)
opcode |= CRYPTO_ERROR;
break;
}
/*
* Round up the field length to a multiple of 8 bytes and save
* the request code and length.
*/
len = ((len + 7) / 8) * 8;
if (len >= 4) {
xpkt[i] = htonl((u_int32)((opcode << 16) | len));
#ifdef DEBUG
if (debug)
printf(
"crypto_xmit: ext offset %d len %d code %x assoc ID %d\n",
start, len, code, associd);
#endif
}
return (len);
}
#ifdef PUBKEY
/*
* crypto_agree - compute public and private Diffie-Hellman values from
* given prime and generator, then sign with private key.
*/
void
crypto_agree(void)
{
u_int len, temp;
R_RANDOM_STRUCT randomstr;
R_SIGNATURE_CTX ctx; /* signature context */
int rval;
int i;
/*
* Sign host name and timestamp.
*/
host.tstamp = htonl(sys_revoketime.l_ui);
host.vallen = htonl(sys_hostnamelen);
host.val = sys_hostname;
host.siglen = dh_params.primeLen;
if (host.sig == NULL)
host.sig = emalloc(dh_params.primeLen);
R_SignInit(&ctx, DA_MD5);
R_SignUpdate(&ctx, (u_char *)&host, 8);
R_SignUpdate(&ctx, host.val, sys_hostnamelen);
rval = R_SignFinal(&ctx, host.sig, &temp, &private_key);
if (rval != 0) {
msyslog(LOG_ERR,
"crypto_agree: host signature fails %x", rval);
return;
}
/*
* Compute Diffie-Hellman public value. Note that the length of
* the private value is set arbitrarily to half the prime
* length.
*/
if (dh_params.primeLen == 0) {
msyslog(LOG_ERR,
"unavailable d-h parameters");
return;
}
R_RandomInit(&randomstr);
R_GetRandomBytesNeeded(&len, &randomstr);
for (i = 0; i < len; i++) {
temp = random();
R_RandomUpdate(&randomstr, (u_char *)&temp, 1);
}
if (dh_private == NULL)
dh_private = (u_char *)emalloc(dh_keyLen);
if (dh_public == NULL)
dh_public = (u_char *)emalloc(dh_params.primeLen);
rval = R_SetupDHAgreement(dh_public, dh_private, dh_keyLen,
&dh_params, &randomstr);
if (rval != 0) {
msyslog(LOG_ERR, "invalid d-h parameters");
return;
}
/*
* Sigfie-Hellman public value and save for later.
*/
if (dh_sign == NULL)
dh_sign = emalloc(dh_params.primeLen);
R_SignInit(&ctx, DA_MD5);
R_SignUpdate(&ctx, (char *)&host.tstamp, 4);
temp = htonl(dh_params.primeLen);
R_SignUpdate(&ctx, (char *)&temp, 4);
R_SignUpdate(&ctx, dh_public, dh_params.primeLen);
rval = R_SignFinal(&ctx, dh_sign, &temp, &private_key);
if (rval != 0) {
msyslog(LOG_ERR,
"crypto_agree: d-h signature fails %x", rval);
return;
}
#ifdef DEBUG
if (debug)
printf(
"cypto_agree: host %s d-h prime %d gen %d\n",
host.val, dh_params.primeLen,
dh_params.generatorLen);
#endif
}
/*
* crypto_read - read RSA key, decode and check for errors
*/
static int
crypto_read(
u_char *cp, /* file name */
u_char *key, /* key pointer */
u_int keylen /* key length */
)
{
FILE *str;
u_char buf[MAX_KEYLEN];
u_char encoded_key[MAX_KEYLEN];
char filename[MAXFILENAME];
u_int modulus;
u_int buflen;
char *rptr;
int rval;
/*
* Open the key file and discard comment lines.
*/
if (strlen(cp) == 0 || strlen(cp) >= MAXFILENAME - 1) {
msyslog(LOG_ERR, "invalid key file name length %d",
strlen(filename));
return (0);
} else if (*cp == '/') {
strcpy(filename, cp);
} else {
snprintf(filename, MAXFILENAME - 1, "%s%s", keysdir,
cp);
}
str = fopen(filename, "r");
if (str == NULL) {
msyslog(LOG_ERR, "key file %s not found", filename);
return (0);
}
while ((rptr = fgets(buf, MAX_KEYLEN - 1, str)) != NULL) {
buflen = strlen(buf);
if (buflen < 1)
continue;
if (*buf == '#' || *buf == '\r' || *buf == '\0')
continue;
break;
}
/*
* We are rather paranoid here, since an intruder can cause a
* coredump by infiltrating a naughty key. The line must contain
* a single integer followed by a PEM encoded, null-terminated
* string.
*/
if (rptr == NULL) {
msyslog(LOG_ERR, "invalid key file %s", filename);
return (0);
}
/* XXX sizeof(encoded_key) == 1024 */
if (sscanf(buf, "%d %1023s", &modulus, encoded_key) != 2) {
msyslog(LOG_ERR, "invalid key format %s", filename);
return (0);
}
/*
* Initialize the key with the decoded PEM string, but leave
* room for the modulus length in the key structure.
*/
rval = R_DecodePEMBlock(&key[sizeof(u_int)], &buflen,
encoded_key, strlen(encoded_key));
if (rval != 0) {
msyslog(LOG_ERR, "invalid key %s %x", filename, rval);
return (0);
}
/*
* Make sure the structure has the required length.
*/
buflen += sizeof(u_int);
if (buflen != keylen) {
msyslog(LOG_ERR, "invalid key length %s %d", filename,
buflen);
return (0);
}
/*
* Make sure the modulus length is within limits.
*/
if (modulus < MIN_RSA_MODULUS_BITS || modulus >
MAX_RSA_MODULUS_BITS) {
msyslog(LOG_ERR, "invalid key modulus %s %d", filename,
modulus);
return (0);
}
((u_int *)key)[0] = modulus;
#ifdef DEBUG
if (debug)
printf(
"crypto_read: RSA key file %s length %d modulus %d\n",
cp, keylen, modulus);
#endif
return (1);
}
/*
* crypto_public - read and install the public key from the public key
* file. The name of the file is in the form "ntpkey_host", where host
* is the DNS canonical name of the host. If the file is not found or
* has errors, we just keep going and expect the host to fetch the
* public key from the peer via the extension field.
*/
int
crypto_public(
struct peer *peer, /* peer structure pointer */
u_char *cp /* canonical host name */
)
{
R_RSA_PUBLIC_KEY keybuf;
u_int keylen = sizeof(R_RSA_PUBLIC_KEY);
char filename[MAXFILENAME];
snprintf(filename, MAXFILENAME - 1, "ntpkey_%s", cp);
if (!crypto_read(filename, (u_char *)&keybuf, keylen))
return (0);
if (peer->keystr != NULL)
free(peer->keystr);
peer->keystr = emalloc(strlen(filename) + 1);
strcpy(peer->keystr, filename);
if (peer->pubkey == NULL)
peer->pubkey = emalloc(keylen);
memcpy(peer->pubkey, (char *)&keybuf, keylen);
return (1);
}
/*
* crypto_line - read, decode and install Diffie-Hellman prime and
* generator. Be very parannoyed here if every little thing is not
* exactly right.
*/
static void
crypto_line(
FILE *str, /* file handle */
u_char **key, /* decoded string pointer */
u_int *len /* length */
)
{
u_char buf[MAX_KEYLEN];
u_char encoded_key[MAX_KEYLEN];
u_int temp, temp1, temp2;
char *rptr;
/*
* Read key and length. Bail out if the length word doesn't
* match the decoded string length and in other cases.
*/
*key = NULL;
*len = 0;
while ((rptr = fgets(buf, MAX_KEYLEN - 1, str)) != NULL) {
if (strlen(buf) < 1)
continue;
if (*buf == '#' || *buf == '\r' || *buf == '\0')
continue;
break;
}
/* XXX sizeof(encoded_key) == 1024 */
if (sscanf(buf, "%d %1023s", &temp, encoded_key) != 2)
return;
if (temp > MAX_DH_LEN)
return;
temp2 = DECODED_CONTENT_LEN(strlen(encoded_key));
if (R_DecodePEMBlock(buf, &temp1, encoded_key,
strlen(encoded_key)))
return;
if (temp != temp1 || temp > temp2)
return;
*key = (u_char *)emalloc(temp2);
memcpy(*key, buf, temp2);
*len = temp;
return;
}
/*
* crypto_setup - read RSA private key, RSA public key and Diffie-
* Hellman parameter files and initialize cryptographic data.
*/
void
crypto_setup(void)
{
FILE *str;
char hostname[MAXFILENAME];
char filename[MAXFILENAME];
/*
* Load RSA private key from file.
*/
if (private_key_file == NULL)
private_key_file = "ntpkey";
crypto_read(private_key_file, (u_char *)&private_key,
sizeof(R_RSA_PRIVATE_KEY));
/*
* Load RSA public key from file, default "ntpkey_host", where
* host is the DNS name of this machine.
*/
if (public_key_file == NULL) {
gethostname(hostname, MAXFILENAME);
snprintf(filename, MAXFILENAME - 1, "ntpkey_%s",
hostname);
public_key_file = emalloc(strlen(filename) + 1);
strcpy(public_key_file, filename);
}
crypto_read(public_key_file, (u_char *)&public_key,
sizeof(R_RSA_PUBLIC_KEY));
/*
* Load Diffie-Hellman key agreement parameters from file.
*/
if (dh_params_file == NULL)
dh_params_file = "ntpkey_dh";
if (*dh_params_file == '/')
strcpy(filename, dh_params_file);
else
snprintf(filename, MAXFILENAME - 1, "%s%s", keysdir,
dh_params_file);
str = fopen(filename, "r");
if (str == NULL) {
msyslog(LOG_ERR, "key file %s not found",
filename);
return;
}
/*
* Read prime and generator. Note that the private value length
* is set arbitrarily at half the prime length.
*/
crypto_line(str, &dh_params.prime, &dh_params.primeLen);
crypto_line(str, &dh_params.generator, &dh_params.generatorLen);
if (dh_params.primeLen == 0|| dh_params.generatorLen == 0) {
msyslog(LOG_ERR,
"invalid Diffie-Hellman file format or value");
return;
}
dh_keyLen = dh_params.primeLen / 2;
}
/*
* crypto_config - configure crypto data.
*/
void
crypto_config(
int item, /* configuration item */
char *cp /* file name */
)
{
switch (item) {
/*
* Initialize flags
*/
case CRYPTO_CONF_FLAGS:
crypto_flags = atoi(cp);
break;
/*
* Override the default Diffie-Hellman parameter file name.
*/
case CRYPTO_CONF_DH:
dh_params_file = emalloc(strlen(cp) + 1);
strcpy(dh_params_file, cp);
break;
/*
* Override the default RSA private key file name.
*/
case CRYPTO_CONF_PRIV:
private_key_file = emalloc(strlen(cp) + 1);
strcpy(private_key_file, cp);
break;
/*
* Override the default RSA public key file name.
*/
case CRYPTO_CONF_PUBL:
public_key_file = emalloc(strlen(cp) + 1);
strcpy(public_key_file, cp);
break;
/*
* Override the default crypto keys directory.
*/
case CRYPTO_CONF_KEYS:
keysdir = emalloc(strlen(cp) + 1);
strcpy(keysdir, cp);
break;
}
}
/*
* crypto_init () - initialize things.
*/
void
crypto_init(void)
{
/*
* Zeroize sensitive crypto data.
*/
memset((char *)&private_key, 0, sizeof(private_key));
memset((char *)&public_key, 0, sizeof(public_key));
memset((char *)&dh_params, 0, sizeof(dh_params));
memset((char *)&host, 0, sizeof(host));
}
#endif /* PUBKEY */
#endif /* AUTOKEY */