# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <ctype.h>
# include <time.h>
# include <assert.h>
# include <dirent.h>
# include <errno.h>
# include <unistd.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
# include "config_zkt.h"
# include "zconf.h"
# include "debug.h"
# include "misc.h"
# include "zone.h"
# include "dki.h"
# include "log.h"
#define extern
# include "rollover.h"
#undef extern
static dki_t *genkey (dki_t **listp, const char *dir, const char *domain, int ksk, const zconf_t *conf, int status)
{
dki_t *dkp;
if ( listp == NULL || domain == NULL )
return NULL;
if ( ksk )
dkp = dki_new (dir, domain, DKI_KSK, conf->k_algo, conf->k_bits, conf->k_random, conf->k_life / DAYSEC);
else
dkp = dki_new (dir, domain, DKI_ZSK, conf->z_algo, conf->z_bits, conf->z_random, conf->z_life / DAYSEC);
dki_add (listp, dkp);
dki_setstatus (dkp, status);
return dkp;
}
static time_t get_exptime (dki_t *key, const zconf_t *z)
{
time_t exptime;
exptime = dki_exptime (key);
if ( exptime == 0L )
{
if ( dki_lifetime (key) )
exptime = dki_time (key) + dki_lifetime (key);
else
exptime = dki_time (key) + z->k_life;
}
return exptime;
}
static int is_parentdirsigned (const zone_t *zonelist, const zone_t *zp)
{
char path[MAX_PATHSIZE+1];
const char *ext;
#if 0
const zconf_t *conf;
snprintf (path, sizeof (path), "%s/../%s", zp->dir, LOCALCONF_FILE);
if ( fileexist (path) )
conf = loadconfig (path, NULL);
else
conf = zp->conf;
snprintf (path, sizeof (path), "%s/../%s.signed", conf->dir, conf->zonefile);
if ( conf != zp->conf )
free (conf);
#else
ext = strrchr (zp->sfile, '.');
if ( ext && strcmp (zp->sfile, ".dsigned") == 0 )
snprintf (path, sizeof (path), "%s/../%s", zp->dir, "zone.db.signed");
else
{
# if 1
const zone_t *parent;
const char *parentname;
parentname = strchr (zp->zone, '.');
if ( parentname == NULL )
return 0;
parentname += 1;
if ( (parent = zone_search (zonelist, parentname)) == NULL )
return 0;
snprintf (path, sizeof (path), "%s/%s", parent->dir, parent->sfile);
# else
snprintf (path, sizeof (path), "%s/../%s", zp->dir, zp->sfile);
# endif
}
#endif
lg_mesg (LG_DEBUG, "%s: is_parentdirsigned = %d fileexist (%s)\n", zp->zone, fileexist (path), path);
return fileexist (path);
}
static int create_parent_file (const char *fname, int phase, int ttl, const dki_t *dkp)
{
FILE *fp;
assert ( fname != NULL );
if ( dkp == NULL || (phase != 1 && phase != 2) )
return 0;
if ( (fp = fopen (fname, "w")) == NULL )
fatal ("can\'t create new parentfile \"%s\"\n", fname);
if ( phase == 1 )
fprintf (fp, "; KSK rollover phase1 (new key generated but this is alread the old one)\n");
else
fprintf (fp, "; KSK rollover phase2 (this is the new key)\n");
dki_prt_dnskeyttl (dkp, fp, ttl);
fclose (fp);
return phase;
}
static int get_parent_phase (const char *file)
{
FILE *fp;
int phase;
if ( (fp = fopen (file, "r")) == NULL )
return -1;
phase = 0;
if ( fscanf (fp, "; KSK rollover phase%d", &phase) != 1 )
phase = 0;
fclose (fp);
return phase;
}
static int kskrollover (dki_t *ksk, zone_t *zonelist, zone_t *zp)
{
char path[MAX_PATHSIZE+1];
const zconf_t *z;
time_t lifetime;
time_t currtime;
time_t age;
int currphase;
int parfile_age;
int parent_propagation;
int parent_resign;
int parent_keyttl;
assert ( ksk != NULL );
assert ( zp != NULL );
z = zp->conf;
if ( (lifetime = dki_lifetime (ksk)) == 0 )
lifetime = z->k_life;
currtime = time (NULL);
age = dki_age (ksk, currtime);
pathname (path, sizeof (path), zp->dir, "parent-", zp->zone);
if ( lifetime > 0 && age > lifetime && !fileexist (path) )
{
if ( z->keysetdir && strcmp (z->keysetdir, "..") == 0 && is_parentdirsigned (zonelist, zp) )
{
verbmesg (2, z, "\t\tkskrollover: create new key signing key\n");
ksk = genkey (&zp->keys, zp->dir, zp->zone, DKI_KSK, z, DKI_ACTIVE);
if ( ksk == NULL )
{
lg_mesg (LG_ERROR, "\"%s\": unable to generate new ksk for double signing rollover", zp->zone);
return 0;
}
lg_mesg (LG_INFO, "\"%s\": kskrollover phase1: New key %d generated", zp->zone, ksk->tag);
if ( (ksk = (dki_t *)dki_find (zp->keys, 1, 'a', 1)) == NULL )
lg_mesg (LG_ERROR, "kskrollover phase1: Couldn't find the old active key\n");
if ( !create_parent_file (path, 1, z->key_ttl, ksk) )
lg_mesg (LG_ERROR, "Couldn't create parentfile %s\n", path);
}
else
{
logmesg ("\t\tWarning: Lifetime of Key Signing Key %d exceeded: %s\n",
ksk->tag, str_delspace (age2str (age)));
lg_mesg (LG_WARNING, "\"%s\": lifetime of key signing key %d exceeded since %s",
zp->zone, ksk->tag, str_delspace (age2str (age - lifetime)));
}
return 1;
}
if ( !fileexist (path) )
return 0;
currphase = get_parent_phase (path);
parfile_age = file_age (path);
parent_propagation = 5 * MINSEC;
parent_resign = z->resign;
parent_keyttl = z->key_ttl;
switch ( currphase )
{
case 1:
if ( parfile_age > z->proptime + z->key_ttl )
{
verbmesg (2, z, "\t\tkskrollover: save new ksk in parent file\n");
ksk = ksk->next;
if ( !create_parent_file (path, currphase+1, z->key_ttl, ksk) )
lg_mesg (LG_ERROR, "Couldn't create parentfile %s\n", path);
lg_mesg (LG_INFO, "\"%s\": kskrollover phase2: send new key %d to the parent zone", zp->zone, ksk->tag);
return 1;
}
else
verbmesg (2, z, "\t\tkskrollover: we are in state 1 and waiting for propagation of the new key (parentfile %d < prop %d + keyttl %d\n", parfile_age, z->proptime, z->key_ttl);
break;
case 2:
#if 0
if ( parfile_age >= parent_propagation + parent_resign + parent_keyttl )
#else
if ( parfile_age >= parent_propagation + parent_keyttl )
#endif
{
unlink (path);
zp->keys = dki_remove (ksk);
verbmesg (2, z, "\t\tkskrollover: remove parentfile and rename old key to k%s+%03d+%05d.key\n",
ksk->name, ksk->algo, ksk->tag);
lg_mesg (LG_INFO, "\"%s\": kskrollover phase3: Remove old key %d", zp->zone, ksk->tag);
return 1;
}
else
#if 0
verbmesg (2, z, "\t\tkskrollover: we are in state 2 and waiting for parent propagation (parentfile %d < parentprop %d + parentresig %d + parentkeyttl %d\n", parfile_age, parent_propagation, parent_resign, parent_keyttl);
#else
verbmesg (2, z, "\t\tkskrollover: we are in state 2 and waiting for parent propagation (parentfile %d < parentprop %d + parentkeyttl %d\n", parfile_age, parent_propagation, parent_keyttl);
#endif
break;
default:
assert ( currphase == 1 || currphase == 2 );
}
return 0;
}
int ksk5011status (dki_t **listp, const char *dir, const char *domain, const zconf_t *z)
{
dki_t *standbykey;
dki_t *activekey;
dki_t *dkp;
dki_t *prev;
time_t currtime;
time_t exptime;
int ret;
assert ( listp != NULL );
assert ( z != NULL );
if ( z->k_life == 0 )
return 0;
verbmesg (1, z, "\tCheck RFC5011 status\n");
ret = 0;
currtime = time (NULL);
standbykey = activekey = NULL;
prev = NULL;
for ( dkp = *listp; dkp && dki_isksk (dkp); dkp = dkp->next )
{
exptime = get_exptime (dkp, z);
if ( dki_isrevoked (dkp) )
lg_mesg (LG_DEBUG, "Rev Exptime: %s", time2str (exptime, 's'));
if ( dki_isrevoked (dkp) && currtime > exptime + (DAYSEC * 30) )
{
verbmesg (1, z, "\tRemove revoked key %d which is older than 30 days\n", dkp->tag);
lg_mesg (LG_NOTICE, "zone \"%s\": removing revoked key %d", domain, dkp->tag);
if ( prev == NULL )
*listp = dki_remove (dkp);
else
prev->next = dki_remove (dkp);
ret |= 01;
}
if ( dki_status (dkp) == DKI_PUBLISHED )
standbykey = dkp;
if ( dki_status (dkp) == DKI_ACTIVE )
activekey = dkp;
}
if ( standbykey == NULL && ret == 0 )
return ret;
ret |= 02;
exptime = get_exptime (activekey, z);
#if 0
lg_mesg (LG_DEBUG, "Act Exptime: %s", time2str (exptime, 's'));
lg_mesg (LG_DEBUG, "Stb time: %s", time2str (dki_time (standbykey), 's'));
lg_mesg (LG_DEBUG, "Stb time+wait: %s", time2str (dki_time (standbykey) + min (DAYSEC * 30, z->key_ttl), 's'));
#endif
if ( currtime > exptime && currtime > dki_time (standbykey) + min (DAYSEC * 30, z->key_ttl) )
{
lg_mesg (LG_NOTICE, "\"%s\": starting rfc5011 rollover", domain);
verbmesg (1, z, "\tLifetime of Key Signing Key %d exceeded (%s): Starting rfc5011 rollover!\n",
activekey->tag, str_delspace (age2str (dki_age (activekey, currtime))));
verbmesg (2, z, "\t\t=>Generating new standby key signing key\n");
dkp = genkey (listp, dir, domain, DKI_KSK, z, DKI_PUBLISHED);
if ( !dkp )
{
error ("\tcould not generate new standby KSK\n");
lg_mesg (LG_ERROR, "\%s\": can't generate new standby KSK", domain);
}
else
lg_mesg (LG_INFO, "\"%s\": generated new standby KSK %d", domain, dkp->tag);
verbmesg (2, z, "\t\t=>Activating old standby key %d \n", standbykey->tag);
dki_setstatus (standbykey, DKI_ACT);
verbmesg (2, z, "\t\t=>Revoking old active key %d \n", activekey->tag);
dki_setstatus (activekey, DKI_REVOKED);
dki_setexptime (activekey, currtime);
ret |= 01;
}
return ret;
}
int kskstatus (zone_t *zonelist, zone_t *zp)
{
dki_t *akey;
const zconf_t *z;
assert ( zp != NULL );
z = zp->conf;
if ( z->k_life == 0 )
return 0;
verbmesg (1, z, "\tCheck KSK status\n");
akey = (dki_t *)dki_find (zp->keys, 1, 'a', 1);
if ( akey == NULL )
{
verbmesg (1, z, "\tNo active KSK found: generate new one\n");
akey = genkey (&zp->keys, zp->dir, zp->zone, DKI_KSK, z, DKI_ACTIVE);
if ( !akey )
{
error ("\tcould not generate new KSK\n");
lg_mesg (LG_ERROR, "\"%s\": can't generate new KSK: \"%s\"",
zp->zone, dki_geterrstr());
}
else
lg_mesg (LG_INFO, "\"%s\": generated new KSK %d", zp->zone, akey->tag);
return akey != NULL;
}
else
kskrollover (akey, zonelist, zp);
return 0;
}
int zskstatus (dki_t **listp, const char *dir, const char *domain, const zconf_t *z)
{
dki_t *akey;
dki_t *nextkey;
dki_t *dkp, *last;
int keychange;
time_t lifetime;
time_t age;
time_t currtime;
assert ( listp != NULL );
assert ( domain != NULL );
assert ( z != NULL );
currtime = time (NULL);
verbmesg (1, z, "\tCheck ZSK status\n");
dbg_val("zskstatus for %s \n", domain);
keychange = 0;
lifetime = z->max_ttl + z->proptime;
last = NULL;
dkp = *listp;
while ( dkp )
if ( !dki_isksk (dkp) &&
dki_status (dkp) == DKI_DEPRECIATED &&
dki_age (dkp, currtime) > lifetime )
{
keychange = 1;
verbmesg (1, z, "\tLifetime(%d sec) of depreciated key %d exceeded (%d sec)\n",
lifetime, dkp->tag, dki_age (dkp, currtime));
lg_mesg (LG_INFO, "\"%s\": old ZSK %d removed", domain, dkp->tag);
dkp = dki_destroy (dkp);
dbg_msg("zskstatus: depreciated key removed ");
if ( last )
last->next = dkp;
else
*listp = dkp;
verbmesg (1, z, "\t\t->remove it\n");
}
else
{
last = dkp;
dkp = dkp->next;
}
dbg_msg("zskstatus check status of active key ");
lifetime = z->z_life;
akey = (dki_t *)dki_find (*listp, 0, 'a', 1);
if ( akey == NULL && lifetime > 0 )
{
verbmesg (1, z, "\tNo active ZSK found: generate new one\n");
akey = genkey (listp, dir, domain, DKI_ZSK, z, DKI_ACTIVE);
lg_mesg (LG_INFO, "\"%s\": generated new ZSK %d", domain, akey->tag);
}
else
{
if ( dki_lifetime (akey) )
lifetime = dki_lifetime (akey);
age = dki_age (akey, currtime);
if ( lifetime > 0 && age > lifetime - (OFFSET) )
{
verbmesg (1, z, "\tLifetime(%d +/-%d sec) of active key %d exceeded (%d sec)\n",
lifetime, (OFFSET) , akey->tag, dki_age (akey, currtime) );
if ( (nextkey = (dki_t *)dki_find (*listp, 0, 'a', 2)) == NULL ||
nextkey == akey )
nextkey = (dki_t *)dki_find (*listp, 0, 'p', 1);
if ( nextkey && dki_age (nextkey, currtime) > z->key_ttl + z->proptime )
{
keychange = 1;
verbmesg (1, z, "\t\t->depreciate it\n");
dki_setstatus (akey, 'd');
verbmesg (1, z, "\t\t->activate published key %d\n", nextkey->tag);
dki_setstatus (nextkey, 'a');
lg_mesg (LG_NOTICE, "\"%s\": lifetime of zone signing key %d exceeded: ZSK rollover done", domain, akey->tag);
akey = nextkey;
nextkey = NULL;
}
else
{
verbmesg (1, z, "\t\t->waiting for published key\n");
lg_mesg (LG_NOTICE, "\"%s\": lifetime of zone signing key %d exceeded since %s: ZSK rollover deferred: waiting for published key",
domain, akey->tag, str_delspace (age2str (age - lifetime)));
}
}
}
nextkey = (dki_t *)dki_find (*listp, 0, 'p', 1);
if ( nextkey == NULL && lifetime > 0 && (akey == NULL ||
dki_age (akey, currtime + z->resign) > lifetime - (OFFSET)) )
{
keychange = 1;
verbmesg (1, z, "\tNew key for publishing needed\n");
nextkey = genkey (listp, dir, domain, DKI_ZSK, z, DKI_PUB);
if ( nextkey )
{
verbmesg (1, z, "\t\t->creating new key %d\n", nextkey->tag);
lg_mesg (LG_INFO, "\"%s\": new key %d generated for publishing", domain, nextkey->tag);
}
else
{
error ("\tcould not generate new ZSK: \"%s\"\n", dki_geterrstr());
lg_mesg (LG_ERROR, "\"%s\": can't generate new ZSK: \"%s\"",
domain, dki_geterrstr());
}
}
return keychange;
}