Fix bugzilla 473860: certwatch does not warn of expiring certificates
This commit is contained in:
parent
13582087e9
commit
835af071b0
161
certwatch.c
161
certwatch.c
@ -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, ¬Before, ¬After)
|
||||
!= 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user