diff options
Diffstat (limited to 'contrib/ntp/ntpd/ntp_crypto.c')
-rw-r--r-- | contrib/ntp/ntpd/ntp_crypto.c | 4815 |
1 files changed, 3394 insertions, 1421 deletions
diff --git a/contrib/ntp/ntpd/ntp_crypto.c b/contrib/ntp/ntpd/ntp_crypto.c index 19f902a56a18..3e677032a164 100644 --- a/contrib/ntp/ntpd/ntp_crypto.c +++ b/contrib/ntp/ntpd/ntp_crypto.c @@ -5,136 +5,167 @@ #include <config.h> #endif -#ifdef AUTOKEY +#ifdef OPENSSL #include <stdio.h> #include <sys/types.h> +#include <sys/param.h> #include <unistd.h> #include <fcntl.h> #include "ntpd.h" #include "ntp_stdlib.h" +#include "ntp_unixtime.h" #include "ntp_string.h" -#include "ntp_crypto.h" + +#include "openssl/asn1_mac.h" +#include "openssl/bn.h" +#include "openssl/err.h" +#include "openssl/evp.h" +#include "openssl/pem.h" +#include "openssl/rand.h" +#include "openssl/x509v3.h" #ifdef KERNEL_PLL #include "ntp_syscall.h" #endif /* KERNEL_PLL */ /* - * Extension field message formats - * - * +-------+-------+ +-------+-------+ +-------+-------+ - * 0 | 3 | len | | 2,4 | len | | 5-9 | len | - * +-------+-------+ +-------+-------+ +-------+-------+ - * 1 | assocID | | assocID | | assocID | - * +---------------+ +---------------+ +---------------+ - * 2 | timestamp | | timestamp | | timestamp | - * +---------------+ +---------------+ +---------------+ - * 3 | final seq | | cookie/flags | | filestamp | - * +---------------+ +---------------+ +---------------+ - * 4 | final key | | signature len | | value len | - * +---------------+ +---------------+ +---------------+ - * 5 | signature len | | | | | - * +---------------+ = signature = = value = - * 6 | | | | | | - * = signature = +---------------+ +---------------+ - * 7 | | CRYPTO_ASSOC rsp | signature len | - * +---------------+ CRYPTO_PRIV rsp +---------------+ - * CRYPTO_AUTO rsp | | - * = signature = - * | | - * +---------------+ - * CRYPTO_DHPAR rsp - * CRYPTO_DH rsp - * CRYPTO_NAME rsp - * CRYPTO_CERT rsp - * CRYPTO_TAI rsp - * - * CRYPTO_STAT 1 - offer/select - * CRYPTO_ASSOC 2 20 association ID - * CRYPTO_AUTO 3 88 autokey values - * CRYPTO_PRIV 4 84 cookie value - * CRYPTO_DHPAR 5 220 agreement parameters - * CRYPTO_DH 6 152 public value - * CRYPTO_NAME 7 460 host name/public key - * CRYPTO_CERT 8 ? certificate - * CRYPTO_TAI 9 144 leapseconds table - * - * Note: requests carry the association ID of the receiver; responses - * carry the association ID of the sender. + * Extension field message format + * + * These are always signed and saved before sending in network byte + * order. They must be converted to and from host byte order for + * processing. + * + * +-------+-------+ + * | op | len | <- extension pointer + * +-------+-------+ + * | assocID | + * +---------------+ + * | timestamp | <- value pointer + * +---------------+ + * | filestamp | + * +---------------+ + * | value len | + * +---------------+ + * | | + * = value = + * | | + * +---------------+ + * | signature len | + * +---------------+ + * | | + * = signature = + * | | + * +---------------+ + * + * The CRYPTO_RESP bit is set to 0 for requests, 1 for responses. + * Requests carry the association ID of the receiver; responses carry + * the association ID of the sender. Some messages include only the + * operation/length and association ID words and so have length 8 + * octets. Ohers include the value structure and associated value and + * signature fields. These messages include the timestamp, filestamp, + * value and signature words and so have length at least 24 octets. The + * signature and/or value fields can be empty, in which case the + * respective length words are zero. An empty value with nonempty + * signature is syntactically valid, but semantically questionable. + * + * The filestamp represents the time when a cryptographic data file such + * as a public/private key pair is created. It follows every reference + * depending on that file and serves as a means to obsolete earlier data + * of the same type. The timestamp represents the time when the + * cryptographic data of the message were last signed. Creation of a + * cryptographic data file or signing a message can occur only when the + * creator or signor is synchronized to an authoritative source and + * proventicated to a trusted authority. + * + * Note there are four conditions required for server trust. First, the + * public key on the certificate must be verified, which involves a + * number of format, content and consistency checks. Next, the server + * identity must be confirmed by one of four schemes: private + * certificate, IFF scheme, GQ scheme or certificate trail hike to a + * self signed trusted certificate. Finally, the server signature must + * be verified. */ /* - * Minimum sizes of fields + * Cryptodefines */ -#define COOKIE_LEN (5 * 4) -#define AUTOKEY_LEN (6 * 4) -#define VALUE_LEN (6 * 4) +#define TAI_1972 10 /* initial TAI offset (s) */ +#define MAX_LEAP 100 /* max UTC leapseconds (s) */ +#define VALUE_LEN (6 * 4) /* min response field length */ +#define YEAR (60 * 60 * 24 * 365) /* seconds in year */ /* - * Global cryptodata in host byte order. + * Global cryptodata in host byte order */ -u_int crypto_flags; /* status word */ +u_int32 crypto_flags = 0x0; /* status word */ u_int sys_tai; /* current UTC offset from TAI */ -#ifdef PUBKEY /* - * Cryptodefines - */ -#define TAI_1972 10 /* initial TAI offset */ -#define MAX_LEAP 100 /* max UTC leapseconds */ -#define MAX_LINLEN 1024 /* max line */ -#define MAX_KEYLEN 1024 /* max key */ -#define MAX_ENCLEN (ENCODED_CONTENT_LEN(1024)) /* max enc key */ - -/* - * Private cryptodata in network byte order. + * Global cryptodata in network byte order */ -static R_RSA_PRIVATE_KEY private_key; /* private key */ -static R_RSA_PUBLIC_KEY public_key; /* public key */ -static R_DH_PARAMS dh_params; /* agreement parameters */ -static u_char *dh_private; /* private value */ -static u_int dh_keyLen; /* private value length */ -static char *keysdir = NTP_KEYSDIR; /* crypto keys directory */ -static char *private_key_file = NULL; /* private key file */ -static char *public_key_file = NULL; /* public key file */ -static char *certif_file = NULL; /* certificate file */ -static char *dh_params_file = NULL; /* agreement parameters file */ -static char *tai_leap_file = NULL; /* leapseconds file */ +struct cert_info *cinfo = NULL; /* certificate info/value */ +struct value hostval; /* host value */ +struct value pubkey; /* public key */ +struct value tai_leap; /* leapseconds table */ /* - * Global cryptodata in network byte order + * Private cryptodata in host byte order */ -struct value host; /* host name/public key */ -struct value certif; /* certificate */ -struct value dhparam; /* agreement parameters */ -struct value dhpub; /* public value */ -struct value tai_leap; /* leapseconds table */ +static char *passwd = NULL; /* private key password */ +static EVP_PKEY *host_pkey = NULL; /* host key */ +static EVP_PKEY *sign_pkey = NULL; /* sign key */ +static EVP_PKEY *iffpar_pkey = NULL; /* IFF parameters */ +static EVP_PKEY *gqpar_pkey = NULL; /* GQ parameters */ +static EVP_PKEY *mvpar_pkey = NULL; /* MV parameters */ +static const EVP_MD *sign_digest = NULL; /* sign digest */ +static u_int sign_siglen; /* sign key length */ +static char *rand_file = NULL; /* random seed file */ +static char *host_file = NULL; /* host key file */ +static char *sign_file = NULL; /* sign key file */ +static char *iffpar_file = NULL; /* IFF parameters file */ +static char *gqpar_file = NULL; /* GQ parameters file */ +static char *mvpar_file = NULL; /* MV parameters file */ +static char *cert_file = NULL; /* certificate file */ +static char *leap_file = NULL; /* leapseconds file */ +static tstamp_t if_fstamp = 0; /* IFF file stamp */ +static tstamp_t gq_fstamp = 0; /* GQ file stamp */ +static tstamp_t mv_fstamp = 0; /* MV file stamp */ /* * Cryptotypes */ -static u_int crypto_rsa P((char *, u_char *, u_int)); -static void crypto_cert P((char *)); -static void crypto_dh P((char *)); +static int crypto_verify P((struct exten *, struct value *, + struct peer *)); +static int crypto_encrypt P((struct exten *, struct value *, + keyid_t *)); +static int crypto_alice P((struct peer *, struct value *)); +static int crypto_alice2 P((struct peer *, struct value *)); +static int crypto_alice3 P((struct peer *, struct value *)); +static int crypto_bob P((struct exten *, struct value *)); +static int crypto_bob2 P((struct exten *, struct value *)); +static int crypto_bob3 P((struct exten *, struct value *)); +static int crypto_iff P((struct exten *, struct peer *)); +static int crypto_gq P((struct exten *, struct peer *)); +static int crypto_mv P((struct exten *, struct peer *)); +static u_int crypto_send P((struct exten *, struct value *)); +static tstamp_t crypto_time P((void)); +static u_long asn2ntp P((ASN1_TIME *)); +static struct cert_info *cert_parse P((u_char *, u_int, tstamp_t)); +static int cert_sign P((struct exten *, struct value *)); +static int cert_valid P((struct cert_info *, EVP_PKEY *)); +static int cert_install P((struct exten *, struct peer *)); +static void cert_free P((struct cert_info *)); +static EVP_PKEY *crypto_key P((char *, tstamp_t *)); +static int bighash P((BIGNUM *, BIGNUM *)); +static struct cert_info *crypto_cert P((char *)); static void crypto_tai P((char *)); -#endif /* PUBKEY */ -/* - * Autokey protocol status codes - */ -#define RV_OK 0 /* success */ -#define RV_LEN 1 /* invalid field length */ -#define RV_TSP 2 /* invalid timestamp */ -#define RV_FSP 3 /* invalid filestamp */ -#define RV_PUB 4 /* missing public key */ -#define RV_KEY 5 /* invalid RSA modulus */ -#define RV_SIG 6 /* invalid signature length */ -#define RV_DH 7 /* invalid agreement parameters */ -#define RV_FIL 8 /* missing or corrupted key file */ -#define RV_DAT 9 /* missing or corrupted data */ -#define RV_DEC 10 /* PEM decoding error */ -#define RV_DUP 11 /* duplicate flags */ -#define RV_VN 12 /* incorrect version */ +#ifdef SYS_WINNT +int +readlink(char * link, char * file, int len) { + return (-1); +} +#endif /* * session_key - generate session key @@ -143,43 +174,61 @@ static void crypto_tai P((char *)); * 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. + * + * Returns the next key ID */ -keyid_t /* returns next key ID */ +keyid_t 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 */ + struct sockaddr_storage *srcadr, /* source address */ + struct sockaddr_storage *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 */ + EVP_MD_CTX ctx; /* message digest context */ + u_char dgst[EVP_MAX_MD_SIZE]; /* message digest */ + keyid_t keyid; /* key identifer */ + u_int32 header[10]; /* data in network byte order */ + u_int hdlen, len; /* * 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); + hdlen = 0; + switch(srcadr->ss_family) { + case AF_INET: + header[0] = ((struct sockaddr_in *)srcadr)->sin_addr.s_addr; + header[1] = ((struct sockaddr_in *)dstadr)->sin_addr.s_addr; + header[2] = htonl(keyno); + header[3] = htonl(private); + hdlen = 4 * sizeof(u_int32); + break; + case AF_INET6: + memcpy(&header[0], &GET_INADDR6(*srcadr), + sizeof(struct in6_addr)); + memcpy(&header[4], &GET_INADDR6(*dstadr), + sizeof(struct in6_addr)); + header[8] = htonl(keyno); + header[9] = htonl(private); + hdlen = 10 * sizeof(u_int32); + break; + } + EVP_DigestInit(&ctx, EVP_md5()); + EVP_DigestUpdate(&ctx, (u_char *)header, hdlen); + EVP_DigestFinal(&ctx, dgst, &len); + memcpy(&keyid, dgst, 4); keyid = ntohl(keyid); if (lifetime != 0) { - MD5auth_setkey(keyno, digest, 16); + MD5auth_setkey(keyno, dgst, len); 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, + stoa(srcadr), stoa(dstadr), keyno, private, keyid, lifetime); #endif return (keyid); @@ -201,26 +250,22 @@ make_keylist( struct interface *dstadr /* interface */ ) { + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ 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 */ + struct value *vp; /* value pointer */ + keyid_t keyid = 0; /* next key ID */ + keyid_t cookie; /* private value */ + u_long lifetime; + u_int len; + int i; /* * Allocate the key list if necessary. */ - L_CLR(&tstamp); - if (sys_leap != LEAP_NOTINSYNC) - get_systime(&tstamp); + tstamp = crypto_time(); if (peer->keylist == NULL) - peer->keylist = (keyid_t *)emalloc(sizeof(keyid_t) * + peer->keylist = emalloc(sizeof(keyid_t) * NTP_MAXSESSION); /* @@ -243,55 +288,57 @@ make_keylist( * included in the hash is zero if broadcast mode, the peer * cookie if client mode or the host cookie if symmetric modes. */ - ltemp = min(sys_automax, NTP_MAXSESSION * (1 << (peer->kpoll))); - peer->hcookie = session_key(&dstadr->sin, &peer->srcadr, 0, - sys_private, 0); + lifetime = min(sys_automax, (unsigned long) NTP_MAXSESSION * (1 <<(peer->kpoll))); if (peer->hmode == MODE_BROADCAST) cookie = 0; else - cookie = peer->pcookie.key; + cookie = peer->pcookie; for (i = 0; i < NTP_MAXSESSION; i++) { peer->keylist[i] = keyid; peer->keynumber = i; keyid = session_key(&dstadr->sin, &peer->srcadr, keyid, - cookie, ltemp); - ltemp -= 1 << peer->kpoll; + cookie, lifetime); + lifetime -= 1 << peer->kpoll; if (auth_havekey(keyid) || keyid <= NTP_MAXKEY || - ltemp <= (1 << (peer->kpoll))) + lifetime <= (unsigned long)(1 << (peer->kpoll))) 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. + * careful not to use invalid key media. Use the public values + * timestamp as filestamp. */ - ap = &peer->sndauto; - ap->tstamp = htonl(tstamp.l_ui); + vp = &peer->sndval; + if (vp->ptr == NULL) + vp->ptr = emalloc(sizeof(struct autokey)); + ap = (struct autokey *)vp->ptr; ap->seq = htonl(peer->keynumber); ap->key = htonl(keyid); - ap->siglen = 0; -#if DEBUG + vp->tstamp = htonl(tstamp); + vp->fstamp = hostval.tstamp; + vp->vallen = htonl(sizeof(struct autokey)); + vp->siglen = 0; + if (vp->tstamp != 0) { + if (vp->sig == NULL) + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)vp, 12); + EVP_SignUpdate(&ctx, vp->ptr, sizeof(struct autokey)); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + else + msyslog(LOG_ERR, "make_keys %s\n", + ERR_error_string(ERR_get_error(), NULL)); + peer->flags |= FLAG_ASSOC; + } +#ifdef DEBUG if (debug) - printf("make_keys: %d %08x %08x ts %u poll %d\n", + printf("make_keys: %d %08x %08x ts %u fs %u poll %d\n", ntohl(ap->seq), ntohl(ap->key), cookie, - ntohl(ap->tstamp), peer->kpoll); + ntohl(vp->tstamp), ntohl(vp->fstamp), peer->kpoll); #endif -#ifdef PUBKEY - if(!crypto_flags) - return; - if (ap->sig == NULL) - ap->sig = emalloc(private_key.bits / 8); - EVP_SignInit(&ctx, DA_MD5); - EVP_SignUpdate(&ctx, (u_char *)ap, 12); - rval = EVP_SignFinal(&ctx, ap->sig, &len, &private_key); - if (rval != RV_OK) - msyslog(LOG_ERR, "crypto: keylist signature fails %x", - rval); - else - ap->siglen = htonl(len); - peer->flags |= FLAG_ASSOC; -#endif /* PUBKEY */ } @@ -303,688 +350,778 @@ make_keylist( * extension field values only if the field has proper format and * length, the timestamp and filestamp are valid and the signature has * valid length and is verified. There are a few cases where some values - * are believed even if the signature fails, but only if the authentic + * are believed even if the signature fails, but only if the proventic * bit is not set. */ -void +int 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 */ - tstamp_t tstamp; /* timestamp */ - int i, rval; - u_int temp; -#ifdef PUBKEY - R_SIGNATURE_CTX ctx; /* signature context */ - struct value *vp; /* value pointer */ - u_char dh_key[MAX_KEYLEN]; /* agreed key */ - R_RSA_PUBLIC_KEY *kp; /* temporary public key pointer */ - tstamp_t fstamp; /* filestamp */ - u_int32 *pp; /* packet pointer */ - u_int rsalen = sizeof(R_RSA_PUBLIC_KEY) - sizeof(u_int) + 4; - u_int bits; - int j; + const EVP_MD *dp; /* message digest algorithm */ + u_int32 *pkt; /* receive packet pointer */ + struct autokey *ap, *bp; /* autokey pointer */ + struct exten *ep, *fp; /* extension pointers */ + int has_mac; /* length of MAC field */ + int authlen; /* offset of MAC field */ + associd_t associd; /* association ID */ + tstamp_t tstamp = 0; /* timestamp */ + tstamp_t fstamp = 0; /* filestamp */ + u_int len; /* extension field length */ + u_int code; /* extension field opcode */ + u_int vallen = 0; /* value length */ + X509 *cert; /* X509 certificate */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + keyid_t cookie; /* crumbles */ + int rval = XEVNT_OK; + u_char *ptr; + u_int32 temp32; #ifdef KERNEL_PLL #if NTP_API > 3 struct timex ntv; /* kernel interface structure */ #endif /* NTP_API */ #endif /* KERNEL_PLL */ -#endif /* PUBKEY */ /* * Initialize. Note that the packet has already been checked for - * valid format and extension field lengths. We first extract - * the field length, command code and timestamp in host byte - * order. These are used with all commands and modes. We discard - * old timestamps and filestamps; but, for duplicate timestamps - * we discard only if the authentic bit is set. Cute. + * valid format and extension field lengths. First extract the + * field length, command code and association ID in host byte + * order. These are used with all commands and modes. Then check + * the version number, which must be 2, and length, which must + * be at least 8 for requests and VALUE_LEN (24) for responses. + * Packets that fail either test sink without a trace. The + * association ID is saved only if nonzero. */ - 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; - temp = (code >> 8) & 0x3f; - if (temp != CRYPTO_VN) { - sys_unknownversion++; -#ifdef DEBUG - if (debug) - printf( - "crypto_recv: incorrect version %d should be %d\n", - temp, CRYPTO_VN); -#endif - return; - } - tstamp = ntohl(pkt[i + 2]); + pkt = (u_int32 *)&rbufp->recv_pkt + authlen / 4; + ep = (struct exten *)pkt; + code = ntohl(ep->opcode) & 0xffff0000; + len = ntohl(ep->opcode) & 0x0000ffff; + associd = (associd_t) ntohl(pkt[1]); + rval = XEVNT_OK; #ifdef DEBUG if (debug) printf( - "crypto_recv: ext offset %d len %d code %x assocID %d\n", - authlen, len, code, (u_int32)ntohl(pkt[i + - 1])); + "crypto_recv: flags 0x%x ext offset %d len %u code %x assocID %d\n", + peer->crypto, authlen, len, code >> 16, + associd); #endif + + /* + * Check version number and field length. If bad, + * quietly ignore the packet. + */ + if (((code >> 24) & 0x3f) != CRYPTO_VN || len < 8 || + (len < VALUE_LEN && (code & CRYPTO_RESP))) { + sys_unknownversion++; + code |= CRYPTO_ERROR; + } + + /* + * Little vulnerability bandage here. If a perp tosses a + * fake association ID over the fence, we better toss it + * out. Only the first one counts. + */ + if (code & CRYPTO_RESP) { + if (peer->assoc == 0) + peer->assoc = associd; + else if (peer->assoc != associd) + code |= CRYPTO_ERROR; + } + if (len >= VALUE_LEN) { + tstamp = ntohl(ep->tstamp); + fstamp = ntohl(ep->fstamp); + vallen = ntohl(ep->vallen); + } switch (code) { /* - * Install association ID and status word. + * Install status word, host name, signature scheme and + * association ID. In OpenSSL the signature algorithm is + * bound to the digest algorithm, so the NID completely + * defines the signature scheme. Note the request and + * response are identical, but neither is validated by + * signature. The request is processed here only in + * symmetric modes. The server name field would be + * useful to implement access controls in future. */ + case CRYPTO_ASSOC: + + /* + * Pass the extension field to the transmit + * side. + */ + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + /* fall through */ + case CRYPTO_ASSOC | CRYPTO_RESP: - cp = (struct cookie *)&pkt[i + 2]; - temp = ntohl(cp->key); - if (len < COOKIE_LEN) { - rval = RV_LEN; - } else if (tstamp == 0) { - rval = RV_TSP; - } else { - if (!peer->crypto) - peer->crypto = temp; - if (ntohl(pkt[i + 1]) != 0) - peer->assoc = ntohl(pkt[i + 1]); - rval = RV_OK; + + /* + * Discard the message if it has already been + * stored or the server is not synchronized. + */ + if (peer->crypto || !fstamp) + break; + + if (len < VALUE_LEN + vallen) { + rval = XEVNT_LEN; + break; + } + + /* + * Check the identity schemes are compatible. If + * the client has PC, the server must have PC, + * in which case the server public key and + * identity are presumed valid, so we skip the + * certificate and identity exchanges and move + * immediately to the cookie exchange which + * confirms the server signature. If the client + * has IFF or GC or both, the server must have + * the same one or both. Otherwise, the default + * TC scheme is used. + */ + if (crypto_flags & CRYPTO_FLAG_PRIV) { + if (!(fstamp & CRYPTO_FLAG_PRIV)) + rval = XEVNT_KEY; + else + fstamp |= CRYPTO_FLAG_VALID | + CRYPTO_FLAG_VRFY; + } else if (crypto_flags & CRYPTO_FLAG_MASK && + !(crypto_flags & fstamp & + CRYPTO_FLAG_MASK)) { + rval = XEVNT_KEY; } + + /* + * Discard the message if identity error. + */ + if (rval != XEVNT_OK) + break; + + /* + * Discard the message if the host name length + * is unreasonable or the signature digest NID + * is not supported. + */ + temp32 = (fstamp >> 16) & 0xffff; + dp = + (const EVP_MD *)EVP_get_digestbynid(temp32); + if (vallen == 0 || vallen > MAXHOSTNAME) + rval = XEVNT_LEN; + else if (dp == NULL) + rval = XEVNT_MD; + if (rval != XEVNT_OK) + break; + + /* + * Save status word, host name and message + * digest/signature type. If PC identity, be + * sure not to sign the certificate. + */ + if (crypto_flags & CRYPTO_FLAG_PRIV) + fstamp |= CRYPTO_FLAG_SIGN; + peer->crypto = fstamp; + peer->digest = dp; + peer->subject = emalloc(vallen + 1); + memcpy(peer->subject, ep->pkt, vallen); + peer->subject[vallen] = '\0'; + peer->issuer = emalloc(vallen + 1); + strcpy(peer->issuer, peer->subject); + temp32 = (fstamp >> 16) & 0xffff; + sprintf(statstr, + "flags 0x%x host %s signature %s", fstamp, + peer->subject, OBJ_nid2ln(temp32)); + record_crypto_stats(&peer->srcadr, statstr); #ifdef DEBUG if (debug) - printf( - "crypto_recv: verify %d flags 0x%x ts %u\n", - rval, temp, tstamp); + printf("crypto_recv: %s\n", statstr); #endif break; /* - * Install autokey values in broadcast client and - * symmetric modes. + * Decode X509 certificate in ASN.1 format and extract + * the data containing, among other things, subject + * name and public key. In the default identification + * scheme, the certificate trail is followed to a self + * signed trusted certificate. */ - case CRYPTO_AUTO | CRYPTO_RESP: - if (!(peer->flags & FLAG_AUTOKEY) && - ntohl(pkt[i + 1]) != 0) - peer->assoc = ntohl(pkt[i + 1]); - ap = (struct autokey *)&pkt[i + 2]; -#ifdef PUBKEY - temp = ntohl(ap->siglen); - kp = (R_RSA_PUBLIC_KEY *)peer->pubkey.ptr; - if (len < AUTOKEY_LEN) { - rval = RV_LEN; - } else if (tstamp == 0 || tstamp < - peer->recauto.tstamp || (tstamp == - peer->recauto.tstamp && (peer->flags & - FLAG_AUTOKEY))) { - rval = RV_TSP; - } else if (!crypto_flags) { - rval = RV_OK; - } else if (kp == NULL) { - rval = RV_PUB; - } else if (temp != kp->bits / 8) { - rval = RV_SIG; - } else { - EVP_VerifyInit(&ctx, DA_MD5); - EVP_VerifyUpdate(&ctx, (u_char *)ap, - 12); - rval = EVP_VerifyFinal(&ctx, - (u_char *)ap->pkt, temp, kp); + case CRYPTO_CERT | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * Scan the certificate list to delete old + * versions and link the newest version first on + * the list. + */ + if ((rval = cert_install(ep, peer)) != XEVNT_OK) + break; + + /* + * If we snatch the certificate before the + * server certificate has been signed by its + * server, it will be self signed. When it is, + * we chase the certificate issuer, which the + * server has, and keep going until a self + * signed trusted certificate is found. Be sure + * to update the issuer field, since it may + * change. + */ + if (peer->issuer != NULL) + free(peer->issuer); + peer->issuer = emalloc(strlen(cinfo->issuer) + + 1); + strcpy(peer->issuer, cinfo->issuer); + + /* + * We plug in the public key and group key in + * the first certificate received. However, note + * that this certificate might not be signed by + * the server, so we can't check the + * signature/digest NID. + */ + if (peer->pkey == NULL) { + ptr = (u_char *)cinfo->cert.ptr; + cert = d2i_X509(NULL, &ptr, + ntohl(cinfo->cert.vallen)); + peer->pkey = X509_get_pubkey(cert); + X509_free(cert); } -#else /* PUBKEY */ - if (tstamp < peer->recauto.tstamp || (tstamp == - peer->recauto.tstamp && (peer->flags & - FLAG_AUTOKEY))) - rval = RV_TSP; - else - rval = RV_OK; -#endif /* PUBKEY */ + peer->flash &= ~TEST10; + temp32 = cinfo->nid; + sprintf(statstr, "cert %s 0x%x %s (%u) fs %u", + cinfo->subject, cinfo->flags, + OBJ_nid2ln(temp32), temp32, + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); #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); + printf("crypto_recv: %s\n", statstr); #endif - if (rval != RV_OK) { - if (rval != RV_TSP) - msyslog(LOG_ERR, - "crypto: %x autokey %d %08x ts %u (%u)\n", - rval, ntohl(ap->seq), - ntohl(ap->key), tstamp, - peer->recauto.tstamp); - break; - } - peer->flags |= FLAG_AUTOKEY; - peer->flash &= ~TEST10; - peer->assoc = ntohl(pkt[i + 1]); - peer->recauto.tstamp = 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. Use this also - * in symmetric modes for test when rsaref20 has not - * been installed. + * Schnorr (IFF)identity scheme. This scheme is designed + * for use with shared secret group keys and where the + * certificate may be generated by a third party. The + * client sends a challenge to the server, which + * performs a calculation and returns the result. A + * positive result is possible only if both client and + * server contain the same secret group key. */ - case CRYPTO_PRIV: - peer->cmmd = ntohl(pkt[i]); - /* fall through */ + case CRYPTO_IFF | CRYPTO_RESP: - case CRYPTO_PRIV | CRYPTO_RESP: - cp = (struct cookie *)&pkt[i + 2]; -#ifdef PUBKEY - temp = ntohl(cp->siglen); - kp = (R_RSA_PUBLIC_KEY *)peer->pubkey.ptr; - if (len < COOKIE_LEN) { - rval = RV_LEN; - } else if (tstamp == 0 || tstamp < - peer->pcookie.tstamp || (tstamp == - peer->pcookie.tstamp && (peer->flags & - FLAG_AUTOKEY))) { - rval = RV_TSP; - } else if (!crypto_flags) { - rval = RV_OK; - } else if (kp == NULL) { - rval = RV_PUB; - } else if (temp != kp->bits / 8) { - rval = RV_SIG; - } else { - EVP_VerifyInit(&ctx, DA_MD5); - EVP_VerifyUpdate(&ctx, (u_char *)cp, 8); - rval = EVP_VerifyFinal(&ctx, - (u_char *)cp->pkt, temp, kp); - } -#else /* PUBKEY */ - if (tstamp <= peer->pcookie.tstamp || (tstamp == - peer->pcookie.tstamp && (peer->flags & - FLAG_AUTOKEY))) - rval = RV_TSP; - else - rval = RV_OK; -#endif /* PUBKEY */ + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; /* - * Tricky here. If in client mode, use the - * server cookie; otherwise, use EXOR of both - * peer cookies. We call this Daffy-Hooligan - * agreement. + * If the the challenge matches the response, + * the certificate public key, as well as the + * server public key, signatyre and identity are + * all verified at the same time. The server is + * declared trusted, so we skip further + * certificate stages and move immediately to + * the cookie stage. */ - if (peer->hmode == MODE_CLIENT) - temp = ntohl(cp->key); - else - temp = ntohl(cp->key) ^ peer->hcookie; + if ((rval = crypto_iff(ep, peer)) != XEVNT_OK) + break; + + peer->crypto |= CRYPTO_FLAG_VRFY | + CRYPTO_FLAG_PROV; + peer->flash &= ~TEST10; + sprintf(statstr, "iff fs %u", + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); #ifdef DEBUG if (debug) - printf( - "crypto_recv: verify %x cookie %08x ts %u (%u)\n", - rval, temp, tstamp, - peer->pcookie.tstamp); + printf("crypto_recv: %s\n", statstr); #endif - if (rval != RV_OK) { - if (rval != RV_TSP) - msyslog(LOG_ERR, - "crypto: %x cookie %08x ts %u (%u)\n", - rval, temp, tstamp, - peer->pcookie.tstamp); - peer->cmmd |= CRYPTO_ERROR; - break; - } - if (!(peer->cast_flags & MDF_BCLNT)) - peer->flags |= FLAG_AUTOKEY; - peer->flash &= ~TEST10; - peer->assoc = ntohl(pkt[i + 1]); - peer->pcookie.tstamp = tstamp; - if (temp != peer->pcookie.key) { - peer->pcookie.key = temp; - key_expire(peer); - } break; /* - * The following commands and responses work only when - * public-key cryptography has been configured. If - * configured, but disabled due to no crypto command in - * the configuration file, they are ignored. + * Guillou-Quisquater (GQ) identity scheme. This scheme + * is designed for use with public certificates carrying + * the GQ public key in an extension field. The client + * sends a challenge to the server, which performs a + * calculation and returns the result. A positive result + * is possible only if both client and server contain + * the same group key and the server has the matching GQ + * private key. */ -#ifdef PUBKEY - /* - * Install public key and host name. - */ - case CRYPTO_NAME | CRYPTO_RESP: - if (!crypto_flags) + case CRYPTO_GQ | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) break; - vp = (struct value *)&pkt[i + 2]; - fstamp = ntohl(vp->fstamp); - temp = ntohl(vp->vallen); - j = i + 5 + ntohl(vp->vallen) / 4; - bits = ntohl(pkt[i + 5]); - if (len < VALUE_LEN) { - rval = RV_LEN; - } else if (temp < rsalen || bits < - MIN_RSA_MODULUS_BITS || bits > - MAX_RSA_MODULUS_BITS) { - rval = RV_KEY; - } else if (ntohl(pkt[j]) != bits / 8) { - rval = RV_SIG; - } else if (tstamp == 0 || tstamp < - peer->pubkey.tstamp || (tstamp == - peer->pubkey.tstamp && (peer->flags & - FLAG_AUTOKEY))) { - rval = RV_TSP; - } else if (tstamp < peer->pubkey.fstamp || - fstamp < peer->pubkey.fstamp) { - rval = RV_FSP; - } else if (fstamp == peer->pubkey.fstamp && - (peer->flags & FLAG_AUTOKEY)) { - rval = RV_FSP; - } else { - EVP_VerifyInit(&ctx, DA_MD5); - EVP_VerifyUpdate(&ctx, (u_char *)vp, - temp + 12); - kp = emalloc(sizeof(R_RSA_PUBLIC_KEY)); - kp->bits = bits; - memcpy(kp->modulus, &pkt[i + 6], - rsalen - 4); - rval = EVP_VerifyFinal(&ctx, - (u_char *)&pkt[j + 1], - ntohl(pkt[j]), kp); - if (rval != 0) { - free(kp); - } else { - j = i + 5 + rsalen / 4; - peer->pubkey.ptr = (u_char *)kp; - temp = strlen((char *)&pkt[j]); - peer->keystr = emalloc(temp + - 1); - strcpy(peer->keystr, - (char *)&pkt[j]); - peer->pubkey.tstamp = tstamp; - peer->pubkey.fstamp = fstamp; - peer->flash &= ~TEST10; - if (!(peer->crypto & - CRYPTO_FLAG_CERT)) - peer->flags |= - FLAG_PROVEN; - } - } + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * If the the challenge matches the response, + * the certificate public key, as well as the + * server public key, signatyre and identity are + * all verified at the same time. The server is + * declared trusted, so we skip further + * certificate stages and move immediately to + * the cookie stage. + */ + if ((rval = crypto_gq(ep, peer)) != XEVNT_OK) + break; + + peer->crypto |= CRYPTO_FLAG_VRFY | + CRYPTO_FLAG_PROV; + peer->flash &= ~TEST10; + sprintf(statstr, "gq fs %u", + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); #ifdef DEBUG if (debug) - - printf( - "crypto_recv: verify %x host %s ts %u fs %u\n", - rval, (char *)&pkt[i + 5 + rsalen / - 4], tstamp, fstamp); + printf("crypto_recv: %s\n", statstr); #endif - if (rval != RV_OK) { - if (rval != RV_TSP) - msyslog(LOG_ERR, - "crypto: %x host %s ts %u fs %u\n", - rval, (char *)&pkt[i + 5 + - rsalen / 4], tstamp, - fstamp); - } break; /* - * Install certificate. + * MV */ - case CRYPTO_CERT | CRYPTO_RESP: - if (!crypto_flags) + case CRYPTO_MV | CRYPTO_RESP: + + /* + * Discard the message if invalid or identity + * already confirmed. + */ + if (peer->crypto & CRYPTO_FLAG_VRFY) break; - vp = (struct value *)&pkt[i + 2]; - fstamp = ntohl(vp->fstamp); - temp = ntohl(vp->vallen); - kp = (R_RSA_PUBLIC_KEY *)peer->pubkey.ptr; - j = i + 5 + temp / 4; - if (len < VALUE_LEN) { - rval = RV_LEN; - } else if (kp == NULL) { - rval = RV_PUB; - } else if (ntohl(pkt[j]) != kp->bits / 8) { - rval = RV_SIG; - } else if (tstamp == 0) { - rval = RV_TSP; - } else if (tstamp < - ntohl(peer->certif.fstamp) || fstamp < - ntohl(peer->certif.fstamp)) { - rval = RV_FSP; - } else if (fstamp == - ntohl(peer->certif.fstamp) && (peer->flags & - FLAG_AUTOKEY)) { - peer->crypto &= ~CRYPTO_FLAG_CERT; - rval = RV_FSP; - } else { - EVP_VerifyInit(&ctx, DA_MD5); - EVP_VerifyUpdate(&ctx, (u_char *)vp, - temp + 12); - rval = EVP_VerifyFinal(&ctx, - (u_char *)&pkt[j + 1], - ntohl(pkt[j]), kp); - } + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * If the the challenge matches the response, + * the certificate public key, as well as the + * server public key, signatyre and identity are + * all verified at the same time. The server is + * declared trusted, so we skip further + * certificate stages and move immediately to + * the cookie stage. + */ + if ((rval = crypto_mv(ep, peer)) != XEVNT_OK) + break; + + peer->crypto |= CRYPTO_FLAG_VRFY | + CRYPTO_FLAG_PROV; + peer->flash &= ~TEST10; + sprintf(statstr, "mv fs %u", + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); #ifdef DEBUG if (debug) - printf( - "crypto_recv: verify %x certificate %u ts %u fs %u\n", - rval, temp, tstamp, fstamp); + printf("crypto_recv: %s\n", statstr); #endif + break; + + /* + * X509 certificate sign response. Validate the + * certificate signed by the server and install. Later + * this can be provided to clients of this server in + * lieu of the self signed certificate in order to + * validate the public key. + */ + case CRYPTO_SIGN | CRYPTO_RESP: /* - * If the peer data are newer than the host - * data, replace the host data. Otherwise, - * wait for the peer to fetch the host data. + * Discard the message if invalid or identity + * not confirmed. */ - if (rval != RV_OK || temp == 0) { - if (rval != RV_TSP) - msyslog(LOG_ERR, - "crypto: %x certificate %u ts %u fs %u\n", - rval, temp, tstamp, fstamp); + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) break; - } - peer->flash &= ~TEST10; - peer->flags |= FLAG_PROVEN; - peer->crypto &= ~CRYPTO_FLAG_CERT; /* - * Initialize agreement parameters and extension - * field in network byte order. Note the private - * key length is set arbitrarily at half the - * prime length. + * Scan the certificate list to delete old + * versions and link the newest version first on + * the list. */ - peer->certif.tstamp = vp->tstamp; - peer->certif.fstamp = vp->fstamp; - peer->certif.vallen = vp->vallen; - if (peer->certif.ptr == NULL) - free(peer->certif.ptr); - peer->certif.ptr = emalloc(temp); - memcpy(peer->certif.ptr, vp->pkt, temp); - crypto_agree(); + if ((rval = cert_install(ep, peer)) != XEVNT_OK) break; + + peer->crypto |= CRYPTO_FLAG_SIGN; + peer->flash &= ~TEST10; + temp32 = cinfo->nid; + sprintf(statstr, "sign %s 0x%x %s (%u) fs %u", + cinfo->issuer, cinfo->flags, + OBJ_nid2ln(temp32), temp32, + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif break; /* - * Install agreement parameters in symmetric modes. + * Cookie request in symmetric modes. Roll a random + * cookie and install in symmetric mode. Encrypt for the + * response, which is transmitted later. */ - case CRYPTO_DHPAR | CRYPTO_RESP: - if (!crypto_flags) + case CRYPTO_COOK: + + /* + * Discard the message if invalid or identity + * not confirmed. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * Pass the extension field to the transmit + * side. If already agreed, walk away. + */ + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + if (peer->crypto & CRYPTO_FLAG_AGREE) { + peer->flash &= ~TEST10; break; - vp = (struct value *)&pkt[i + 2]; - fstamp = ntohl(vp->fstamp); - temp = ntohl(vp->vallen); - kp = (R_RSA_PUBLIC_KEY *)peer->pubkey.ptr; - j = i + 5 + temp / 4; - if (len < VALUE_LEN) { - rval = RV_LEN; - } else if (kp == NULL) { - rval = RV_PUB; - } else if (ntohl(pkt[j]) != kp->bits / 8) { - rval = RV_SIG; - } else if (tstamp == 0) { - rval = RV_TSP; - } else if (tstamp < ntohl(dhparam.fstamp) || - fstamp < ntohl(dhparam.fstamp)) { - rval = RV_FSP; - } else if (fstamp == ntohl(dhparam.fstamp) && - (peer->flags & FLAG_AUTOKEY)) { - peer->crypto &= ~CRYPTO_FLAG_DH; - rval = RV_FSP; - } else { - EVP_VerifyInit(&ctx, DA_MD5); - EVP_VerifyUpdate(&ctx, (u_char *)vp, - temp + 12); - rval = EVP_VerifyFinal(&ctx, - (u_char *)&pkt[j + 1], - ntohl(pkt[j]), kp); } + + /* + * Install cookie values and light the cookie + * bit. The transmit side will pick up and + * encrypt it for the response. + */ + key_expire(peer); + peer->cookval.tstamp = ep->tstamp; + peer->cookval.fstamp = ep->fstamp; + RAND_bytes((u_char *)&peer->pcookie, 4); + peer->crypto &= ~CRYPTO_FLAG_AUTO; + peer->crypto |= CRYPTO_FLAG_AGREE; + peer->flash &= ~TEST10; + sprintf(statstr, "cook %x ts %u fs %u", + peer->pcookie, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); #ifdef DEBUG if (debug) - printf( - "crypto_recv: verify %x parameters %u ts %u fs %u\n", - rval, temp, tstamp, fstamp); + printf("crypto_recv: %s\n", statstr); #endif + break; + + /* + * Cookie response in client and symmetric modes. If the + * cookie bit is set, the working cookie is the EXOR of + * the current and new values. + */ + case CRYPTO_COOK | CRYPTO_RESP: /* - * If the peer data are newer than the host - * data, replace the host data. Otherwise, - * wait for the peer to fetch the host data. + * Discard the message if invalid or identity + * not confirmed or signature not verified with + * respect to the cookie values. */ - if (rval != RV_OK || temp == 0) { - if (rval != RV_TSP) - msyslog(LOG_ERR, - "crypto: %x parameters %u ts %u fs %u\n", - rval, temp, tstamp, fstamp); + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, &peer->cookval, + peer)) != XEVNT_OK) break; - } - peer->flash &= ~TEST10; - crypto_flags |= CRYPTO_FLAG_DH; - peer->crypto &= ~CRYPTO_FLAG_DH; /* - * Initialize agreement parameters and extension - * field in network byte order. Note the private - * key length is set arbitrarily at half the - * prime length. + * Decrypt the cookie, hunting all the time for + * errors. */ - dhparam.tstamp = vp->tstamp; - dhparam.fstamp = vp->fstamp; - dhparam.vallen = vp->vallen; - if (dhparam.ptr != NULL) - free(dhparam.ptr); - pp = emalloc(temp); - dhparam.ptr = (u_char *)pp; - memcpy(pp, vp->pkt, temp); - dh_params.primeLen = ntohl(*pp++); - dh_params.prime = (u_char *)pp; - pp += dh_params.primeLen / 4; - dh_params.generatorLen = ntohl(*pp++); - dh_params.generator = (u_char *)pp; - dh_keyLen = dh_params.primeLen / 2; - if (dh_private != NULL) - free(dh_private); - dh_private = emalloc(dh_keyLen); - if (dhparam.sig == NULL) - dhparam.sig = emalloc(private_key.bits / - 8); + if (vallen == (u_int) EVP_PKEY_size(host_pkey)) { + RSA_private_decrypt(vallen, + (u_char *)ep->pkt, + (u_char *)&temp32, + host_pkey->pkey.rsa, + RSA_PKCS1_OAEP_PADDING); + cookie = ntohl(temp32); + } else { + rval = XEVNT_CKY; + break; + } /* - * Initialize public value extension field. + * Install cookie values and light the cookie + * bit. If this is not broadcast client mode, we + * are done here. */ - dhpub.tstamp = vp->tstamp; - dhpub.fstamp = vp->fstamp; - dhpub.vallen = htonl(dh_params.primeLen); - if (dhpub.ptr != NULL) - free(dhpub.ptr); - dhpub.ptr = emalloc(dh_params.primeLen); - if (dhpub.sig == NULL) - dhpub.sig = emalloc(private_key.bits / - 8); - crypto_agree(); + key_expire(peer); + peer->cookval.tstamp = ep->tstamp; + peer->cookval.fstamp = ep->fstamp; + if (peer->crypto & CRYPTO_FLAG_AGREE) + peer->pcookie ^= cookie; + else + peer->pcookie = cookie; + if (peer->hmode == MODE_CLIENT && + !(peer->cast_flags & MDF_BCLNT)) + peer->crypto |= CRYPTO_FLAG_AUTO; + else + peer->crypto &= ~CRYPTO_FLAG_AUTO; + peer->crypto |= CRYPTO_FLAG_AGREE; + peer->flash &= ~TEST10; + sprintf(statstr, "cook %x ts %u fs %u", + peer->pcookie, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif break; /* - * Verify public value and compute agreed key in - * symmetric modes. + * Install autokey values in broadcast client and + * symmetric modes. We have to do this every time the + * sever/peer cookie changes or a new keylist is + * rolled. Ordinarily, this is automatic as this message + * is piggybacked on the first NTP packet sent upon + * either of these events. Note that a broadcast client + * or symmetric peer can receive this response without a + * matching request. */ - case CRYPTO_DH: - peer->cmmd = ntohl(pkt[i]); - if (!crypto_flags) - peer->cmmd |= CRYPTO_ERROR; - /* fall through */ + case CRYPTO_AUTO | CRYPTO_RESP: - case CRYPTO_DH | CRYPTO_RESP: - if (!crypto_flags) + /* + * Discard the message if invalid or identity + * not confirmed or signature not verified with + * respect to the receive autokey values. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, &peer->recval, + peer)) != XEVNT_OK) break; - vp = (struct value *)&pkt[i + 2]; - fstamp = ntohl(vp->fstamp); - temp = ntohl(vp->vallen); - kp = (R_RSA_PUBLIC_KEY *)peer->pubkey.ptr; - j = i + 5 + temp / 4; - if (len < VALUE_LEN) { - rval = RV_LEN; - } else if (temp != dh_params.primeLen) { - rval = RV_DH; - } else if (kp == NULL) { - rval = RV_PUB; - } else if (ntohl(pkt[j]) != kp->bits / 8) { - rval = RV_SIG; - } else if (tstamp == 0 || tstamp < - peer->pcookie.tstamp || (tstamp == - peer->pcookie.tstamp && (peer->flags & - FLAG_AUTOKEY))) { - rval = RV_TSP; - } else { - EVP_VerifyInit(&ctx, DA_MD5); - EVP_VerifyUpdate(&ctx, (u_char *)vp, - temp + 12); - rval = EVP_VerifyFinal(&ctx, - (u_char *)&pkt[j + 1], - ntohl(pkt[j]), kp); - } /* - * Run the agreement algorithm and stash the key - * value. We use only the first u_int32 for the - * host cookie. Wasteful. If the association ID - * is zero, the other guy hasn't seen us as - * synchronized, in which case both of us should - * be using a zero cookie. + * Install autokey values and light the + * autokey bit. This is not hard. */ - if (rval != RV_OK) { - temp = 0; - } else if (fstamp > dhparam.fstamp) { - crypto_flags &= ~CRYPTO_FLAG_DH; - rval = RV_FSP; - } else { - rval = R_ComputeDHAgreedKey(dh_key, - (u_char *)&pkt[i + 5], dh_private, - dh_keyLen, &dh_params); - temp = ntohl(*(u_int32 *)dh_key); - } + if (peer->recval.ptr == NULL) + peer->recval.ptr = + emalloc(sizeof(struct autokey)); + bp = (struct autokey *)peer->recval.ptr; + peer->recval.tstamp = ep->tstamp; + peer->recval.fstamp = ep->fstamp; + ap = (struct autokey *)ep->pkt; + bp->seq = ntohl(ap->seq); + bp->key = ntohl(ap->key); + peer->pkeyid = bp->key; + peer->crypto |= CRYPTO_FLAG_AUTO; + peer->flash &= ~TEST10; + sprintf(statstr, + "auto seq %d key %x ts %u fs %u", bp->seq, + bp->key, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); #ifdef DEBUG if (debug) - printf( - "crypto_recv: verify %x agreement %08x ts %u (%u) fs %u\n", - rval, temp, tstamp, - peer->pcookie.tstamp, fstamp); + printf("crypto_recv: %s\n", statstr); #endif - if (rval != RV_OK) { - if (rval != RV_TSP) - msyslog(LOG_ERR, - "crypto: %x agreement %08x ts %u (%u) fs %u\n", - rval, temp, tstamp, - peer->pcookie.tstamp, - fstamp); - peer->cmmd |= CRYPTO_ERROR; - break; - } - peer->flash &= ~TEST10; - peer->flags &= ~FLAG_AUTOKEY; - peer->assoc = ntohl(pkt[i + 1]); - peer->pcookie.tstamp = tstamp; - if (temp != peer->pcookie.key) { - peer->pcookie.key = temp; - key_expire(peer); - } break; /* - * Install leapseconds table. + * Install leapseconds table in symmetric modes. This + * table is proventicated to the NIST primary servers, + * either by copying the file containing the table from + * a NIST server to a trusted server or directly using + * this protocol. While the entire table is installed at + * the server, presently only the current TAI offset is + * provided via the kernel to other applications. */ - case CRYPTO_TAI | CRYPTO_RESP: - if (!crypto_flags) + case CRYPTO_TAI: + + /* + * Discard the message if invalid or identity + * not confirmed. + */ + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) + break; + + if ((rval = crypto_verify(ep, NULL, peer)) != + XEVNT_OK) + break; + + /* + * Pass the extension field to the transmit + * side. Continue below if a leapseconds table + * accompanies the message. + */ + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + if (len <= VALUE_LEN) { + peer->flash &= ~TEST10; break; - vp = (struct value *)&pkt[i + 2]; - fstamp = ntohl(vp->fstamp); - temp = ntohl(vp->vallen); - kp = (R_RSA_PUBLIC_KEY *)peer->pubkey.ptr; - j = i + 5 + temp / 4; - if (len < VALUE_LEN) { - rval = RV_LEN; - } if (kp == NULL) { - rval = RV_PUB; - } else if (ntohl(pkt[j]) != kp->bits / 8) { - rval = RV_SIG; - } else if (tstamp == 0) { - rval = RV_TSP; - } else if (tstamp < ntohl(tai_leap.fstamp) || - fstamp < ntohl(tai_leap.fstamp)) { - rval = RV_FSP; - } else if (fstamp == ntohl(tai_leap.fstamp) && - (peer->flags & FLAG_AUTOKEY)) { - peer->crypto &= ~CRYPTO_FLAG_TAI; - rval = RV_FSP; - } else { - EVP_VerifyInit(&ctx, DA_MD5); - EVP_VerifyUpdate(&ctx, (u_char *)vp, - temp + 12); - rval = EVP_VerifyFinal(&ctx, - (u_char *)&pkt[j + 1], - ntohl(pkt[j]), kp); } -#ifdef DEBUG - if (debug) - printf( - "crypto_recv: verify %x leapseconds %u ts %u fs %u\n", - rval, temp, tstamp, fstamp); -#endif + /* fall through */ + + case CRYPTO_TAI | CRYPTO_RESP: /* - * If the peer data are newer than the host - * data, replace the host data. Otherwise, - * wait for the peer to fetch the host data. + * Discard the message if invalid or identity + * not confirmed or signature not verified with + * respect to the leapsecond table values. */ - if (rval != RV_OK || temp == 0) { - if (rval != RV_TSP) - msyslog(LOG_ERR, - "crypto: %x leapseconds %u ts %u fs %u\n", - rval, temp, tstamp, fstamp); + if (!(peer->crypto & CRYPTO_FLAG_VRFY)) break; + + if ((rval = crypto_verify(ep, &peer->tai_leap, + peer)) != XEVNT_OK) + break; + + /* + * Initialize peer variables, leapseconds + * structure and extension field in network byte + * order. Since a filestamp may have changed, + * recompute the signatures. + */ + peer->tai_leap.tstamp = ep->tstamp; + peer->tai_leap.fstamp = ep->fstamp; + peer->tai_leap.vallen = ep->vallen; + + /* + * Install the new table if there is no stored + * table or the new table is more recent than + * the stored table. Since a filestamp may have + * changed, recompute the signatures. + */ + if (ntohl(peer->tai_leap.fstamp) > + ntohl(tai_leap.fstamp)) { + tai_leap.fstamp = ep->fstamp; + tai_leap.vallen = ep->vallen; + if (tai_leap.ptr != NULL) + free(tai_leap.ptr); + tai_leap.ptr = emalloc(vallen); + memcpy(tai_leap.ptr, ep->pkt, vallen); + crypto_update(); + sys_tai = vallen / 4 + TAI_1972 - 1; } - peer->flash &= ~TEST10; crypto_flags |= CRYPTO_FLAG_TAI; - peer->crypto &= ~CRYPTO_FLAG_TAI; - sys_tai = temp / 4 + TAI_1972 - 1; + peer->crypto |= CRYPTO_FLAG_LEAP; + peer->flash &= ~TEST10; #ifdef KERNEL_PLL #if NTP_API > 3 + /* + * If the kernel cooperates, initialize the + * current TAI offset. + */ ntv.modes = MOD_TAI; ntv.constant = sys_tai; (void)ntp_adjtime(&ntv); #endif /* NTP_API */ #endif /* KERNEL_PLL */ - - /* - * Initialize leapseconds table and extension - * field in network byte order. - */ - tai_leap.tstamp = vp->tstamp; - tai_leap.fstamp = vp->fstamp; - tai_leap.vallen = vp->vallen; - if (tai_leap.ptr == NULL) - free(tai_leap.ptr); - tai_leap.ptr = emalloc(temp); - memcpy(tai_leap.ptr, vp->pkt, temp); - if (tai_leap.sig == NULL) - tai_leap.sig = - emalloc(private_key.bits / 8); - crypto_agree(); + sprintf(statstr, "leap %u ts %u fs %u", + vallen, ntohl(ep->tstamp), + ntohl(ep->fstamp)); + record_crypto_stats(&peer->srcadr, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif break; -#endif /* PUBKEY */ /* - * For other requests, save the request code for later; - * for unknown responses or errors, just ignore for now. + * We come here in symmetric modes for miscellaneous + * commands that have value fields but are processed on + * the transmit side. All we need do here is check for + * valid field length. Remaining checks are below and on + * the transmit side. */ - default: - if (code & (CRYPTO_RESP | CRYPTO_ERROR)) + case CRYPTO_IFF: + case CRYPTO_GQ: + case CRYPTO_MV: + case CRYPTO_SIGN: + if (len < VALUE_LEN) { + rval = XEVNT_LEN; break; - peer->cmmd = ntohl(pkt[i]); - break; + } + + /* fall through */ + + /* + * We come here for miscellaneous requests and unknown + * requests and responses. If an unknown response or + * error, forget it. If a request, save the extension + * field for later. Unknown requests will be caught on + * the transmit side. + */ + default: + if (code & (CRYPTO_RESP | CRYPTO_ERROR)) { + rval = XEVNT_LEN; + } else if ((rval = crypto_verify(ep, NULL, + peer)) == XEVNT_OK) { + fp = emalloc(len); + memcpy(fp, ep, len); + temp32 = CRYPTO_RESP; + fp->opcode |= htonl(temp32); + peer->cmmd = fp; + } + } + /* + * We log everything except length/format errors and + * duplicates, which are log clogging vulnerabilities. + * The first error found terminates the extension field + * scan and we return the laundry to the caller. + */ + if (rval != XEVNT_OK) { + sprintf(statstr, + "error %x opcode %x ts %u fs %u", rval, + code, tstamp, fstamp); + if (rval > XEVNT_TSP) + record_crypto_stats(&peer->srcadr, + statstr); + report_event(rval, peer); +#ifdef DEBUG + if (debug) + printf("crypto_recv: %s\n", statstr); +#endif + break; } authlen += len; } + return (rval); } @@ -992,255 +1129,248 @@ crypto_recv( * 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 + * when one is not. The only case where this matters is to retrieve the + * autokey information, in which case the caller has to provide the * association ID to match the association. + * + * Returns length of extension field. */ -int /* return length of extension field */ +int crypto_xmit( - u_int32 *xpkt, /* packet pointer */ - int start, /* offset to extension field */ - u_int code, /* extension field code */ - keyid_t cookie, /* session cookie */ - u_int associd /* association ID */ + struct pkt *xpkt, /* transmit packet pointer */ + struct sockaddr_storage *srcadr_sin, /* active runway */ + int start, /* offset to extension field */ + struct exten *ep, /* extension pointer */ + keyid_t cookie /* session cookie */ ) { + u_int32 *pkt; /* packet pointer */ 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 */ + u_int opcode; /* extension field opcode */ + struct exten *fp; /* extension pointers */ + struct cert_info *cp; /* certificate info/value pointer */ + char certname[MAXHOSTNAME + 1]; /* subject name buffer */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + u_int vallen; + u_int len; + struct value vtemp; + associd_t associd; + int rval; + keyid_t tcookie; /* * Generate the requested extension field request code, length - * and association ID. Note that several extension fields are - * used with and without public-key cryptography. If public-key - * cryptography has not been configured, we do the same thing, - * but leave off the signature. - */ - i = start / 4; - opcode = code; - xpkt[i + 1] = htonl(associd); + * and association ID. If this is a response and the host is not + * synchronized, light the error bit and go home. + */ + pkt = (u_int32 *)xpkt + start / 4; + fp = (struct exten *)pkt; + opcode = ntohl(ep->opcode); + associd = (associd_t) ntohl(ep->associd); + fp->associd = htonl(associd); len = 8; - switch (opcode) { + rval = XEVNT_OK; + switch (opcode & 0xffff0000) { /* - * Send association ID, timestamp and status word. + * Send association request and response with status word and + * host name. Note, this message is not signed and the filestamp + * contains only the status word. We check at this point whether + * the identity schemes are compatible to save tears later on. */ case CRYPTO_ASSOC | CRYPTO_RESP: - cp = (struct cookie *)&xpkt[i + 2]; -#ifdef PUBKEY - cp->tstamp = host.tstamp; -#else - cp->tstamp = 0; -#endif /* PUBKEY */ - cp->key = htonl(crypto_flags); - cp->siglen = 0; - len += 12; + case CRYPTO_ASSOC: + len += crypto_send(fp, &hostval); + if (crypto_time() == 0) + fp->fstamp = 0; + else + fp->fstamp = htonl(crypto_flags); break; /* - * Find peer and send autokey data and signature in broadcast - * server and symmetric modes. If no association is found, - * either the server has restarted with new associations or some - * perp has replayed an old message. + * Send certificate request. Use the values from the extension + * field. */ - case CRYPTO_AUTO | CRYPTO_RESP: - peer = findpeerbyassoc(associd); - if (peer == NULL) { + case CRYPTO_CERT: + memset(&vtemp, 0, sizeof(vtemp)); + vtemp.tstamp = ep->tstamp; + vtemp.fstamp = ep->fstamp; + vtemp.vallen = ep->vallen; + vtemp.ptr = (unsigned char *)ep->pkt; + len += crypto_send(fp, &vtemp); + break; + + /* + * Send certificate response or sign request. Use the values + * from the certificate. If the request contains no subject + * name, assume the name of this host. This is for backwards + * compatibility. Light the error bit if no certificate with + * the given subject name is found. Of course, private + * certificates are never sent. + */ + case CRYPTO_SIGN: + case CRYPTO_CERT | CRYPTO_RESP: + vallen = ntohl(ep->vallen); + if (vallen == 8) { + strcpy(certname, sys_hostname); + } else if (vallen == 0 || vallen > MAXHOSTNAME) { opcode |= CRYPTO_ERROR; break; + + } else { + memcpy(certname, ep->pkt, vallen); + certname[vallen] = '\0'; } - peer->flags &= ~FLAG_ASSOC; - ap = (struct autokey *)&xpkt[i + 2]; - ap->tstamp = peer->sndauto.tstamp; - ap->seq = peer->sndauto.seq; - ap->key = peer->sndauto.key; - ap->siglen = peer->sndauto.siglen; - len += 16; -#ifdef PUBKEY - if (!crypto_flags) - break; - temp = ntohl(ap->siglen); - if (temp != 0) - memcpy(ap->pkt, peer->sndauto.sig, temp); - len += temp; -#endif /* PUBKEY */ + for (cp = cinfo; cp != NULL; cp = cp->link) { + if (cp->flags & CERT_PRIV) + continue; + if (strcmp(certname, cp->subject) == 0) { + len += crypto_send(fp, &cp->cert); + break; + } + } + if (cp == NULL) + opcode |= CRYPTO_ERROR; break; /* - * Send peer cookie and signature in server mode. + * Send challenge in Schnorr (IFF) identity scheme. */ - case CRYPTO_PRIV: - case CRYPTO_PRIV | CRYPTO_RESP: - cp = (struct cookie *)&xpkt[i + 2]; - cp->key = htonl(cookie); - cp->siglen = 0; - len += 12; -#ifdef PUBKEY - cp->tstamp = host.tstamp; - if (!crypto_flags) - break; - EVP_SignInit(&ctx, DA_MD5); - EVP_SignUpdate(&ctx, (u_char *)cp, 8); - rval = EVP_SignFinal(&ctx, (u_char *)cp->pkt, &temp, - &private_key); - if (rval != RV_OK) { - msyslog(LOG_ERR, - "crypto: cookie signature fails %x", rval); + case CRYPTO_IFF: + if ((peer = findpeerbyassoc(ep->pkt[0])) == NULL) { + opcode |= CRYPTO_ERROR; break; } - cp->siglen = htonl(temp); - len += temp; -#endif /* PUBKEY */ + if ((rval = crypto_alice(peer, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); break; -#ifdef PUBKEY /* - * The following commands and responses work only when public- - * key cryptography has been configured. If configured, but - * disabled due to no crypto command in the configuration file, - * they are ignored and an error response is returned. + * Send response in Schnorr (IFF) identity scheme. */ + case CRYPTO_IFF | CRYPTO_RESP: + if ((rval = crypto_bob(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + /* - * Send certificate, timestamp and signature. + * Send challenge in Guillou-Quisquater (GQ) identity scheme. */ - case CRYPTO_CERT | CRYPTO_RESP: - if (!crypto_flags) { + case CRYPTO_GQ: + if ((peer = findpeerbyassoc(ep->pkt[0])) == NULL) { opcode |= CRYPTO_ERROR; break; } - vp = (struct value *)&xpkt[i + 2]; - vp->tstamp = certif.tstamp; - vp->fstamp = certif.fstamp; - vp->vallen = 0; - len += 12; - temp = ntohl(certif.vallen); - if (temp == 0) - break; - vp->vallen = htonl(temp); - memcpy(vp->pkt, certif.ptr, temp); - len += temp; - j = i + 5 + temp / 4; - temp = public_key.bits / 8; - xpkt[j++] = htonl(temp); - memcpy(&xpkt[j], certif.sig, temp); - len += temp + 4; + if ((rval = crypto_alice2(peer, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send response in Guillou-Quisquater (GQ) identity scheme. + */ + case CRYPTO_GQ | CRYPTO_RESP: + if ((rval = crypto_bob2(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); break; /* - * Send agreement parameters, timestamp and signature. + * Send challenge in MV identity scheme. */ - case CRYPTO_DHPAR | CRYPTO_RESP: - if (!crypto_flags) { + case CRYPTO_MV: + if ((peer = findpeerbyassoc(ep->pkt[0])) == NULL) { opcode |= CRYPTO_ERROR; break; } - vp = (struct value *)&xpkt[i + 2]; - vp->tstamp = dhparam.tstamp; - vp->fstamp = dhparam.fstamp; - vp->vallen = 0; - len += 12; - temp = ntohl(dhparam.vallen); - if (temp == 0) - break; - vp->vallen = htonl(temp); - memcpy(vp->pkt, dhparam.ptr, temp); - len += temp; - j = i + 5 + temp / 4; - temp = public_key.bits / 8; - xpkt[j++] = htonl(temp); - memcpy(&xpkt[j], dhparam.sig, temp); - len += temp + 4; + if ((rval = crypto_alice3(peer, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); break; /* - * Send public value, timestamp and signature. + * Send response in MV identity scheme. */ - case CRYPTO_DH: - case CRYPTO_DH | CRYPTO_RESP: - if (!crypto_flags) { + case CRYPTO_MV | CRYPTO_RESP: + if ((rval = crypto_bob3(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send certificate sign response. The integrity of the request + * certificate has already been verified on the receive side. + * Sign the response using the local server key. Use the + * filestamp from the request and use the timestamp as the + * current time. Light the error bit if the certificate is + * invalid or contains an unverified signature. + */ + case CRYPTO_SIGN | CRYPTO_RESP: + if ((rval = cert_sign(ep, &vtemp)) == XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); + break; + + /* + * Send public key and signature. Use the values from the public + * key. + */ + case CRYPTO_COOK: + len += crypto_send(fp, &pubkey); + break; + + /* + * Encrypt and send cookie and signature. Light the error bit if + * anything goes wrong. + */ + case CRYPTO_COOK | CRYPTO_RESP: + if ((opcode & 0xffff) < VALUE_LEN) { opcode |= CRYPTO_ERROR; break; } - vp = (struct value *)&xpkt[i + 2]; - vp->tstamp = dhpub.tstamp; - vp->fstamp = dhpub.fstamp; - vp->vallen = 0; - len += 12; - temp = ntohl(dhpub.vallen); - if (temp == 0) - break; - vp->vallen = htonl(temp); - memcpy(vp->pkt, dhpub.ptr, temp); - len += temp; - j = i + 5 + temp / 4; - temp = public_key.bits / 8; - xpkt[j++] = htonl(temp); - memcpy(&xpkt[j], dhpub.sig, temp); - len += temp + 4; + if (PKT_MODE(xpkt->li_vn_mode) == MODE_SERVER) { + tcookie = cookie; + } else { + if ((peer = findpeerbyassoc(associd)) == NULL) { + opcode |= CRYPTO_ERROR; + break; + } + tcookie = peer->pcookie; + } + if ((rval = crypto_encrypt(ep, &vtemp, &tcookie)) == + XEVNT_OK) + len += crypto_send(fp, &vtemp); + value_free(&vtemp); break; /* - * Send public key, host name, timestamp and signature. + * Find peer and send autokey data and signature in broadcast + * server and symmetric modes. Use the values in the autokey + * structure. If no association is found, either the server has + * restarted with new associations or some perp has replayed an + * old message, in which case light the error bit. */ - case CRYPTO_NAME | CRYPTO_RESP: - if (!crypto_flags) { + case CRYPTO_AUTO | CRYPTO_RESP: + if ((peer = findpeerbyassoc(associd)) == NULL) { opcode |= CRYPTO_ERROR; break; } - vp = (struct value *)&xpkt[i + 2]; - vp->tstamp = host.tstamp; - vp->fstamp = host.fstamp; - vp->vallen = 0; - len += 12; - temp = ntohl(host.vallen); - if (temp == 0) - break; - vp->vallen = htonl(temp); - memcpy(vp->pkt, host.ptr, temp); - len += temp; - j = i + 5 + temp / 4; - temp = public_key.bits / 8; - xpkt[j++] = htonl(temp); - memcpy(&xpkt[j], host.sig, temp); - len += temp + 4; + peer->flags &= ~FLAG_ASSOC; + len += crypto_send(fp, &peer->sndval); break; /* - * Send leapseconds table, timestamp and signature. + * Send leapseconds table and signature. Use the values from the + * tai structure. If no table has been loaded, just send a + * request. */ + case CRYPTO_TAI: case CRYPTO_TAI | CRYPTO_RESP: - if (!crypto_flags) { - opcode |= CRYPTO_ERROR; - break; - } - vp = (struct value *)&xpkt[i + 2]; - vp->tstamp = tai_leap.tstamp; - vp->fstamp = tai_leap.fstamp; - vp->vallen = 0; - len += 12; - temp = ntohl(tai_leap.vallen); - if (temp == 0) - break; - vp->vallen = htonl(temp); - memcpy(vp->pkt, tai_leap.ptr, temp); - len += temp; - j = i + 5 + temp / 4; - temp = public_key.bits / 8; - xpkt[j++] = htonl(temp); - memcpy(&xpkt[j], tai_leap.sig, temp); - len += temp + 4; + if (crypto_flags & CRYPTO_FLAG_TAI) + len += crypto_send(fp, &tai_leap); break; -#endif /* PUBKEY */ /* * Default - Fall through for requests; for unknown responses, @@ -1249,626 +1379,2226 @@ crypto_xmit( default: if (opcode & CRYPTO_RESP) opcode |= CRYPTO_ERROR; - break; } /* - * Round up the field length to a multiple of 8 octets and save - * the request code and length. + * We ignore length/format errors and duplicates. Other errors + * are reported to the log and deny further service. To really + * persistent rascals we toss back a kiss-of-death grenade. */ - len = ((len + 7) / 8) * 8; - if (len >= 4) { - xpkt[i] = htonl((u_int32)((opcode << 16) | len)); + if (rval > XEVNT_TSP) { + opcode |= CRYPTO_ERROR; + sprintf(statstr, "error %x opcode %x", rval, opcode); + record_crypto_stats(srcadr_sin, statstr); #ifdef DEBUG if (debug) - printf( - "crypto_xmit: ext offset %d len %d code %x assocID %d\n", - start, len, code, associd); + printf("crypto_xmit: %s\n", statstr); #endif } + + /* + * Round up the field length to a multiple of 8 bytes and save + * the request code and length. + */ + len = ((len + 7) / 8) * 8; + fp->opcode = htonl((opcode & 0xffff0000) | len); +#ifdef DEBUG + if (debug) + printf( + "crypto_xmit: ext offset %d len %u code %x assocID %d\n", + start, len, opcode>> 16, associd); +#endif return (len); } -#ifdef PUBKEY + /* - * crypto_setup - load private key, public key, optional agreement - * parameters and optional leapseconds table, then initialize extension - * fields for later signatures. + * crypto_verify - parse and verify the extension field and value + * + * Returns + * XEVNT_OK success + * XEVNT_LEN bad field format or length + * XEVNT_TSP bad timestamp + * XEVNT_FSP bad filestamp + * XEVNT_PUB bad or missing public key + * XEVNT_SGL bad signature length + * XEVNT_SIG signature not verified */ -void -crypto_setup(void) +static int +crypto_verify( + struct exten *ep, /* extension pointer */ + struct value *vp, /* value pointer */ + struct peer *peer /* peer structure pointer */ + ) { - char filename[MAXFILENAME]; - u_int fstamp; /* filestamp */ - u_int len, temp; - u_int32 *pp; + EVP_PKEY *pkey; /* server public key */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* timestamp */ + tstamp_t fstamp; /* filestamp */ + u_int vallen; /* value length */ + u_int siglen; /* signature length */ + u_int opcode, len; + int rval; + int i; /* - * Initialize structures. + * We require valid opcode and field length, timestamp, + * filestamp, public key, digest, signature length and + * signature, where relevant. Note that preliminary length + * checks are done in the main loop. */ - memset(&private_key, 0, sizeof(private_key)); - memset(&public_key, 0, sizeof(public_key)); - memset(&certif, 0, sizeof(certif)); - memset(&dh_params, 0, sizeof(dh_params)); - memset(&host, 0, sizeof(host)); - memset(&dhparam, 0, sizeof(dhparam)); - memset(&dhpub, 0, sizeof(dhpub)); - memset(&tai_leap, 0, sizeof(tai_leap)); - if (!crypto_flags) - return; + len = ntohl(ep->opcode) & 0x0000ffff; + opcode = ntohl(ep->opcode) & 0xffff0000; + + /* + * Check for valid operation code and protocol. The opcode must + * not have the error bit set. If a response, it must have a + * value header. If a request and does not contain a value + * header, no need for further checking. + */ + if (opcode & CRYPTO_ERROR) + return (XEVNT_LEN); + if (opcode & CRYPTO_RESP) { + if (len < VALUE_LEN) + return (XEVNT_LEN); + } else { + if (len < VALUE_LEN) + return (XEVNT_OK); + } + /* + * We have a value header. Check for valid field lengths. The + * field length must be long enough to contain the value header, + * value and signature. If a request and a previous request of + * the same type is pending, discard the previous request. If a + * request but no signature, there is no need for further + * checking. + */ + vallen = ntohl(ep->vallen); + if (len < ((VALUE_LEN + vallen + 3) / 4) * 4) + return (XEVNT_LEN); + + i = (vallen + 3) / 4; + siglen = ntohl(ep->pkt[i++]); + if (len < VALUE_LEN + vallen + siglen) + return (XEVNT_LEN); + + if (!(opcode & CRYPTO_RESP)) { + if (peer->cmmd != NULL) { + if ((opcode | CRYPTO_RESP) == + (ntohl(peer->cmmd->opcode) & 0xffff0000)) { + free(peer->cmmd); + peer->cmmd = NULL; + } else { + return (XEVNT_LEN); + } + } + if (siglen == 0) + return (XEVNT_OK); + } /* - * Load required private key from file, default "ntpkey". + * We have a signature. Check for valid timestamp and filestamp. + * The timestamp must not precede the filestamp. The timestamp + * and filestamp must not precede the corresponding values in + * the value structure. Once the autokey values have been + * installed, the timestamp must always be later than the + * corresponding value in the value structure. Duplicate + * timestamps are illegal once the cookie has been validated. */ - if (private_key_file == NULL) - private_key_file = "ntpkey"; - host.fstamp = htonl(crypto_rsa(private_key_file, - (u_char *)&private_key, sizeof(R_RSA_PRIVATE_KEY))); + rval = XEVNT_OK; + if (crypto_flags & peer->crypto & CRYPTO_FLAG_PRIV) + pkey = sign_pkey; + else + pkey = peer->pkey; + tstamp = ntohl(ep->tstamp); + fstamp = ntohl(ep->fstamp); + if (tstamp == 0 || tstamp < fstamp) { + rval = XEVNT_TSP; + } else if (vp != NULL && (tstamp < ntohl(vp->tstamp) || + (tstamp == ntohl(vp->tstamp) && (peer->crypto & + CRYPTO_FLAG_AUTO)))) { + rval = XEVNT_TSP; + } else if (vp != NULL && (tstamp < ntohl(vp->fstamp) || fstamp < + ntohl(vp->fstamp))) { + rval = XEVNT_FSP; /* - * Load required public key from file, default - * "ntpkey_host", where "host" is the canonical name of this - * machine. + * If a public key and digest is present, and if valid key + * length, check for valid signature. Note that the first valid + * signature lights the proventic bit. */ - if (public_key_file == NULL) { - snprintf(filename, MAXFILENAME, "ntpkey_%s", - sys_hostname); - public_key_file = emalloc(strlen(filename) + 1); - strcpy(public_key_file, filename); + } else if (pkey == NULL || peer->digest == NULL) { + /* fall through */ + } else if (siglen != (u_int) EVP_PKEY_size(pkey)) { + rval = XEVNT_SGL; + } else { + EVP_VerifyInit(&ctx, peer->digest); + EVP_VerifyUpdate(&ctx, (u_char *)&ep->tstamp, vallen + + 12); + if (EVP_VerifyFinal(&ctx, (u_char *)&ep->pkt[i], siglen, + pkey)) { + if (peer->crypto & CRYPTO_FLAG_VRFY) + peer->crypto |= CRYPTO_FLAG_PROV; + } else { + rval = XEVNT_SIG; + } } - fstamp = htonl(crypto_rsa(public_key_file, - (u_char *)&public_key, sizeof(R_RSA_PUBLIC_KEY))); - if (fstamp != host.fstamp || strstr(public_key_file, - sys_hostname) == NULL) { - msyslog(LOG_ERR, - "crypto: public/private key files mismatch"); - exit (-1); +#ifdef DEBUG + if (debug > 1) + printf( + "crypto_recv: verify %x vallen %u siglen %u ts %u fs %u\n", + rval, vallen, siglen, tstamp, fstamp); +#endif + return (rval); +} + + +/* + * crypto_encrypt - construct encrypted cookie and signature from + * extension field and cookie + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_CKY bad or missing cookie + */ +static int +crypto_encrypt( + struct exten *ep, /* extension pointer */ + struct value *vp, /* value pointer */ + keyid_t *cookie /* server cookie */ + ) +{ + EVP_PKEY *pkey; /* public key */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + u_int32 temp32; + u_int len; + u_char *ptr; + + /* + * Extract the public key from the request. + */ + len = ntohl(ep->vallen); + ptr = (u_char *)ep->pkt; + pkey = d2i_PublicKey(EVP_PKEY_RSA, NULL, &ptr, len); + if (pkey == NULL) { + msyslog(LOG_ERR, "crypto_encrypt %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); } - crypto_flags |= CRYPTO_FLAG_RSA; /* - * Assemble public key and host name in network byte order. - * These data will later be signed and sent in response to - * a client request. Note that the modulus must be a u_int32 in - * network byte order independent of the host order or u_int - * size. + * Encrypt the cookie, encode in ASN.1 and sign. */ - strcpy(filename, sys_hostname); - for (len = strlen(filename) + 1; len % 4 != 0; len++) - filename[len - 1] = 0; - temp = sizeof(R_RSA_PUBLIC_KEY) - sizeof(u_int) + 4; - host.vallen = htonl(temp + len); - pp = emalloc(temp + len); - host.ptr = (u_char *)pp; - *pp++ = htonl(public_key.bits); - memcpy(pp--, public_key.modulus, temp - 4); - pp += temp / 4; - memcpy(pp, filename, len); - host.sig = emalloc(private_key.bits / 8); + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = hostval.tstamp; + len = EVP_PKEY_size(pkey); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + temp32 = htonl(*cookie); + if (!RSA_public_encrypt(4, (u_char *)&temp32, vp->ptr, + pkey->pkey.rsa, RSA_PKCS1_OAEP_PADDING)) { + msyslog(LOG_ERR, "crypto_encrypt %s\n", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_free(pkey); + return (XEVNT_CKY); + } + EVP_PKEY_free(pkey); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_ident - construct extension field for identity scheme + * + * This routine determines which identity scheme is in use and + * constructs an extension field for that scheme. + */ +u_int +crypto_ident( + struct peer *peer /* peer structure pointer */ + ) +{ + char filename[MAXFILENAME + 1]; /* - * Load optional certificate from file, default "ntpkey_certif". - * If the file is missing or defective, the values can later be - * retrieved from a server. + * If the server identity has already been verified, no further + * action is necessary. Otherwise, try to load the identity file + * of the certificate issuer. If the issuer file is not found, + * try the host file. If nothing found, declare a cryptobust. + * Note we can't get here unless the trusted certificate has + * been found and the CRYPTO_FLAG_VALID bit is set, so the + * certificate issuer is valid. */ - if (certif_file == NULL) - snprintf(filename, MAXFILENAME, "ntpkey_certif_%s", + if (peer->crypto & CRYPTO_FLAG_VRFY) + return (0); + + if (peer->ident_pkey != NULL) + EVP_PKEY_free(peer->ident_pkey); + if (peer->crypto & CRYPTO_FLAG_GQ) { + snprintf(filename, MAXFILENAME, "ntpkey_gq_%s", + peer->issuer); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_GQ); + + snprintf(filename, MAXFILENAME, "ntpkey_gq_%s", + sys_hostname); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_GQ); + } + if (peer->crypto & CRYPTO_FLAG_IFF) { + snprintf(filename, MAXFILENAME, "ntpkey_iff_%s", + peer->issuer); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_IFF); + + snprintf(filename, MAXFILENAME, "ntpkey_iff_%s", + sys_hostname); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_IFF); + } + if (peer->crypto & CRYPTO_FLAG_MV) { + snprintf(filename, MAXFILENAME, "ntpkey_mv_%s", + peer->issuer); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_MV); + + snprintf(filename, MAXFILENAME, "ntpkey_mv_%s", sys_hostname); - certif_file = emalloc(strlen(filename) + 1); - strcpy(certif_file, filename); - crypto_cert(certif_file); + peer->ident_pkey = crypto_key(filename, &peer->fstamp); + if (peer->ident_pkey != NULL) + return (CRYPTO_MV); + } /* - * Load optional agreement parameters from file, default - * "ntpkey_dh". If the file is missing or defective, the values - * can later be retrieved from a server. + * No compatible identity scheme is available. Use the default + * TC scheme. */ - if (dh_params_file == NULL) - dh_params_file = "ntpkey_dh"; - crypto_dh(dh_params_file); + msyslog(LOG_INFO, + "crypto_ident: no compatible identity scheme found"); + return (0); +} + + +/* + * crypto_args - construct extension field from arguments + * + * This routine creates an extension field with current timestamps and + * specified opcode, association ID and optional string. Note that the + * extension field is created here, but freed after the crypto_xmit() + * call in the protocol module. + * + * Returns extension field pointer (no errors). + */ +struct exten * +crypto_args( + struct peer *peer, /* peer structure pointer */ + u_int opcode, /* operation code */ + char *str /* argument string */ + ) +{ + tstamp_t tstamp; /* NTP timestamp */ + struct exten *ep; /* extension field pointer */ + u_int len; /* extension field length */ + + tstamp = crypto_time(); + len = sizeof(struct exten); + if (str != NULL) + len += strlen(str); + ep = emalloc(len); + memset(ep, 0, len); + ep->opcode = htonl(opcode + len); /* - * Load optional leapseconds from file, default "ntpkey_leap". - * If the file is missing or defective, the values can later be - * retrieved from a server. + * If a response, send our ID; if a request, send the + * responder's ID. */ - if (tai_leap_file == NULL) - tai_leap_file = "ntpkey_leap"; - crypto_tai(tai_leap_file); + if (opcode & CRYPTO_RESP) + ep->associd = htonl(peer->associd); + else + ep->associd = htonl(peer->assoc); + ep->tstamp = htonl(tstamp); + ep->fstamp = hostval.tstamp; + ep->vallen = 0; + if (str != NULL) { + ep->vallen = htonl(strlen(str)); + memcpy((char *)ep->pkt, str, strlen(str)); + } else { + ep->pkt[0] = peer->associd; + } + return (ep); } /* - * crypto_agree - compute new public value and sign extension fields. + * crypto_send - construct extension field from value components + * + * Returns extension field length. Note: it is not polite to send a + * nonempty signature with zero timestamp or a nonzero timestamp with + * empty signature, but these rules are not enforced here. */ -void -crypto_agree(void) +u_int +crypto_send( + struct exten *ep, /* extension field pointer */ + struct value *vp /* value pointer */ + ) { - R_RANDOM_STRUCT randomstr; /* wiggle bits */ - R_SIGNATURE_CTX ctx; /* signature context */ - l_fp lstamp; /* NTP time */ - tstamp_t tstamp; /* seconds timestamp */ - u_int len, temp; - int rval, i; + u_int len, temp32; + int i; /* - * Sign host name and timestamps, but only if the clock is - * synchronized. + * Copy data. If the data field is empty or zero length, encode + * an empty value with length zero. */ - if (sys_leap == LEAP_NOTINSYNC) - return; - get_systime(&lstamp); - tstamp = lstamp.l_ui; - host.tstamp = htonl(tstamp); - if (!crypto_flags) + ep->tstamp = vp->tstamp; + ep->fstamp = vp->fstamp; + ep->vallen = vp->vallen; + len = 12; + temp32 = ntohl(vp->vallen); + if (temp32 > 0 && vp->ptr != NULL) + memcpy(ep->pkt, vp->ptr, temp32); + + /* + * Copy signature. If the signature field is empty or zero + * length, encode an empty signature with length zero. + */ + i = (temp32 + 3) / 4; + len += i * 4 + 4; + ep->pkt[i++] = vp->siglen; + temp32 = ntohl(vp->siglen); + if (temp32 > 0 && vp->sig != NULL) + memcpy(&ep->pkt[i], vp->sig, temp32); + len += temp32; + return (len); +} + + +/* + * crypto_update - compute new public value and sign extension fields + * + * This routine runs periodically, like once a day, and when something + * changes. It updates the timestamps on three value structures and one + * value structure list, then signs all the structures: + * + * hostval host name (not signed) + * pubkey public key + * cinfo certificate info/value list + * tai_leap leapseconds file + * + * Filestamps are proventicated data, so this routine is run only when + * the host has been synchronized to a proventicated source. Thus, the + * timestamp is proventicated, too, and can be used to deflect + * clogging attacks and even cook breakfast. + * + * Returns void (no errors) + */ +void +crypto_update(void) +{ + EVP_MD_CTX ctx; /* message digest context */ + struct cert_info *cp, *cpn, **zp; /* certificate info/value */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + tstamp_t tstamp; /* NTP timestamp */ + u_int len; + + if ((tstamp = crypto_time()) == 0) return; - EVP_SignInit(&ctx, DA_MD5); - EVP_SignUpdate(&ctx, (u_char *)&host, 12); - EVP_SignUpdate(&ctx, host.ptr, ntohl(host.vallen)); - rval = EVP_SignFinal(&ctx, host.sig, &len, &private_key); - if (rval != RV_OK || len != private_key.bits / 8) { - msyslog(LOG_ERR, "crypto: host signature fails %x", - rval); - exit (-1); - } - host.siglen = ntohl(len); + hostval.tstamp = htonl(tstamp); /* - * Sign certificate and timestamps. + * Sign public key and timestamps. The filestamp is derived from + * the host key file extension from wherever the file was + * generated. */ - if (certif.vallen != 0) { - certif.tstamp = htonl(tstamp); - EVP_SignInit(&ctx, DA_MD5); - EVP_SignUpdate(&ctx, (u_char *)&certif, 12); - EVP_SignUpdate(&ctx, certif.ptr, - ntohl(certif.vallen)); - rval = EVP_SignFinal(&ctx, certif.sig, &len, - &private_key); - if (rval != RV_OK || len != private_key.bits / 8) { - msyslog(LOG_ERR, - "crypto: certificate signature fails %x", - rval); - exit (-1); - } - certif.siglen = ntohl(len); + if (pubkey.vallen != 0) { + pubkey.tstamp = hostval.tstamp; + pubkey.siglen = 0; + if (pubkey.sig == NULL) + pubkey.sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&pubkey, 12); + EVP_SignUpdate(&ctx, pubkey.ptr, ntohl(pubkey.vallen)); + if (EVP_SignFinal(&ctx, pubkey.sig, &len, sign_pkey)) + pubkey.siglen = htonl(len); } /* - * Sign agreement parameters and timestamps. + * Sign certificates and timestamps. The filestamp is derived + * from the certificate file extension from wherever the file + * was generated. At the same time expired certificates are + * expunged. */ - if (dhparam.vallen != 0) { - dhparam.tstamp = htonl(tstamp); - EVP_SignInit(&ctx, DA_MD5); - EVP_SignUpdate(&ctx, (u_char *)&dhparam, 12); - EVP_SignUpdate(&ctx, dhparam.ptr, - ntohl(dhparam.vallen)); - rval = EVP_SignFinal(&ctx, dhparam.sig, &len, - &private_key); - if (rval != RV_OK || len != private_key.bits / 8) { - msyslog(LOG_ERR, - "crypto: parameters signature fails %x", - rval); - exit (-11); - } - dhparam.siglen = ntohl(len); - - /* - * Compute public value. - */ - R_RandomInit(&randomstr); - R_GetRandomBytesNeeded(&len, &randomstr); - for (i = 0; i < len; i++) { - temp = RANDOM; - R_RandomUpdate(&randomstr, (u_char *)&temp, 1); - } - rval = R_SetupDHAgreement(dhpub.ptr, dh_private, - dh_keyLen, &dh_params, &randomstr); - if (rval != RV_OK) { - msyslog(LOG_ERR, - "crypto: invalid public value"); - exit (-1); + zp = &cinfo; + for (cp = cinfo; cp != NULL; cp = cpn) { + cpn = cp->link; + if (tstamp > cp->last) { + *zp = cpn; + cert_free(cp); + } else { + cp->cert.tstamp = hostval.tstamp; + cp->cert.siglen = 0; + if (cp->cert.sig == NULL) + cp->cert.sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&cp->cert, 12); + EVP_SignUpdate(&ctx, cp->cert.ptr, + ntohl(cp->cert.vallen)); + if (EVP_SignFinal(&ctx, cp->cert.sig, &len, + sign_pkey)) + cp->cert.siglen = htonl(len); + zp = &cp->link; } - - /* - * Sign public value and timestamps. - */ - dhpub.tstamp = htonl(tstamp); - EVP_SignInit(&ctx, DA_MD5); - EVP_SignUpdate(&ctx, (u_char *)&dhpub, 12); - EVP_SignUpdate(&ctx, dhpub.ptr, ntohl(dhpub.vallen)); - rval = EVP_SignFinal(&ctx, dhpub.sig, &len, - &private_key); - if (rval != RV_OK || len != private_key.bits / 8) { - msyslog(LOG_ERR, - "crypto: public value signature fails %x", - rval); - exit (-1); - } - dhpub.siglen = ntohl(len); } /* - * Sign leapseconds table and timestamps. + * Sign leapseconds table and timestamps. The filestamp is + * derived from the leapsecond file extension from wherever the + * file was generated. */ if (tai_leap.vallen != 0) { - tai_leap.tstamp = htonl(tstamp); - EVP_SignInit(&ctx, DA_MD5); + tai_leap.tstamp = hostval.tstamp; + tai_leap.siglen = 0; + if (tai_leap.sig == NULL) + tai_leap.sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); EVP_SignUpdate(&ctx, (u_char *)&tai_leap, 12); EVP_SignUpdate(&ctx, tai_leap.ptr, ntohl(tai_leap.vallen)); - rval = EVP_SignFinal(&ctx, tai_leap.sig, &len, - &private_key); - if (rval != RV_OK || len != private_key.bits / 8) { - msyslog(LOG_ERR, - "crypto: leapseconds signature fails %x", - rval); - exit (-1); - } - tai_leap.siglen = ntohl(len); + if (EVP_SignFinal(&ctx, tai_leap.sig, &len, sign_pkey)) + tai_leap.siglen = htonl(len); } + sprintf(statstr, "update ts %u", ntohl(hostval.tstamp)); + record_crypto_stats(NULL, statstr); #ifdef DEBUG if (debug) - printf( - "cypto_agree: ts %u host %u par %u pub %u leap %u\n", - tstamp, ntohl(host.fstamp), ntohl(dhparam.fstamp), - ntohl(dhpub.fstamp), ntohl(tai_leap.fstamp)); + printf("crypto_update: %s\n", statstr); #endif } /* - * crypto_rsa - read RSA key, decode and check for errors. + * value_free - free value structure components. + * + * Returns void (no errors) */ -static u_int -crypto_rsa( - char *cp, /* file name */ - u_char *key, /* key pointer */ - u_int keylen /* key length */ +void +value_free( + struct value *vp /* value structure */ ) { - FILE *str; /* file handle */ - u_char buf[MAX_LINLEN]; /* file line buffer */ - u_char encoded_key[MAX_ENCLEN]; /* encoded key buffer */ - char filename[MAXFILENAME]; /* name of parameter file */ - char linkname[MAXFILENAME]; /* file link (for filestamp) */ - u_int fstamp; /* filestamp */ - u_int bits, len; - char *rptr; - int rval; + if (vp->ptr != NULL) + free(vp->ptr); + if (vp->sig != NULL) + free(vp->sig); + memset(vp, 0, sizeof(struct value)); +} + + +/* + * crypto_time - returns current NTP time in seconds. + */ +tstamp_t +crypto_time() +{ + l_fp tstamp; /* NTP time */ L_CLR(&tstamp); + + L_CLR(&tstamp); + if (sys_leap != LEAP_NOTINSYNC) + get_systime(&tstamp); + return (tstamp.l_ui); +} + + +/* + * asn2ntp - convert ASN1_TIME time structure to NTP time in seconds. + */ +u_long +asn2ntp ( + ASN1_TIME *asn1time /* pointer to ASN1_TIME structure */ + ) +{ + char *v; /* pointer to ASN1_TIME string */ + struct tm tm; /* used to convert to NTP time */ /* - * Open the file and discard comment lines. If the first - * character of the file name is not '/', prepend the keys - * directory string. + * Extract time string YYMMDDHHMMSSZ from ASN1 time structure. + * Note that the YY, MM, DD fields start with one, the HH, MM, + * SS fiels start with zero and the Z character should be 'Z' + * for UTC. Also note that years less than 50 map to years + * greater than 100. Dontcha love ASN.1? Better than MIL-188. */ - if (*cp == '/') - strcpy(filename, cp); - else - snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); - str = fopen(filename, "r"); - if (str == NULL) { - msyslog(LOG_ERR, "crypto: RSA file %s not found", - filename); - exit (-1); + if (asn1time->length > 13) + return ((u_long)(~0)); /* We can't use -1 here. It's invalid */ + v = (char *)asn1time->data; + tm.tm_year = (v[0] - '0') * 10 + v[1] - '0'; + if (tm.tm_year < 50) + tm.tm_year += 100; + tm.tm_mon = (v[2] - '0') * 10 + v[3] - '0' - 1; + tm.tm_mday = (v[4] - '0') * 10 + v[5] - '0'; + tm.tm_hour = (v[6] - '0') * 10 + v[7] - '0'; + tm.tm_min = (v[8] - '0') * 10 + v[9] - '0'; + tm.tm_sec = (v[10] - '0') * 10 + v[11] - '0'; + tm.tm_wday = 0; + tm.tm_yday = 0; + tm.tm_isdst = 0; + return (timegm(&tm) + JAN_1970); +} + + +/* + * bigdig() - compute a BIGNUM MD5 hash of a BIGNUM number. + */ +static int +bighash( + BIGNUM *bn, /* BIGNUM * from */ + BIGNUM *bk /* BIGNUM * to */ + ) +{ + EVP_MD_CTX ctx; /* message digest context */ + u_char dgst[EVP_MAX_MD_SIZE]; /* message digest */ + u_char *ptr; /* a BIGNUM as binary string */ + u_int len; + + len = BN_num_bytes(bn); + ptr = emalloc(len); + BN_bn2bin(bn, ptr); + EVP_DigestInit(&ctx, EVP_md5()); + EVP_DigestUpdate(&ctx, ptr, len); + EVP_DigestFinal(&ctx, dgst, &len); + BN_bin2bn(dgst, len, bk); + return (1); +} + + +/* + *********************************************************************** + * * + * The following routines implement the Schnorr (IFF) identity scheme * + * * + *********************************************************************** + * + * The Schnorr (IFF) identity scheme is intended for use when + * the ntp-genkeys program does not generate the certificates used in + * the protocol and the group key cannot be conveyed in the certificate + * itself. For this purpose, new generations of IFF values must be + * securely transmitted to all members of the group before use. The + * scheme is self contained and independent of new generations of host + * keys, sign keys and certificates. + * + * The IFF identity scheme is based on DSA cryptography and algorithms + * described in Stinson p. 285. The IFF values hide in a DSA cuckoo + * structure, but only the primes and generator are used. The p is a + * 512-bit prime, q a 160-bit prime that divides p - 1 and is a qth root + * of 1 mod p; that is, g^q = 1 mod p. The TA rolls primvate random + * group key b disguised as a DSA structure member, then computes public + * key g^(q - b). These values are shared only among group members and + * never revealed in messages. Alice challenges Bob to confirm identity + * using the protocol described below. + * + * How it works + * + * The scheme goes like this. Both Alice and Bob have the public primes + * p, q and generator g. The TA gives private key b to Bob and public + * key v = g^(q - a) mod p to Alice. + * + * Alice rolls new random challenge r and sends to Bob in the IFF + * request message. Bob rolls new random k, then computes y = k + b r + * mod q and x = g^k mod p and sends (y, hash(x)) to Alice in the + * response message. Besides making the response shorter, the hash makes + * it effectivey impossible for an intruder to solve for b by observing + * a number of these messages. + * + * Alice receives the response and computes g^y v^r mod p. After a bit + * of algebra, this simplifies to g^k. If the hash of this result + * matches hash(x), Alice knows that Bob has the group key b. The signed + * response binds this knowledge to Bob's private key and the public key + * previously received in his certificate. + * + * crypto_alice - construct Alice's challenge in IFF scheme + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_ID bad or missing identity parameters + */ +static int +crypto_alice( + struct peer *peer, /* peer pointer */ + struct value *vp /* value pointer */ + ) +{ + DSA *dsa; /* IFF parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; + u_int len; + + /* + * The identity parameters must have correct format and content. + */ + if (peer->ident_pkey == NULL) + return (XEVNT_ID); + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_alice: defective key"); + return (XEVNT_PUB); } /* - * Ignore initial comments and empty lines. + * Roll new random r (0 < r < q). The OpenSSL library has a bug + * omitting BN_rand_range, so we have to do it the hard way. */ - while ((rptr = fgets(buf, MAX_LINLEN - 1, str)) != NULL) { - len = strlen(buf); - if (len < 1) - continue; - if (*buf == '#' || *buf == '\r' || *buf == '\0') - continue; - break; + bctx = BN_CTX_new(); + len = BN_num_bytes(dsa->q); + if (peer->iffval != NULL) + BN_free(peer->iffval); + peer->iffval = BN_new(); + BN_rand(peer->iffval, len * 8, -1, 1); /* r */ + BN_mod(peer->iffval, peer->iffval, dsa->q, bctx); + BN_CTX_free(bctx); + + /* + * Sign and send to Bob. The filestamp is from the local file. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(peer->fstamp); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + BN_bn2bin(peer->iffval, vp->ptr); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_bob - construct Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + */ +static int +crypto_bob( + struct exten *ep, /* extension pointer */ + struct value *vp /* value pointer */ + ) +{ + DSA *dsa; /* IFF parameters */ + DSA_SIG *sdsa; /* DSA signature context fake */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + BIGNUM *bn, *bk, *r; + u_char *ptr; + u_int len; + + /* + * If the IFF parameters are not valid, something awful + * happened or we are being tormented. + */ + if (!(crypto_flags & CRYPTO_FLAG_IFF)) { + msyslog(LOG_INFO, "crypto_bob: scheme unavailable"); + return (XEVNT_PUB); } + dsa = iffpar_pkey->pkey.dsa; /* - * We are rather paranoid here, since an intruder might 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) - rval = RV_DAT; - else if (sscanf(buf, "%d %s", &bits, encoded_key) != 2) - rval = RV_DAT; - else if (R_DecodePEMBlock(&buf[sizeof(u_int)], &len, - encoded_key, strlen(encoded_key))) - rval = RV_DEC; - else if ((len += sizeof(u_int)) != keylen) - rval = RV_KEY; - else if (bits < MIN_RSA_MODULUS_BITS || bits > - MAX_RSA_MODULUS_BITS) - rval = RV_KEY; + * Extract r from the challenge. + */ + len = ntohl(ep->vallen); + if ((r = BN_bin2bn((u_char *)ep->pkt, len, NULL)) == NULL) { + msyslog(LOG_ERR, "crypto_bob %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Bob rolls random k (0 < k < q), computes y = k + b r mod q + * and x = g^k mod p, then sends (y, hash(x)) to Alice. + */ + bctx = BN_CTX_new(); bk = BN_new(); bn = BN_new(); + sdsa = DSA_SIG_new(); + BN_rand(bk, len * 8, -1, 1); /* k */ + BN_mod_mul(bn, dsa->priv_key, r, dsa->q, bctx); /* b r mod q */ + BN_add(bn, bn, bk); + BN_mod(bn, bn, dsa->q, bctx); /* k + b r mod q */ + sdsa->r = BN_dup(bn); + BN_mod_exp(bk, dsa->g, bk, dsa->p, bctx); /* g^k mod p */ + bighash(bk, bk); + sdsa->s = BN_dup(bk); + BN_CTX_free(bctx); + BN_free(r); BN_free(bn); BN_free(bk); + + /* + * Encode the values in ASN.1 and sign. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(if_fstamp); + len = i2d_DSA_SIG(sdsa, NULL); + if (len <= 0) { + msyslog(LOG_ERR, "crypto_bob %s\n", + ERR_error_string(ERR_get_error(), NULL)); + DSA_SIG_free(sdsa); + return (XEVNT_PUB); + } + vp->vallen = htonl(len); + ptr = emalloc(len); + vp->ptr = ptr; + i2d_DSA_SIG(sdsa, &ptr); + DSA_SIG_free(sdsa); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_iff - verify Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_FSP bad filestamp + * XEVNT_ID bad or missing identity parameters + */ +int +crypto_iff( + struct exten *ep, /* extension pointer */ + struct peer *peer /* peer structure pointer */ + ) +{ + DSA *dsa; /* IFF parameters */ + BN_CTX *bctx; /* BIGNUM context */ + DSA_SIG *sdsa; /* DSA parameters */ + BIGNUM *bn, *bk; + u_int len; + const u_char *ptr; + int temp; + + /* + * If the IFF parameters are not valid or no challenge was sent, + * something awful happened or we are being tormented. + */ + if (peer->ident_pkey == NULL) { + msyslog(LOG_INFO, "crypto_iff: scheme unavailable"); + return (XEVNT_PUB); + } + if (ntohl(ep->fstamp) != peer->fstamp) { + msyslog(LOG_INFO, "crypto_iff: invalid filestamp %u", + ntohl(ep->fstamp)); + return (XEVNT_FSP); + } + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_iff: defective key"); + return (XEVNT_PUB); + } + if (peer->iffval == NULL) { + msyslog(LOG_INFO, "crypto_iff: missing challenge"); + return (XEVNT_PUB); + } + + /* + * Extract the k + b r and g^k values from the response. + */ + bctx = BN_CTX_new(); bk = BN_new(); bn = BN_new(); + len = ntohl(ep->vallen); + ptr = (const u_char *)ep->pkt; + if ((sdsa = d2i_DSA_SIG(NULL, &ptr, len)) == NULL) { + msyslog(LOG_ERR, "crypto_iff %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Compute g^(k + b r) g^(q - b)r mod p. + */ + BN_mod_exp(bn, dsa->pub_key, peer->iffval, dsa->p, bctx); + BN_mod_exp(bk, dsa->g, sdsa->r, dsa->p, bctx); + BN_mod_mul(bn, bn, bk, dsa->p, bctx); + + /* + * Verify the hash of the result matches hash(x). + */ + bighash(bn, bn); + temp = BN_cmp(bn, sdsa->s); + BN_free(bn); BN_free(bk); BN_CTX_free(bctx); + BN_free(peer->iffval); + peer->iffval = NULL; + DSA_SIG_free(sdsa); + if (temp == 0) + return (XEVNT_OK); else - rval = RV_OK; - if (rval != RV_OK) { - fclose(str); - msyslog(LOG_ERR, "crypto: RSA file %s error %x", cp, - rval); - exit (-1); + return (XEVNT_ID); +} + + +/* + *********************************************************************** + * * + * The following routines implement the Guillou-Quisquater (GQ) * + * identity scheme * + * * + *********************************************************************** + * + * The Guillou-Quisquater (GQ) identity scheme is intended for use when + * the ntp-genkeys program generates the certificates used in the + * protocol and the group key can be conveyed in a certificate extension + * field. The scheme is self contained and independent of new + * generations of host keys, sign keys and certificates. + * + * The GQ identity scheme is based on RSA cryptography and algorithms + * described in Stinson p. 300 (with errors). The GQ values hide in a + * RSA cuckoo structure, but only the modulus is used. The 512-bit + * public modulus is n = p q, where p and q are secret large primes. The + * TA rolls random group key b disguised as a RSA structure member. + * Except for the public key, these values are shared only among group + * members and never revealed in messages. + * + * When rolling new certificates, Bob recomputes the private and + * public keys. The private key u is a random roll, while the public key + * is the inverse obscured by the group key v = (u^-1)^b. These values + * replace the private and public keys normally generated by the RSA + * scheme. Alice challenges Bob to confirm identity using the protocol + * described below. + * + * How it works + * + * The scheme goes like this. Both Alice and Bob have the same modulus n + * and some random b as the group key. These values are computed and + * distributed in advance via secret means, although only the group key + * b is truly secret. Each has a private random private key u and public + * key (u^-1)^b, although not necessarily the same ones. Bob and Alice + * can regenerate the key pair from time to time without affecting + * operations. The public key is conveyed on the certificate in an + * extension field; the private key is never revealed. + * + * Alice rolls new random challenge r and sends to Bob in the GQ + * request message. Bob rolls new random k, then computes y = k u^r mod + * n and x = k^b mod n and sends (y, hash(x)) to Alice in the response + * message. Besides making the response shorter, the hash makes it + * effectivey impossible for an intruder to solve for b by observing + * a number of these messages. + * + * Alice receives the response and computes y^b v^r mod n. After a bit + * of algebra, this simplifies to k^b. If the hash of this result + * matches hash(x), Alice knows that Bob has the group key b. The signed + * response binds this knowledge to Bob's private key and the public key + * previously received in his certificate. + * + * crypto_alice2 - construct Alice's challenge in GQ scheme + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_ID bad or missing identity parameters + */ +static int +crypto_alice2( + struct peer *peer, /* peer pointer */ + struct value *vp /* value pointer */ + ) +{ + RSA *rsa; /* GQ parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; + u_int len; + + /* + * The identity parameters must have correct format and content. + */ + if (peer->ident_pkey == NULL) + return (XEVNT_ID); + if ((rsa = peer->ident_pkey->pkey.rsa) == NULL) { + msyslog(LOG_INFO, "crypto_alice2: defective key"); + return (XEVNT_PUB); } - fclose(str); - *(u_int *)buf = bits; - memcpy(key, buf, keylen); /* - * Extract filestamp if present. + * Roll new random r (0 < r < n). The OpenSSL library has a bug + * omitting BN_rand_range, so we have to do it the hard way. */ - rval = readlink(filename, linkname, MAXFILENAME - 1); - if (rval > 0) { - linkname[rval] = '\0'; - rptr = strrchr(linkname, '.'); - } else { - rptr = strrchr(filename, '.'); + bctx = BN_CTX_new(); + len = BN_num_bytes(rsa->n); + if (peer->iffval != NULL) + BN_free(peer->iffval); + peer->iffval = BN_new(); + BN_rand(peer->iffval, len * 8, -1, 1); /* r mod n */ + BN_mod(peer->iffval, peer->iffval, rsa->n, bctx); + BN_CTX_free(bctx); + + /* + * Sign and send to Bob. The filestamp is from the local file. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(peer->fstamp); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + BN_bn2bin(peer->iffval, vp->ptr); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_bob2 - construct Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + */ +static int +crypto_bob2( + struct exten *ep, /* extension pointer */ + struct value *vp /* value pointer */ + ) +{ + RSA *rsa; /* GQ parameters */ + DSA_SIG *sdsa; /* DSA parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + BIGNUM *r, *k, *g, *y; + u_char *ptr; + u_int len; + + /* + * If the GQ parameters are not valid, something awful + * happened or we are being tormented. + */ + if (!(crypto_flags & CRYPTO_FLAG_GQ)) { + msyslog(LOG_INFO, "crypto_bob2: scheme unavailable"); + return (XEVNT_PUB); + } + rsa = gqpar_pkey->pkey.rsa; + + /* + * Extract r from the challenge. + */ + len = ntohl(ep->vallen); + if ((r = BN_bin2bn((u_char *)ep->pkt, len, NULL)) == NULL) { + msyslog(LOG_ERR, "crypto_bob2 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Bob rolls random k (0 < k < n), computes y = k u^r mod n and + * x = k^b mod n, then sends (y, hash(x)) to Alice. + */ + bctx = BN_CTX_new(); k = BN_new(); g = BN_new(); y = BN_new(); + sdsa = DSA_SIG_new(); + BN_rand(k, len * 8, -1, 1); /* k */ + BN_mod(k, k, rsa->n, bctx); + BN_mod_exp(y, rsa->p, r, rsa->n, bctx); /* u^r mod n */ + BN_mod_mul(y, k, y, rsa->n, bctx); /* k u^r mod n */ + sdsa->r = BN_dup(y); + BN_mod_exp(g, k, rsa->e, rsa->n, bctx); /* k^b mod n */ + bighash(g, g); + sdsa->s = BN_dup(g); + BN_CTX_free(bctx); + BN_free(r); BN_free(k); BN_free(g); BN_free(y); + + /* + * Encode the values in ASN.1 and sign. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(gq_fstamp); + len = i2d_DSA_SIG(sdsa, NULL); + if (len <= 0) { + msyslog(LOG_ERR, "crypto_bob2 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + DSA_SIG_free(sdsa); + return (XEVNT_PUB); + } + vp->vallen = htonl(len); + ptr = emalloc(len); + vp->ptr = ptr; + i2d_DSA_SIG(sdsa, &ptr); + DSA_SIG_free(sdsa); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_gq - verify Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_FSP bad filestamp + * XEVNT_ID bad or missing identity parameters + */ +int +crypto_gq( + struct exten *ep, /* extension pointer */ + struct peer *peer /* peer structure pointer */ + ) +{ + RSA *rsa; /* GQ parameters */ + BN_CTX *bctx; /* BIGNUM context */ + DSA_SIG *sdsa; /* RSA signature context fake */ + BIGNUM *y, *v; + const u_char *ptr; + u_int len; + int temp; + + /* + * If the GQ parameters are not valid or no challenge was sent, + * something awful happened or we are being tormented. + */ + if (peer->ident_pkey == NULL) { + msyslog(LOG_INFO, "crypto_gq: scheme unavailable"); + return (XEVNT_PUB); + } + if (ntohl(ep->fstamp) != peer->fstamp) { + msyslog(LOG_INFO, "crypto_gq: invalid filestamp %u", + ntohl(ep->fstamp)); + return (XEVNT_FSP); + } + if ((rsa = peer->ident_pkey->pkey.rsa) == NULL) { + msyslog(LOG_INFO, "crypto_gq: defective key"); + return (XEVNT_PUB); + } + if (peer->iffval == NULL) { + msyslog(LOG_INFO, "crypto_gq: missing challenge"); + return (XEVNT_PUB); + } + + /* + * Extract the y = k u^r and hash(x = k^b) values from the + * response. + */ + bctx = BN_CTX_new(); y = BN_new(); v = BN_new(); + len = ntohl(ep->vallen); + ptr = (const u_char *)ep->pkt; + if ((sdsa = d2i_DSA_SIG(NULL, &ptr, len)) == NULL) { + msyslog(LOG_ERR, "crypto_gq %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); } - if (rptr != NULL) - sscanf(++rptr, "%u", &fstamp); + + /* + * Compute v^r y^b mod n. + */ + BN_mod_exp(v, peer->grpkey, peer->iffval, rsa->n, bctx); + /* v^r mod n */ + BN_mod_exp(y, sdsa->r, rsa->e, rsa->n, bctx); /* y^b mod n */ + BN_mod_mul(y, v, y, rsa->n, bctx); /* v^r y^b mod n */ + + /* + * Verify the hash of the result matches hash(x). + */ + bighash(y, y); + temp = BN_cmp(y, sdsa->s); + BN_CTX_free(bctx); BN_free(y); BN_free(v); + BN_free(peer->iffval); + peer->iffval = NULL; + DSA_SIG_free(sdsa); + if (temp == 0) + return (XEVNT_OK); else - fstamp = 0; -#ifdef DEBUG - if (debug) - printf( - "crypto_rsa: key file %s link %d fs %u modulus %d\n", - cp, rval, fstamp, bits); -#endif - return (fstamp); + return (XEVNT_ID); } /* - * crypto_cert - read certificate + *********************************************************************** + * * + * The following routines implement the Mu-Varadharajan (MV) identity * + * scheme * + * * + *********************************************************************** */ -static void -crypto_cert( - char *cp /* file name */ +/* + * The Mu-Varadharajan (MV) cryptosystem was originally intended when + * servers broadcast messages to clients, but clients never send + * messages to servers. There is one encryption key for the server and a + * separate decryption key for each client. It operated something like a + * pay-per-view satellite broadcasting system where the session key is + * encrypted by the broadcaster and the decryption keys are held in a + * tamperproof set-top box. + * + * The MV parameters and private encryption key hide in a DSA cuckoo + * structure which uses the same parameters, but generated in a + * different way. The values are used in an encryption scheme similar to + * El Gamal cryptography and a polynomial formed from the expansion of + * product terms (x - x[j]), as described in Mu, Y., and V. + * Varadharajan: Robust and Secure Broadcasting, Proc. Indocrypt 2001, + * 223-231. The paper has significant errors and serious omissions. + * + * Let q be the product of n distinct primes s'[j] (j = 1...n), where + * each s'[j] has m significant bits. Let p be a prime p = 2 * q + 1, so + * that q and each s'[j] divide p - 1 and p has M = n * m + 1 + * significant bits. The elements x mod q of Zq with the elements 2 and + * the primes removed form a field Zq* valid for polynomial arithetic. + * Let g be a generator of Zp; that is, gcd(g, p - 1) = 1 and g^q = 1 + * mod p. We expect M to be in the 500-bit range and n relatively small, + * like 25, so the likelihood of a randomly generated element of x mod q + * of Zq colliding with a factor of p - 1 is very small and can be + * avoided. Associated with each s'[j] is an element s[j] such that s[j] + * s'[j] = s'[j] mod q. We find s[j] as the quotient (q + s'[j]) / + * s'[j]. These are the parameters of the scheme and they are expensive + * to compute. + * + * We set up an instance of the scheme as follows. A set of random + * values x[j] mod q (j = 1...n), are generated as the zeros of a + * polynomial of order n. The product terms (x - x[j]) are expanded to + * form coefficients a[i] mod q (i = 0...n) in powers of x. These are + * used as exponents of the generator g mod p to generate the private + * encryption key A. The pair (gbar, ghat) of public server keys and the + * pairs (xbar[j], xhat[j]) (j = 1...n) of private client keys are used + * to construct the decryption keys. The devil is in the details. + * + * The distinguishing characteristic of this scheme is the capability to + * revoke keys. Included in the calculation of E, gbar and ghat is the + * product s = prod(s'[j]) (j = 1...n) above. If the factor s'[j] is + * subsequently removed from the product and E, gbar and ghat + * recomputed, the jth client will no longer be able to compute E^-1 and + * thus unable to decrypt the block. + * + * How it works + * + * The scheme goes like this. Bob has the server values (p, A, q, gbar, + * ghat) and Alice the client values (p, xbar, xhat). + * + * Alice rolls new random challenge r (0 < r < p) and sends to Bob in + * the MV request message. Bob rolls new random k (0 < k < q), encrypts + * y = A^k mod p (a permutation) and sends (hash(y), gbar^k, ghat^k) to + * Alice. + * + * Alice receives the response and computes the decryption key (the + * inverse permutation) from previously obtained (xbar, xhat) and + * (gbar^k, ghat^k) in the message. She computes the inverse, which is + * unique by reasons explained in the ntp-keygen.c program sources. If + * the hash of this result matches hash(y), Alice knows that Bob has the + * group key b. The signed response binds this knowledge to Bob's + * private key and the public key previously received in his + * certificate. + * + * crypto_alice3 - construct Alice's challenge in MV scheme + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_ID bad or missing identity parameters + */ +static int +crypto_alice3( + struct peer *peer, /* peer pointer */ + struct value *vp /* value pointer */ ) { - u_char buf[5000]; /* file line buffer */ - char filename[MAXFILENAME]; /* name of certificate file */ - char linkname[MAXFILENAME]; /* file link (for filestamp) */ - u_int fstamp; /* filestamp */ - u_int32 *pp; - u_int len; - char *rptr; - int rval, fd; + DSA *dsa; /* MV parameters */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; + u_int len; /* - * Open the file and discard comment lines. If the first - * character of the file name is not '/', prepend the keys - * directory string. If the file is not found, not to worry; it - * can be retrieved over the net. But, if it is found with - * errors, we crash and burn. + * The identity parameters must have correct format and content. */ - if (*cp == '/') - strcpy(filename, cp); + if (peer->ident_pkey == NULL) + return (XEVNT_ID); + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_alice3: defective key"); + return (XEVNT_PUB); + } + + /* + * Roll new random r (0 < r < q). The OpenSSL library has a bug + * omitting BN_rand_range, so we have to do it the hard way. + */ + bctx = BN_CTX_new(); + len = BN_num_bytes(dsa->p); + if (peer->iffval != NULL) + BN_free(peer->iffval); + peer->iffval = BN_new(); + BN_rand(peer->iffval, len * 8, -1, 1); /* r */ + BN_mod(peer->iffval, peer->iffval, dsa->p, bctx); + BN_CTX_free(bctx); + + /* + * Sign and send to Bob. The filestamp is from the local file. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(peer->fstamp); + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + BN_bn2bin(peer->iffval, vp->ptr); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_bob3 - construct Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + */ +static int +crypto_bob3( + struct exten *ep, /* extension pointer */ + struct value *vp /* value pointer */ + ) +{ + DSA *dsa; /* MV parameters */ + DSA *sdsa; /* DSA signature context fake */ + BN_CTX *bctx; /* BIGNUM context */ + EVP_MD_CTX ctx; /* signature context */ + tstamp_t tstamp; /* NTP timestamp */ + BIGNUM *r, *k, *u; + u_char *ptr; + u_int len; + + /* + * If the MV parameters are not valid, something awful + * happened or we are being tormented. + */ + if (!(crypto_flags & CRYPTO_FLAG_MV)) { + msyslog(LOG_INFO, "crypto_bob3: scheme unavailable"); + return (XEVNT_PUB); + } + dsa = mvpar_pkey->pkey.dsa; + + /* + * Extract r from the challenge. + */ + len = ntohl(ep->vallen); + if ((r = BN_bin2bn((u_char *)ep->pkt, len, NULL)) == NULL) { + msyslog(LOG_ERR, "crypto_bob3 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Bob rolls random k (0 < k < q), making sure it is not a + * factor of q. He then computes y = A^k r and sends (hash(y), + * gbar^k, ghat^k) to Alice. + */ + bctx = BN_CTX_new(); k = BN_new(); u = BN_new(); + sdsa = DSA_new(); + sdsa->p = BN_new(); sdsa->q = BN_new(); sdsa->g = BN_new(); + while (1) { + BN_rand(k, BN_num_bits(dsa->q), 0, 0); + BN_mod(k, k, dsa->q, bctx); + BN_gcd(u, k, dsa->q, bctx); + if (BN_is_one(u)) + break; + } + BN_mod_exp(u, dsa->g, k, dsa->p, bctx); /* A r */ + BN_mod_mul(u, u, r, dsa->p, bctx); + bighash(u, sdsa->p); + BN_mod_exp(sdsa->q, dsa->priv_key, k, dsa->p, bctx); /* gbar */ + BN_mod_exp(sdsa->g, dsa->pub_key, k, dsa->p, bctx); /* ghat */ + BN_CTX_free(bctx); BN_free(k); BN_free(r); BN_free(u); + + /* + * Encode the values in ASN.1 and sign. + */ + tstamp = crypto_time(); + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = htonl(mv_fstamp); + len = i2d_DSAparams(sdsa, NULL); + if (len <= 0) { + msyslog(LOG_ERR, "crypto_bob3 %s\n", + ERR_error_string(ERR_get_error(), NULL)); + DSA_free(sdsa); + return (XEVNT_PUB); + } + vp->vallen = htonl(len); + ptr = emalloc(len); + vp->ptr = ptr; + i2d_DSAparams(sdsa, &ptr); + DSA_free(sdsa); + vp->siglen = 0; + if (tstamp == 0) + return (XEVNT_OK); + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)&vp->tstamp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); + return (XEVNT_OK); +} + + +/* + * crypto_mv - verify Bob's response to Alice's challenge + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_FSP bad filestamp + * XEVNT_ID bad or missing identity parameters + */ +int +crypto_mv( + struct exten *ep, /* extension pointer */ + struct peer *peer /* peer structure pointer */ + ) +{ + DSA *dsa; /* MV parameters */ + DSA *sdsa; /* DSA parameters */ + BN_CTX *bctx; /* BIGNUM context */ + BIGNUM *k, *u, *v; + u_int len; + const u_char *ptr; + int temp; + + /* + * If the MV parameters are not valid or no challenge was sent, + * something awful happened or we are being tormented. + */ + if (peer->ident_pkey == NULL) { + msyslog(LOG_INFO, "crypto_mv: scheme unavailable"); + return (XEVNT_PUB); + } + if (ntohl(ep->fstamp) != peer->fstamp) { + msyslog(LOG_INFO, "crypto_mv: invalid filestamp %u", + ntohl(ep->fstamp)); + return (XEVNT_FSP); + } + if ((dsa = peer->ident_pkey->pkey.dsa) == NULL) { + msyslog(LOG_INFO, "crypto_mv: defective key"); + return (XEVNT_PUB); + } + if (peer->iffval == NULL) { + msyslog(LOG_INFO, "crypto_mv: missing challenge"); + return (XEVNT_PUB); + } + + /* + * Extract the (hash(y), gbar, ghat) values from the response. + */ + bctx = BN_CTX_new(); k = BN_new(); u = BN_new(); v = BN_new(); + len = ntohl(ep->vallen); + ptr = (const u_char *)ep->pkt; + if ((sdsa = d2i_DSAparams(NULL, &ptr, len)) == NULL) { + msyslog(LOG_ERR, "crypto_mv %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_PUB); + } + + /* + * Compute (gbar^xhat ghat^xbar)^-1 mod p. + */ + BN_mod_exp(u, sdsa->q, dsa->pub_key, dsa->p, bctx); + BN_mod_exp(v, sdsa->g, dsa->priv_key, dsa->p, bctx); + BN_mod_mul(u, u, v, dsa->p, bctx); + BN_mod_inverse(u, u, dsa->p, bctx); + BN_mod_mul(v, u, peer->iffval, dsa->p, bctx); + + /* + * The result should match the hash of r mod p. + */ + bighash(v, v); + temp = BN_cmp(v, sdsa->p); + BN_CTX_free(bctx); BN_free(k); BN_free(u); BN_free(v); + BN_free(peer->iffval); + peer->iffval = NULL; + DSA_free(sdsa); + if (temp == 0) + return (XEVNT_OK); else - snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); - fd = open(filename, O_RDONLY, 0777); - if (fd <= 0) { + return (XEVNT_ID); +} + + +/* + *********************************************************************** + * * + * The following routines are used to manipulate certificates * + * * + *********************************************************************** + */ +/* + * cert_parse - parse x509 certificate and create info/value structures. + * + * The server certificate includes the version number, issuer name, + * subject name, public key and valid date interval. If the issuer name + * is the same as the subject name, the certificate is self signed and + * valid only if the server is configured as trustable. If the names are + * different, another issuer has signed the server certificate and + * vouched for it. In this case the server certificate is valid if + * verified by the issuer public key. + * + * Returns certificate info/value pointer if valid, NULL if not. + */ +struct cert_info * /* certificate information structure */ +cert_parse( + u_char *asn1cert, /* X509 certificate */ + u_int len, /* certificate length */ + tstamp_t fstamp /* filestamp */ + ) +{ + X509 *cert; /* X509 certificate */ + X509_EXTENSION *ext; /* X509v3 extension */ + struct cert_info *ret; /* certificate info/value */ + BIO *bp; + X509V3_EXT_METHOD *method; + char pathbuf[MAXFILENAME]; + u_char *uptr; + char *ptr; + int temp, cnt, i; + + /* + * Decode ASN.1 objects and construct certificate structure. + */ + uptr = asn1cert; + if ((cert = d2i_X509(NULL, &uptr, len)) == NULL) { + msyslog(LOG_ERR, "cert_parse %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (NULL); + } + + /* + * Extract version, subject name and public key. + */ + ret = emalloc(sizeof(struct cert_info)); + memset(ret, 0, sizeof(struct cert_info)); + if ((ret->pkey = X509_get_pubkey(cert)) == NULL) { + msyslog(LOG_ERR, "cert_parse %s\n", + ERR_error_string(ERR_get_error(), NULL)); + cert_free(ret); + X509_free(cert); + return (NULL); + } + ret->version = X509_get_version(cert); + X509_NAME_oneline(X509_get_subject_name(cert), pathbuf, + MAXFILENAME - 1); + ptr = strstr(pathbuf, "CN="); + if (ptr == NULL) { + msyslog(LOG_INFO, "cert_parse: invalid subject %s", + pathbuf); + cert_free(ret); + X509_free(cert); + return (NULL); + } + ret->subject = emalloc(strlen(ptr) + 1); + strcpy(ret->subject, ptr + 3); + + /* + * Extract remaining objects. Note that the NTP serial number is + * the NTP seconds at the time of signing, but this might not be + * the case for other authority. We don't bother to check the + * objects at this time, since the real crunch can happen only + * when the time is valid but not yet certificated. + */ + ret->nid = OBJ_obj2nid(cert->cert_info->signature->algorithm); + ret->digest = (const EVP_MD *)EVP_get_digestbynid(ret->nid); + ret->serial = + (u_long)ASN1_INTEGER_get(X509_get_serialNumber(cert)); + X509_NAME_oneline(X509_get_issuer_name(cert), pathbuf, + MAXFILENAME); + if ((ptr = strstr(pathbuf, "CN=")) == NULL) { + msyslog(LOG_INFO, "cert_parse: invalid issuer %s", + pathbuf); + cert_free(ret); + X509_free(cert); + return (NULL); + } + ret->issuer = emalloc(strlen(ptr) + 1); + strcpy(ret->issuer, ptr + 3); + ret->first = asn2ntp(X509_get_notBefore(cert)); + ret->last = asn2ntp(X509_get_notAfter(cert)); + + /* + * Extract extension fields. These are ad hoc ripoffs of + * currently assigned functions and will certainly be changed + * before prime time. + */ + cnt = X509_get_ext_count(cert); + for (i = 0; i < cnt; i++) { + ext = X509_get_ext(cert, i); + method = X509V3_EXT_get(ext); + temp = OBJ_obj2nid(ext->object); + switch (temp) { + + /* + * If a key_usage field is present, we decode whether + * this is a trusted or private certificate. This is + * dorky; all we want is to compare NIDs, but OpenSSL + * insists on BIO text strings. + */ + case NID_ext_key_usage: + bp = BIO_new(BIO_s_mem()); + X509V3_EXT_print(bp, ext, 0, 0); + BIO_gets(bp, pathbuf, MAXFILENAME); + BIO_free(bp); +#if DEBUG + if (debug) + printf("cert_parse: %s: %s\n", + OBJ_nid2ln(temp), pathbuf); +#endif + if (strcmp(pathbuf, "Trust Root") == 0) + ret->flags |= CERT_TRUST; + else if (strcmp(pathbuf, "Private") == 0) + ret->flags |= CERT_PRIV; + break; + + /* + * If a NID_subject_key_identifier field is present, it + * contains the GQ public key. + */ + case NID_subject_key_identifier: + ret->grplen = ext->value->length - 2; + ret->grpkey = emalloc(ret->grplen); + memcpy(ret->grpkey, &ext->value->data[2], + ret->grplen); + break; + } + } + + /* + * If certificate is self signed, verify signature. + */ + if (strcmp(ret->subject, ret->issuer) == 0) { + if (!X509_verify(cert, ret->pkey)) { + msyslog(LOG_INFO, + "cert_parse: invalid signature not verified %s", + pathbuf); + cert_free(ret); + X509_free(cert); + return (NULL); + } + } + + /* + * Verify certificate valid times. Note that certificates cannot + * be retroactive. + */ + if (ret->first > ret->last || ret->first < fstamp) { msyslog(LOG_INFO, - "crypto: certificate file %s not found", - filename); - return; + "cert_parse: expired %s", + ret->subject); + cert_free(ret); + X509_free(cert); + return (NULL); } /* - * We are rather paranoid here, since an intruder might cause a - * coredump by infiltrating naughty values. + * Build the value structure to sign and send later. */ - rval = RV_OK; - len = read(fd, buf, 5000); - close(fd); - if (rval != RV_OK) { - msyslog(LOG_ERR, - "crypto: certificate file %s error %d", cp, - rval); - exit (-1); + ret->cert.fstamp = htonl(fstamp); + ret->cert.vallen = htonl(len); + ret->cert.ptr = emalloc(len); + memcpy(ret->cert.ptr, asn1cert, len); +#ifdef DEBUG + if (debug > 1) + X509_print_fp(stdout, cert); +#endif + X509_free(cert); + return (ret); +} + + +/* + * cert_sign - sign x509 certificate and update value structure. + * + * The certificate request is a copy of the client certificate, which + * includes the version number, subject name and public key of the + * client. The resulting certificate includes these values plus the + * serial number, issuer name and validity interval of the server. The + * validity interval extends from the current time to the same time one + * year hence. For NTP purposes, it is convenient to use the NTP seconds + * of the current time as the serial number. + * + * Returns + * XEVNT_OK success + * XEVNT_PUB bad or missing public key + * XEVNT_CRT bad or missing certificate + * XEVNT_VFY certificate not verified + */ +static int +cert_sign( + struct exten *ep, /* extension field pointer */ + struct value *vp /* value pointer */ + ) +{ + X509 *req; /* X509 certificate request */ + X509 *cert; /* X509 certificate */ + X509_EXTENSION *ext; /* certificate extension */ + ASN1_INTEGER *serial; /* serial number */ + X509_NAME *subj; /* distinguished (common) name */ + EVP_PKEY *pkey; /* public key */ + EVP_MD_CTX ctx; /* message digest context */ + tstamp_t tstamp; /* NTP timestamp */ + u_int len; + u_char *ptr; + int i, temp; + + /* + * Decode ASN.1 objects and construct certificate structure. + */ + tstamp = crypto_time(); + if (tstamp == 0) + return (XEVNT_TSP); + + ptr = (u_char *)ep->pkt; + if ((req = d2i_X509(NULL, &ptr, ntohl(ep->vallen))) == NULL) { + msyslog(LOG_ERR, "cert_sign %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (XEVNT_CRT); + } + /* + * Extract public key and check for errors. + */ + if ((pkey = X509_get_pubkey(req)) == NULL) { + msyslog(LOG_ERR, "cert_sign %s\n", + ERR_error_string(ERR_get_error(), NULL)); + X509_free(req); + return (XEVNT_PUB); } /* - * The extension field entry consists of the raw certificate. + * Generate X509 certificate signed by this server. For this + * prupose the issuer name is the server name. Also copy any + * extensions that might be present. */ - certif.vallen = htonl(200); /* xxxxxxxxxxxxxxxxxx */ - pp = emalloc(len); - certif.ptr = (u_char *)pp; - memcpy(pp, buf, len); - certif.sig = emalloc(private_key.bits / 8); - crypto_flags |= CRYPTO_FLAG_CERT; + cert = X509_new(); + X509_set_version(cert, X509_get_version(req)); + serial = ASN1_INTEGER_new(); + ASN1_INTEGER_set(serial, tstamp); + X509_set_serialNumber(cert, serial); + X509_gmtime_adj(X509_get_notBefore(cert), 0L); + X509_gmtime_adj(X509_get_notAfter(cert), YEAR); + subj = X509_get_issuer_name(cert); + X509_NAME_add_entry_by_txt(subj, "commonName", MBSTRING_ASC, + (unsigned char *) sys_hostname, strlen(sys_hostname), -1, 0); + subj = X509_get_subject_name(req); + X509_set_subject_name(cert, subj); + X509_set_pubkey(cert, pkey); + ext = X509_get_ext(req, 0); + temp = X509_get_ext_count(req); + for (i = 0; i < temp; i++) { + ext = X509_get_ext(req, i); + X509_add_ext(cert, ext, -1); + } + X509_free(req); /* - * Extract filestamp if present. + * Sign and verify the certificate. */ - rval = readlink(filename, linkname, MAXFILENAME - 1); - if (rval > 0) { - linkname[rval] = '\0'; - rptr = strrchr(linkname, '.'); - } else { - rptr = strrchr(filename, '.'); + X509_sign(cert, sign_pkey, sign_digest); + if (!X509_verify(cert, sign_pkey)) { + printf("cert_sign\n%s\n", + ERR_error_string(ERR_get_error(), NULL)); + X509_free(cert); + return (XEVNT_VFY); } - if (rptr != NULL) - sscanf(++rptr, "%u", &fstamp); - else - fstamp = 0; - certif.fstamp = htonl(fstamp); + len = i2d_X509(cert, NULL); + + /* + * Build and sign the value structure. We have to sign it here, + * since the response has to be returned right away. This is a + * clogging hazard. + */ + memset(vp, 0, sizeof(struct value)); + vp->tstamp = htonl(tstamp); + vp->fstamp = ep->fstamp; + vp->vallen = htonl(len); + vp->ptr = emalloc(len); + ptr = vp->ptr; + i2d_X509(cert, &ptr); + vp->siglen = 0; + vp->sig = emalloc(sign_siglen); + EVP_SignInit(&ctx, sign_digest); + EVP_SignUpdate(&ctx, (u_char *)vp, 12); + EVP_SignUpdate(&ctx, vp->ptr, len); + if (EVP_SignFinal(&ctx, vp->sig, &len, sign_pkey)) + vp->siglen = htonl(len); #ifdef DEBUG - if (debug) - printf( - "crypto_cert: certif file %s link %d fs %u len %d\n", - cp, rval, fstamp, len); + if (debug > 1) + X509_print_fp(stdout, cert); #endif + X509_free(cert); + return (XEVNT_OK); } /* - * crypto_dh - read agreement parameters, decode and check for errors. + * cert_valid - verify certificate with given public key + * + * This is pretty ugly, as the certificate has to be verified in the + * OpenSSL X509 structure, not in the DER format in the info/value + * structure. + * + * Returns + * XEVNT_OK success + * XEVNT_VFY certificate not verified */ -static void -crypto_dh( - char *cp /* file name */ +int +cert_valid( + struct cert_info *cinf, /* certificate information structure */ + EVP_PKEY *pkey /* public key */ ) { - FILE *str; /* file handle */ - u_char buf[MAX_LINLEN]; /* file line buffer */ - u_char encoded_key[MAX_ENCLEN]; /* encoded key buffer */ - u_char prime[MAX_KEYLEN]; /* decoded prime */ - u_char generator[MAX_KEYLEN]; /* decode generator */ - u_int primelen; /* prime length (octets) */ - u_int generatorlen; /* generator length (octets) */ - char filename[MAXFILENAME]; /* name of parameter file */ - char linkname[MAXFILENAME]; /* file link (for filestamp) */ - u_int fstamp; /* filestamp */ - u_int32 *pp; - u_int len; - char *rptr; - int rval; + X509 *cert; /* X509 certificate */ + u_char *ptr; + + if (cinf->flags & CERT_SIGN) + return (XEVNT_OK); + ptr = (u_char *)cinf->cert.ptr; + cert = d2i_X509(NULL, &ptr, ntohl(cinf->cert.vallen)); + if (!X509_verify(cert, pkey)) + return (XEVNT_VFY); + cinf->flags |= CERT_SIGN; + X509_free(cert); + return (XEVNT_OK); +} + + +/* + * cert - install certificate in certificate list + * + * This routine encodes an extension field into a certificate info/value + * structure. It searches the certificate list for duplicates and + * expunges whichever is older. It then searches the list for other + * certificates that might be verified by this latest one. Finally, it + * inserts this certificate first on the list. + * + * Returns + * XEVNT_OK success + * XEVNT_PER certificate expired + * XEVNT_CRT bad or missing certificate + */ +int +cert_install( + struct exten *ep, /* cert info/value */ + struct peer *peer /* peer structure */ + ) +{ + struct cert_info *cp, *xp, *yp, **zp; + int rval; + tstamp_t tstamp; /* - * Open the file and discard comment lines. If the first - * character of the file name is not '/', prepend the keys - * directory string. If the file is not found, not to worry; it - * can be retrieved over the net. But, if it is found with - * errors, we crash and burn. + * Parse and validate the signed certificate. If valid, + * construct the info/value structure; otherwise, scamper home. + * Note this allows a certificate not-before time to be in the + * future, but not a not-after time to be in the past. + */ + if ((cp = cert_parse((u_char *)ep->pkt, ntohl(ep->vallen), + ntohl(ep->fstamp))) == NULL) + return (XEVNT_CRT); + + tstamp = crypto_time(); + if (tstamp > cp->last) { + cert_free(cp); + return (XEVNT_PER); + } + + /* + * Scan certificate list looking for another certificate with + * the same subject and issuer. If another is found with the + * same or older filestamp, unlink it and return the goodies to + * the heap. If another is found with a later filetsamp, discard + * the new one and leave the building. + */ + rval = XEVNT_OK; + yp = cp; + zp = &cinfo; + for (xp = cinfo; xp != NULL; xp = xp->link) { + if (strcmp(cp->subject, xp->subject) == 0 && + strcmp(cp->issuer, xp->issuer) == 0) { + if (ntohl(cp->cert.fstamp) <= + ntohl(xp->cert.fstamp)) { + *zp = xp->link;; + cert_free(xp); + } else { + cert_free(cp); + return (XEVNT_TSP); + } + break; + } + zp = &xp->link; + } + yp->link = cinfo; + cinfo = yp; + + /* + * Scan the certificate list to see if Y is signed by X. + */ + for (yp = cinfo; yp != NULL; yp = yp->link) { + for (xp = cinfo; xp != NULL; xp = xp->link) { + if (yp->flags & CERT_ERROR) + continue; + + /* + * If issuer Y matches subject X and signature Y + * is valid using public key X, then Y is valid. + */ + if (strcmp(yp->issuer, xp->subject) != 0) + continue; + + if (cert_valid(yp, xp->pkey) != XEVNT_OK) { + yp->flags |= CERT_ERROR; + continue; + } + xp->flags |= CERT_SIGN; + + /* + * If X is trusted, then Y is trusted. Note that + * we might stumble over a self signed + * certificate that is not trusted, at least + * temporarily. This can happen when a dude + * first comes up, but has not synchronized the + * clock and had its certificate signed by its + * server. In case of broken certificate trail, + * this might result in a loop that could + * persist until timeout. + */ + if (!(xp->flags & CERT_TRUST)) + continue; + + yp->flags |= CERT_TRUST; + + /* + * If subject Y matches the server subject name, + * then Y has completed the certificate trail. + * Save the group key and light the valid bit. + */ + if (strcmp(yp->subject, peer->subject) != 0) + continue; + + if (yp->grpkey != NULL) { + if (peer->grpkey != NULL) + BN_free(peer->grpkey); + peer->grpkey = BN_bin2bn(yp->grpkey, + yp->grplen, NULL); + } + peer->crypto |= CRYPTO_FLAG_VALID; + + /* + * If the server has an an identity scheme, + * fetch the identity credentials. If not, the + * identity is verified only by the trusted + * certificate. The next signature will set the + * server proventic. + */ + if (peer->crypto & (CRYPTO_FLAG_GQ | + CRYPTO_FLAG_IFF | CRYPTO_FLAG_MV)) + continue; + + peer->crypto |= CRYPTO_FLAG_VRFY; + } + } + + /* + * That was awesome. Now update the timestamps and signatures. + */ + crypto_update(); + return (rval); +} + + +/* + * cert_free - free certificate information structure + */ +void +cert_free( + struct cert_info *cinf /* certificate info/value structure */ + ) +{ + if (cinf->pkey != NULL) + EVP_PKEY_free(cinf->pkey); + if (cinf->subject != NULL) + free(cinf->subject); + if (cinf->issuer != NULL) + free(cinf->issuer); + if (cinf->grpkey != NULL) + free(cinf->grpkey); + value_free(&cinf->cert); + free(cinf); +} + + +/* + *********************************************************************** + * * + * The following routines are used only at initialization time * + * * + *********************************************************************** + */ +/* + * crypto_key - load cryptographic parameters and keys from files + * + * This routine loads a PEM-encoded public/private key pair and extracts + * the filestamp from the file name. + * + * Returns public key pointer if valid, NULL if not. Side effect updates + * the filestamp if valid. + */ +static EVP_PKEY * +crypto_key( + char *cp, /* file name */ + tstamp_t *fstamp /* filestamp */ + ) +{ + FILE *str; /* file handle */ + EVP_PKEY *pkey = NULL; /* public/private key */ + char filename[MAXFILENAME]; /* name of key file */ + char linkname[MAXFILENAME]; /* filestamp buffer) */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + char *ptr; + + /* + * Open the key file. If the first character of the file name is + * not '/', prepend the keys directory string. If something goes + * wrong, abandon ship. */ if (*cp == '/') strcpy(filename, cp); else snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); str = fopen(filename, "r"); - if (str == NULL) { - msyslog(LOG_INFO, - "crypto: parameters file %s not found", filename); - return; - } + if (str == NULL) + return (NULL); /* - * Ignore initial comments and empty lines. + * Read the filestamp, which is contained in the first line. */ - while ((rptr = fgets(buf, MAX_LINLEN - 1, str)) != NULL) { - if (strlen(buf) < 1) - continue; - if (*buf == '#' || *buf == '\r' || *buf == '\0') - continue; - break; + if ((ptr = fgets(linkname, MAXFILENAME, str)) == NULL) { + msyslog(LOG_ERR, "crypto_key: no data %s\n", + filename); + return (NULL); + } + if ((ptr = strrchr(ptr, '.')) == NULL) { + msyslog(LOG_ERR, "crypto_key: no filestamp %s\n", + filename); + return (NULL); + } + if (sscanf(++ptr, "%u", fstamp) != 1) { + msyslog(LOG_ERR, "crypto_key: invalid timestamp %s\n", + filename); + return (NULL); } /* - * We are rather paranoid here, since an intruder might cause a - * coredump by infiltrating a naughty key. There must be two - * lines; the first contains the prime, the second the - * generator. Each line must contain a single integer followed - * by a PEM encoded, null-terminated string. - */ - if (rptr == NULL) - rval = RV_DAT; - else if (sscanf(buf, "%u %s", &primelen, encoded_key) != 2) - rval = RV_DAT; - else if (primelen > MAX_KEYLEN) - rval = RV_KEY; - else if (R_DecodePEMBlock(prime, &len, encoded_key, - strlen(encoded_key))) - rval = RV_DEC; - else if (primelen != len || primelen > - DECODED_CONTENT_LEN(strlen(encoded_key))) - rval = RV_DAT; - else if (fscanf(str, "%u %s", &generatorlen, encoded_key) != 2) - rval = RV_DAT; - else if (generatorlen > MAX_KEYLEN) - rval = RV_KEY; - else if (R_DecodePEMBlock(generator, &len, encoded_key, - strlen(encoded_key))) - rval = RV_DEC; - else if (generatorlen != len || generatorlen > - DECODED_CONTENT_LEN(strlen(encoded_key))) - rval = RV_DAT; - else - rval = RV_OK; - if (rval != RV_OK) { - msyslog(LOG_ERR, - "crypto: parameters file %s error %x", cp, - rval); - exit (-1); - } + * Read and decrypt PEM-encoded private key. + */ + pkey = PEM_read_PrivateKey(str, NULL, NULL, passwd); fclose(str); + if (pkey == NULL) { + msyslog(LOG_ERR, "crypto_key %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (NULL); + } /* - * Initialize agreement parameters and extension field in - * network byte order. Note the private key length is set - * arbitrarily at half the prime length. + * Leave tracks in the cryptostats. */ - len = 4 + primelen + 4 + generatorlen; - dhparam.vallen = htonl(len); - pp = emalloc(len); - dhparam.ptr = (u_char *)pp; - *pp++ = htonl(primelen); - memcpy(pp, prime, primelen); - dh_params.prime = (u_char *)pp; - pp += primelen / 4; - *pp++ = htonl(generatorlen); - memcpy(pp, &generator, generatorlen); - dh_params.generator = (u_char *)pp; + if ((ptr = strrchr(linkname, '\n')) != NULL) + *ptr = '\0'; + sprintf(statstr, "%s mod %d", &linkname[2], + EVP_PKEY_size(pkey) * 8); + record_crypto_stats(NULL, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_key: %s\n", statstr); + if (debug > 1) { + if (EVP_MD_type(pkey) == EVP_PKEY_DSA) + DSA_print_fp(stdout, pkey->pkey.dsa, 0); + else + RSA_print_fp(stdout, pkey->pkey.rsa, 0); + } +#endif + return (pkey); +} - dh_params.primeLen = primelen; - dh_params.generatorLen = generatorlen; - dh_keyLen = primelen / 2; - dh_private = emalloc(dh_keyLen); - dhparam.sig = emalloc(private_key.bits / 8); - crypto_flags |= CRYPTO_FLAG_DH; + +/* + * crypto_cert - load certificate from file + * + * This routine loads a X.509 RSA or DSA certificate from a file and + * constructs a info/cert value structure for this machine. The + * structure includes a filestamp extracted from the file name. Later + * the certificate can be sent to another machine by request. + * + * Returns certificate info/value pointer if valid, NULL if not. + */ +static struct cert_info * /* certificate information */ +crypto_cert( + char *cp /* file name */ + ) +{ + struct cert_info *ret; /* certificate information */ + FILE *str; /* file handle */ + char filename[MAXFILENAME]; /* name of certificate file */ + char linkname[MAXFILENAME]; /* filestamp buffer */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + tstamp_t fstamp; /* filestamp */ + long len; + char *ptr; + char *name, *header; + u_char *data; /* - * Initialize public value extension field. + * Open the certificate file. If the first character of the file + * name is not '/', prepend the keys directory string. If + * something goes wrong, abandon ship. */ - dhpub.vallen = htonl(dh_params.primeLen); - dhpub.ptr = emalloc(dh_params.primeLen); - dhpub.sig = emalloc(private_key.bits / 8); + if (*cp == '/') + strcpy(filename, cp); + else + snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); + str = fopen(filename, "r"); + if (str == NULL) + return (NULL); /* - * Extract filestamp if present. + * Read the filestamp, which is contained in the first line. */ - rval = readlink(filename, linkname, MAXFILENAME - 1); - if (rval > 0) { - linkname[rval] = '\0'; - rptr = strrchr(linkname, '.'); - } else { - rptr = strrchr(filename, '.'); + if ((ptr = fgets(linkname, MAXFILENAME, str)) == NULL) { + msyslog(LOG_ERR, "crypto_cert: no data %s\n", + filename); + return (NULL); } - if (rptr != NULL) - sscanf(++rptr, "%u", &fstamp); - else - fstamp = 0; - dhparam.fstamp = htonl(fstamp); - dhpub.fstamp = htonl(fstamp); + if ((ptr = strrchr(ptr, '.')) == NULL) { + msyslog(LOG_ERR, "crypto_cert: no filestamp %s\n", + filename); + return (NULL); + } + if (sscanf(++ptr, "%u", &fstamp) != 1) { + msyslog(LOG_ERR, "crypto_cert: invalid filestamp %s\n", + filename); + return (NULL); + } + + /* + * Read PEM-encoded certificate and install. + */ + if (!PEM_read(str, &name, &header, &data, &len)) { + msyslog(LOG_ERR, "crypto_cert %s\n", + ERR_error_string(ERR_get_error(), NULL)); + return (NULL); + } + free(header); + if (strcmp(name, "CERTIFICATE") !=0) { + msyslog(LOG_INFO, "crypto_cert: wrong PEM type %s", + name); + free(name); + free(data); + return (NULL); + } + free(name); + + /* + * Parse certificate and generate info/value structure. + */ + ret = cert_parse(data, len, fstamp); + free(data); + if (ret == NULL) + return (NULL); + if ((ptr = strrchr(linkname, '\n')) != NULL) + *ptr = '\0'; + sprintf(statstr, "%s 0x%x len %lu", &linkname[2], ret->flags, + len); + record_crypto_stats(NULL, statstr); #ifdef DEBUG if (debug) - printf( - "crypto_dh: pars file %s link %d fs %u prime %u gen %u\n", - cp, rval, fstamp, dh_params.primeLen, - dh_params.generatorLen); + printf("crypto_cert: %s\n", statstr); #endif + return (ret); } /* - * crypto_tai - read leapseconds table and check for errors. + * crypto_tai - load leapseconds table from file + * + * This routine loads the ERTS leapsecond file in NIST text format, + * converts to a value structure and extracts a filestamp from the file + * name. The data are used to establish the TAI offset from UTC, which + * is provided to the kernel if supported. Later the data can be sent to + * another machine on request. */ static void crypto_tai( - char *cp /* file name */ + char *cp /* file name */ ) { - FILE *str; /* file handle */ - u_char buf[MAX_LINLEN]; /* file line buffer */ - u_int leapsec[MAX_LEAP]; /* NTP time at leaps */ - u_int offset; /* offset at leap (s) */ - char filename[MAXFILENAME]; /* name of leapseconds file */ - char linkname[MAXFILENAME]; /* file link (for filestamp) */ - u_int fstamp; /* filestamp */ - u_int32 *pp; - u_int len; - char *rptr; - int rval, i; + FILE *str; /* file handle */ + char buf[NTP_MAXSTRLEN]; /* file line buffer */ + u_int leapsec[MAX_LEAP]; /* NTP time at leaps */ + u_int offset; /* offset at leap (s) */ + char filename[MAXFILENAME]; /* name of leapseconds file */ + char linkname[MAXFILENAME]; /* file link (for filestamp) */ + char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */ + tstamp_t fstamp; /* filestamp */ + u_int len; + char *ptr; + int rval, i; #ifdef KERNEL_PLL #if NTP_API > 3 struct timex ntv; /* kernel interface structure */ @@ -1886,13 +3616,24 @@ crypto_tai( strcpy(filename, cp); else snprintf(filename, MAXFILENAME, "%s/%s", keysdir, cp); - str = fopen(filename, "r"); - if (str == NULL) { - msyslog(LOG_INFO, - "crypto: leapseconds file %s not found", - filename); + if ((str = fopen(filename, "r")) == NULL) return; + + /* + * Extract filestamp if present. + */ + rval = readlink(filename, linkname, MAXFILENAME - 1); + if (rval > 0) { + linkname[rval] = '\0'; + ptr = strrchr(linkname, '.'); + } else { + ptr = strrchr(filename, '.'); } + if (ptr != NULL) + sscanf(++ptr, "%u", &fstamp); + else + fstamp = 0; + tai_leap.fstamp = htonl(fstamp); /* * We are rather paranoid here, since an intruder might cause a @@ -1905,10 +3646,9 @@ crypto_tai( * 1972 plus one second for each succeeding insertion. */ i = 0; - rval = RV_OK; while (i < MAX_LEAP) { - rptr = fgets(buf, MAX_LINLEN - 1, str); - if (rptr == NULL) + ptr = fgets(buf, NTP_MAXSTRLEN - 1, str); + if (ptr == NULL) break; if (strlen(buf) < 1) continue; @@ -1916,16 +3656,15 @@ crypto_tai( continue; if (sscanf(buf, "%u %u", &leapsec[i], &offset) != 2) continue; - if (i != offset - TAI_1972) { - rval = RV_DAT; + if (i != (int)(offset - TAI_1972)) { break; } i++; } fclose(str); - if (rval != RV_OK || i == 0) { - msyslog(LOG_ERR, - "crypto: leapseconds file %s error %d", cp, + if (ptr != NULL) { + msyslog(LOG_INFO, + "crypto_tai: leapseconds file %s error %d", cp, rval); exit (-1); } @@ -1937,12 +3676,11 @@ crypto_tai( */ len = i * 4; tai_leap.vallen = htonl(len); - pp = emalloc(len); - tai_leap.ptr = (u_char *)pp; + ptr = emalloc(len); + tai_leap.ptr = (unsigned char *) ptr; for (; i >= 0; i--) { - *pp++ = htonl(leapsec[i]); + *ptr++ = (char) htonl(leapsec[i]); } - tai_leap.sig = emalloc(private_key.bits / 8); crypto_flags |= CRYPTO_FLAG_TAI; sys_tai = len / 4 + TAI_1972 - 1; #ifdef KERNEL_PLL @@ -1950,109 +3688,344 @@ crypto_tai( ntv.modes = MOD_TAI; ntv.constant = sys_tai; if (ntp_adjtime(&ntv) == TIME_ERROR) - msyslog(LOG_ERR, - "crypto: kernel TAI update failed"); + msyslog(LOG_INFO, + "crypto_tai: kernel TAI update failed"); #endif /* NTP_API */ #endif /* KERNEL_PLL */ + sprintf(statstr, "%s link %d fs %u offset %u", cp, rval, fstamp, + ntohl(tai_leap.vallen) / 4 + TAI_1972 - 1); + record_crypto_stats(NULL, statstr); +#ifdef DEBUG + if (debug) + printf("crypto_tai: %s\n", statstr); +#endif +} +/* + * crypto_setup - load keys, certificate and leapseconds table + * + * This routine loads the public/private host key and certificate. If + * available, it loads the public/private sign key, which defaults to + * the host key, and leapseconds table. The host key must be RSA, but + * the sign key can be either RSA or DSA. In either case, the public key + * on the certificate must agree with the sign key. + */ +void +crypto_setup(void) +{ + EVP_PKEY *pkey; /* private/public key pair */ + char filename[MAXFILENAME]; /* file name buffer */ + l_fp seed; /* crypto PRNG seed as NTP timestamp */ + tstamp_t fstamp; /* filestamp */ + tstamp_t sstamp; /* sign filestamp */ + u_int len, bytes; + u_char *ptr; + /* - * Extract filestamp if present. + * Initialize structures. */ - rval = readlink(filename, linkname, MAXFILENAME - 1); - if (rval > 0) { - linkname[rval] = '\0'; - rptr = strrchr(linkname, '.'); - } else { - rptr = strrchr(filename, '.'); + if (!crypto_flags) + return; + gethostname(filename, MAXFILENAME); + bytes = strlen(filename) + 1; + sys_hostname = emalloc(bytes); + memcpy(sys_hostname, filename, bytes); + if (passwd == NULL) + passwd = sys_hostname; + memset(&hostval, 0, sizeof(hostval)); + memset(&pubkey, 0, sizeof(pubkey)); + memset(&tai_leap, 0, sizeof(tai_leap)); + + /* + * Load required random seed file and seed the random number + * generator. Be default, it is found in the user home + * directory. The root home directory may be / or /root, + * depending on the system. Wiggle the contents a bit and write + * it back so the sequence does not repeat when we next restart. + */ + ERR_load_crypto_strings(); + if (rand_file == NULL) { + if ((RAND_file_name(filename, MAXFILENAME)) != NULL) { + rand_file = emalloc(strlen(filename) + 1); + strcpy(rand_file, filename); + } + } else if (*rand_file != '/') { + snprintf(filename, MAXFILENAME, "%s/%s", keysdir, + rand_file); + free(rand_file); + rand_file = emalloc(strlen(filename) + 1); + strcpy(rand_file, filename); } - if (rptr != NULL) - sscanf(++rptr, "%u", &fstamp); - else - fstamp = 0; - tai_leap.fstamp = htonl(fstamp); + if (rand_file == NULL) { + msyslog(LOG_ERR, + "crypto_setup: random seed file not specified"); + exit (-1); + } + if ((bytes = RAND_load_file(rand_file, -1)) == 0) { + msyslog(LOG_ERR, + "crypto_setup: random seed file %s not found\n", + rand_file); + exit (-1); + } + get_systime(&seed); + RAND_seed(&seed, sizeof(l_fp)); + RAND_write_file(rand_file); + OpenSSL_add_all_algorithms(); #ifdef DEBUG if (debug) printf( - "crypto_tai: leapseconds file %s link %d fs %u offset %u\n", - cp, rval, fstamp, ntohl(tai_leap.vallen) / 4 + - TAI_1972); + "crypto_setup: OpenSSL version %lx random seed file %s bytes read %d\n", + SSLeay(), rand_file, bytes); +#endif + + /* + * Load required host key from file "ntpkey_host_<hostname>". It + * also becomes the default sign key. + */ + if (host_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_host_%s", + sys_hostname); + host_file = emalloc(strlen(filename) + 1); + strcpy(host_file, filename); + } + pkey = crypto_key(host_file, &fstamp); + if (pkey == NULL) { + msyslog(LOG_ERR, + "crypto_setup: host key file %s not found or corrupt", + host_file); + exit (-1); + } + host_pkey = pkey; + sign_pkey = pkey; + sstamp = fstamp; + hostval.fstamp = htonl(fstamp); + if (EVP_MD_type(host_pkey) != EVP_PKEY_RSA) { + msyslog(LOG_ERR, + "crypto_setup: host key is not RSA key type"); + exit (-1); + } + hostval.vallen = htonl(strlen(sys_hostname)); + hostval.ptr = (unsigned char *) sys_hostname; + + /* + * Construct public key extension field for agreement scheme. + */ + len = i2d_PublicKey(host_pkey, NULL); + ptr = emalloc(len); + pubkey.ptr = ptr; + i2d_PublicKey(host_pkey, &ptr); + pubkey.vallen = htonl(len); + pubkey.fstamp = hostval.fstamp; + + /* + * Load optional sign key from file "ntpkey_sign_<hostname>". If + * loaded, it becomes the sign key. + */ + if (sign_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_sign_%s", + sys_hostname); + sign_file = emalloc(strlen(filename) + 1); + strcpy(sign_file, filename); + } + pkey = crypto_key(sign_file, &fstamp); + if (pkey != NULL) { + sign_pkey = pkey; + sstamp = fstamp; + } + sign_siglen = EVP_PKEY_size(sign_pkey); + + /* + * Load optional IFF parameters from file + * "ntpkey_iff_<hostname>". + */ + if (iffpar_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_iff_%s", + sys_hostname); + iffpar_file = emalloc(strlen(filename) + 1); + strcpy(iffpar_file, filename); + } + iffpar_pkey = crypto_key(iffpar_file, &if_fstamp); + if (iffpar_pkey != NULL) + crypto_flags |= CRYPTO_FLAG_IFF; + + /* + * Load optional GQ parameters from file "ntpkey_gq_<hostname>". + */ + if (gqpar_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_gq_%s", + sys_hostname); + gqpar_file = emalloc(strlen(filename) + 1); + strcpy(gqpar_file, filename); + } + gqpar_pkey = crypto_key(gqpar_file, &gq_fstamp); + if (gqpar_pkey != NULL) + crypto_flags |= CRYPTO_FLAG_GQ; + + /* + * Load optional MV parameters from file "ntpkey_mv_<hostname>". + */ + if (mvpar_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_mv_%s", + sys_hostname); + mvpar_file = emalloc(strlen(filename) + 1); + strcpy(mvpar_file, filename); + } + mvpar_pkey = crypto_key(mvpar_file, &mv_fstamp); + if (mvpar_pkey != NULL) + crypto_flags |= CRYPTO_FLAG_MV; + + /* + * Load required certificate from file "ntpkey_cert_<hostname>". + */ + if (cert_file == NULL) { + snprintf(filename, MAXFILENAME, "ntpkey_cert_%s", + sys_hostname); + cert_file = emalloc(strlen(filename) + 1); + strcpy(cert_file, filename); + } + if ((cinfo = crypto_cert(cert_file)) == NULL) { + msyslog(LOG_ERR, + "certificate file %s not found or corrupt", + cert_file); + exit (-1); + } + + /* + * The subject name must be the same as the host name, unless + * the certificate is private, in which case it may have come + * from another host. + */ + if (!(cinfo->flags & CERT_PRIV) && strcmp(cinfo->subject, + sys_hostname) != 0) { + msyslog(LOG_ERR, + "crypto_setup: certificate %s not for this host", + cert_file); + cert_free(cinfo); + exit (-1); + } + + /* + * It the certificate is trusted, the subject must be the same + * as the issuer, in other words it must be self signed. + */ + if (cinfo->flags & CERT_PRIV && strcmp(cinfo->subject, + cinfo->issuer) != 0) { + if (cert_valid(cinfo, sign_pkey) != XEVNT_OK) { + msyslog(LOG_ERR, + "crypto_setup: certificate %s is trusted, but not self signed.", + cert_file); + cert_free(cinfo); + exit (-1); + } + } + sign_digest = cinfo->digest; + if (cinfo->flags & CERT_PRIV) + crypto_flags |= CRYPTO_FLAG_PRIV; + crypto_flags |= cinfo->nid << 16; + + /* + * Load optional leapseconds table from file "ntpkey_leap". If + * the file is missing or defective, the values can later be + * retrieved from a server. + */ + if (leap_file == NULL) + leap_file = "ntpkey_leap"; + crypto_tai(leap_file); +#ifdef DEBUG + if (debug) + printf( + "crypto_setup: flags 0x%x host %s signature %s\n", + crypto_flags, sys_hostname, OBJ_nid2ln(cinfo->nid)); #endif } /* - * crypto_config - configure crypto data from crypto configuration - * command. + * crypto_config - configure data from crypto configuration command. */ void crypto_config( - int item, /* configuration item */ - char *cp /* file name */ + int item, /* configuration item */ + char *cp /* file name */ ) { switch (item) { /* - * Initialize flags + * Set random seed file name. + */ + case CRYPTO_CONF_RAND: + rand_file = emalloc(strlen(cp) + 1); + strcpy(rand_file, cp); + break; + + /* + * Set private key password. */ - case CRYPTO_CONF_FLAGS: - sscanf(cp, "%x", &crypto_flags); + case CRYPTO_CONF_PW: + passwd = emalloc(strlen(cp) + 1); + strcpy(passwd, cp); break; /* - * Set private key file name. + * Set host file name. */ case CRYPTO_CONF_PRIV: - private_key_file = emalloc(strlen(cp) + 1); - strcpy(private_key_file, cp); + host_file = emalloc(strlen(cp) + 1); + strcpy(host_file, cp); break; /* - * Set public key file name. + * Set sign key file name. */ - case CRYPTO_CONF_PUBL: - public_key_file = emalloc(strlen(cp) + 1); - strcpy(public_key_file, cp); + case CRYPTO_CONF_SIGN: + sign_file = emalloc(strlen(cp) + 1); + strcpy(sign_file, cp); break; /* - * Set certificate file name. + * Set iff parameters file name. */ - case CRYPTO_CONF_CERT: - certif_file = emalloc(strlen(cp) + 1); - strcpy(certif_file, cp); + case CRYPTO_CONF_IFFPAR: + iffpar_file = emalloc(strlen(cp) + 1); + strcpy(iffpar_file, cp); break; /* - * Set agreement parameter file name. + * Set gq parameters file name. */ - case CRYPTO_CONF_DH: - dh_params_file = emalloc(strlen(cp) + 1); - strcpy(dh_params_file, cp); + case CRYPTO_CONF_GQPAR: + gqpar_file = emalloc(strlen(cp) + 1); + strcpy(gqpar_file, cp); break; /* - * Set leapseconds table file name. + * Set mv parameters file name. */ - case CRYPTO_CONF_LEAP: - tai_leap_file = emalloc(strlen(cp) + 1); - strcpy(tai_leap_file, cp); + case CRYPTO_CONF_MVPAR: + mvpar_file = emalloc(strlen(cp) + 1); + strcpy(mvpar_file, cp); + break; + + /* + * Set certificate file name. + */ + case CRYPTO_CONF_CERT: + cert_file = emalloc(strlen(cp) + 1); + strcpy(cert_file, cp); break; /* - * Set crypto keys directory. + * Set leapseconds file name. */ - case CRYPTO_CONF_KEYS: - keysdir = emalloc(strlen(cp) + 1); - strcpy(keysdir, cp); + case CRYPTO_CONF_LEAP: + leap_file = emalloc(strlen(cp) + 1); + strcpy(leap_file, cp); break; } crypto_flags |= CRYPTO_FLAG_ENAB; } # else int ntp_crypto_bs_pubkey; -# endif /* PUBKEY */ -#else -int ntp_crypto_bs_autokey; -#endif /* AUTOKEY */ +# endif /* OPENSSL */ |