Fix bugzilla 473860: certwatch does not warn of expiring certificates

This commit is contained in:
Elio Maldonado 2008-12-24 22:19:24 +00:00
parent 13582087e9
commit 835af071b0
1 changed files with 99 additions and 62 deletions

View File

@ -5,16 +5,16 @@
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
In addition, as a special exception, Red Hat, Inc. gives permission
to link the code of this program with the OpenSSL library (or with
modified versions of OpenSSL that use the same license as OpenSSL),
@ -84,6 +84,7 @@
#include <secmod.h>
#include <base64.h>
#include <seccomon.h>
#include <certt.h>
#include <stdio.h>
#include <string.h>
@ -93,11 +94,11 @@
#define TIME_BUF_SIZE 100
/* Return a certificate structure from a pem-encoded cert in a file;
/* Return a certificate structure from a pem-encoded cert in a file;
* or NULL on failure. Semantics similar to the OpenSSL call
* PEM_read_X509(fp, NULL, NULL, NULL);
*/
extern CERTCertificate *
extern CERTCertificate *
PEMUTIL_PEM_read_X509(const char *filename);
/* size big enough for formatting time buffer */
@ -106,27 +107,11 @@ PEMUTIL_PEM_read_X509(const char *filename);
static int warn_period = 30;
static char *warn_address = "root";
/* Format a PRTime value into a buffer with format "%a %b %d %H:%M:%S %Y"
* and return the buffer. The buffer returned must the freed with PORT_Free.
* Semantics are similar to asctime.
*/
char * AsciiTime(PRTime time)
{
PRExplodedTime printable;
char *timebuf;
PR_ExplodeTime(time, PR_GMTParameters, &printable);
timebuf = PORT_Alloc(TIME_BUF_SIZE);
(void) PR_FormatTime(timebuf, TIME_BUF_SIZE, "%a %b %d %H:%M:%S %Y", &printable);
if (strlen(timebuf) < TIME_BUF_SIZE) timebuf[strlen(timebuf)] = '\0';
return (timebuf);
}
/* Uses the password passed in the -f(pwfile) argument of the command line.
/* Uses the password passed in the -f(pwfile) argument of the command line.
* After use once, null it out otherwise PKCS11 calls us forever.?
*
*
* Code based on SECU_GetModulePassword from the Mozilla NSS secutils
* imternal librart.
* internal library.
*/
static char *GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg)
{
@ -137,12 +122,12 @@ static char *GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg)
char *pwFile = arg;
if (!pwFile) return 0;
if (retry) return 0; /* no good retrying - file contents will be the same */
if (retry) return 0; /* no good retrying - file contents will be the same */
if (!(fd = PR_Open(pwFile, PR_RDONLY, 0))) return 0;
nb = PR_Read(fd, phrase, sizeof(phrase));
PR_Close(fd);
/* handle the Windows EOL case */
i = 0;
while (phrase[i] != '\r' && phrase[i] != '\n' && i < nb) i++;
@ -164,30 +149,81 @@ char *pr_ctime(PRTime time, char *buf, int size)
return buf;
}
/* A year is leap iff is divisible by 4 but not by 100 or is divisible by 400 */
static int leap_year(int year) {
return ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) ? 1 : 0;
}
/* Computes the day difference among two PRTime's */
static int diff_time_days(PRTime aT, PRTime bT)
{
PRTime am1, one;
PRExplodedTime a, b;
int years, days = 0;
LL_I2L(one, 1);
do {
PR_ExplodeTime(aT, PR_GMTParameters, &a);
PR_ExplodeTime(bT, PR_GMTParameters, &b);
years = a.tm_year - b.tm_year;
if (years < 0) break;
if (years == 0) {
days += (a.tm_yday - b.tm_yday);
} else if (a.tm_year == b.tm_year + 1) {
days += (365 + leap_year(b.tm_year) - b.tm_yday + a.tm_yday);
} else {
LL_SUB(am1, aT, one);
aT = am1;
}
} while (0);
return days;
}
/* Print a warning message that the certificate in 'filename', issued
* to hostname 'hostname', will expire (or has expired). */
static int warning(FILE *out, const char *filename, const char *hostname,
SECCertTimeValidity validity,
SECCertTimeValidity validity,
PRTime start, PRTime end, PRTime now, int quiet)
{
/* Note that filename can be the cert nickname. */
int renew = 1;
char subj[50];
PRTime prtimeDiff;
LL_SUB(prtimeDiff, end, start);
if ( LL_CMP(start, >, now) ) {
switch (validity) {
case secCertTimeNotValidYet:
strcpy(subj, "is not yet valid");
renew = 0;
} else if (LL_EQ(now, end)) {
strcpy(subj, "will expire today");
} else if (LL_EQ(prtimeDiff, 1)) {
sprintf(subj, "will expire tomorrow");
} else if (LL_CMP(prtimeDiff, <, warn_period)) {
sprintf(subj, "will expire on %s", AsciiTime(end));
} else {
return 0; /* nothing to warn about. */
break;
case secCertTimeExpired:
sprintf(subj, "has expired");
break;
case secCertTimeValid:
{
/* days till expiry */
int days = diff_time_days(end, now);
if (days == 0) {
strcpy(subj, "will expire today");
} else if (days == 1) {
sprintf(subj, "will expire tomorrow");
} else if (days < warn_period) {
sprintf(subj, "will expire in %d days", days);
} else {
return 0; /* nothing to warn about. */
}
}
break;
case secCertTimeUndetermined:
default:
/* it will never get here if caller checks validity */
strcpy(subj, "validity could not be decoded from the cert");
renew = 0;
break;
}
if (quiet) return 1;
@ -195,11 +231,11 @@ static int warning(FILE *out, const char *filename, const char *hostname,
fprintf(out, "To: %s\n", warn_address);
fprintf(out, "Subject: The certificate for %s %s\n", hostname, subj);
fputs("\n", out);
fprintf(out,
fprintf(out,
" ################# SSL Certificate Warning ################\n\n");
fprintf(out,
fprintf(out,
" Certificate for hostname '%s', in file (or by nickname):\n"
" %s\n\n",
hostname, filename);
@ -215,10 +251,10 @@ static int warning(FILE *out, const char *filename, const char *hostname,
char *result = pr_ctime(start, until, TIME_SIZE);
assert(result == until);
if (strlen(until) < sizeof(until)) until[strlen(until)] = '\0';
fprintf(out,
fprintf(out,
" The certificate is not valid until %s.\n\n"
" Browsers will not be able to correctly connect to this\n"
" web site using SSL until the certificate becomes valid.\n",
" web site using SSL until the certificate becomes valid.\n",
until);
}
@ -252,30 +288,30 @@ static int get_common_name(CERTCertificate *cert, char *buf, size_t bufsiz)
* issue a warning message if 'quiet' is zero. If quiet is non-zero,
* returns one to indicate that a warning would have been issued, zero
* to indicate no warning would be issued, or -1 if an error
* occurred.
* occurred.
*
* When byNickname is 1 then 'name' is a nickname to search
* for in the database otherwise it's the certificate file.
*/
static int check_cert(const char *name, int byNickname, int quiet)
static int check_cert(const char *name, int byNickname, int quiet)
{
CERTCertificate *cert;
SECCertTimeValidity validity;
PRTime notBefore, notAfter;
char cname[128];
int doWarning = 0;
/* parse the cert */
cert = byNickname
cert = byNickname
? CERT_FindCertByNickname(CERT_GetDefaultCertDB(), (char *)name)
: PEMUTIL_PEM_read_X509(name);
if (cert == NULL) return -1;
/* determine the validity period of the cert. */
validity = CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE);
if (validity == secCertTimeUndetermined) goto cleanup;
/* get times out of the cert */
if (CERT_GetCertTimes(cert, &notBefore, &notAfter)
!= SECSuccess) goto cleanup;
@ -295,7 +331,7 @@ cleanup:
if (cert) CERT_DestroyCertificate(cert);
if (!doWarning) return -1;
return warning(stdout, name, cname, validity,
return warning(stdout, name, cname, validity,
notBefore, notAfter, PR_Now(), quiet);
}
@ -312,8 +348,8 @@ int main(int argc, char **argv)
{ "keydbprexix", required_argument, NULL, 'k' },
{ NULL }
};
char *certDBPrefix = "";
const char *opts = "qp:a:d:w:c:k:";
char *certDBPrefix = "";
char *keyDBPrefix = "";
char *configdir = NULL; /* contains the cert database */
@ -323,8 +359,8 @@ int main(int argc, char **argv)
/* The 'timezone' global is needed to adjust local times from
* mktime() back to UTC: */
tzset();
while ((optc = getopt_long(argc, argv, "qp:a:d:w:c:k:", options, NULL)) != -1) {
while ((optc = getopt_long(argc, argv, opts, options, NULL)) != -1) {
switch (optc) {
case 'q':
quiet = 1;
@ -353,18 +389,18 @@ int main(int argc, char **argv)
break;
}
}
/* NSS initialization */
if (byNickname) {
/* cert in database */
if (NSS_Initialize(configdir, certDBPrefix, keyDBPrefix,
if (NSS_Initialize(configdir, certDBPrefix, keyDBPrefix,
SECMOD_DB, NSS_INIT_READONLY) != SECSuccess) {
return EXIT_FAILURE;
}
/* in case module requires a password */
if (passwordfile) {
PK11_SetPasswordFunc(GetModulePassword);
PK11_SetPasswordFunc(GetModulePassword);
}
} else {
/* cert in a pem file */
@ -372,13 +408,14 @@ int main(int argc, char **argv)
if (!certDir) {
certDir = "/etc/pki/nssdb";
}
if (NSS_Initialize(certDir, certDBPrefix, keyDBPrefix,
if (NSS_Initialize(certDir, certDBPrefix, keyDBPrefix,
SECMOD_DB, NSS_INIT_READONLY) != SECSuccess) {
printf("NSS_Init(\"%s\") failed\n", certDir);
return EXIT_FAILURE;
}
}
/* When byNickname is 1 argv[optind] is a nickname otherwise a filename. */
return check_cert(argv[optind], byNickname, quiet) == 1 ? EXIT_SUCCESS : EXIT_FAILURE;
/* When byNickname is 1 argv[optind] is a nickname otherwise a filename. */
return check_cert(argv[optind], byNickname, quiet) == 1
? EXIT_SUCCESS : EXIT_FAILURE;
}