Blame SOURCES/certwatch.c

b76ddc
/*
b76ddc
   Copyright 2005 Red Hat, Inc.
b76ddc
b76ddc
   This program is free software; you can redistribute it and/or modify
b76ddc
   it under the terms of the GNU General Public License as published by
b76ddc
   the Free Software Foundation; either version 2 of the License, or
b76ddc
   (at your option) any later version.
b76ddc
b76ddc
   This program is distributed in the hope that it will be useful,
b76ddc
   but WITHOUT ANY WARRANTY; without even the implied warranty of
b76ddc
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
b76ddc
   GNU General Public License for more details.
b76ddc
b76ddc
   You should have received a copy of the GNU General Public License
b76ddc
   along with this program; if not, write to the Free Software
b76ddc
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
b76ddc
b76ddc
   In addition, as a special exception, Red Hat, Inc. gives permission
b76ddc
   to link the code of this program with the OpenSSL library (or with
b76ddc
   modified versions of OpenSSL that use the same license as OpenSSL),
b76ddc
   and distribute linked combinations including the two. You must obey
b76ddc
   the GNU General Public License in all respects for all of the code
b76ddc
   used other than OpenSSL. If you modify this file, you may extend
b76ddc
   this exception to your version of the file, but you are not
b76ddc
   obligated to do so. If you do not wish to do so, delete this
b76ddc
   exception statement from your version.
b76ddc
b76ddc
*/
b76ddc
b76ddc
/* ***** BEGIN LICENSE BLOCK *****
b76ddc
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
b76ddc
 *
b76ddc
 * The contents of this file are subject to the Mozilla Public License Version
b76ddc
 * 1.1 (the "License"); you may not use this file except in compliance with
b76ddc
 * the License. You may obtain a copy of the License at
b76ddc
 * http://www.mozilla.org/MPL/
b76ddc
 *
b76ddc
 * Software distributed under the License is distributed on an "AS IS" basis,
b76ddc
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
b76ddc
 * for the specific language governing rights and limitations under the
b76ddc
 * License.
b76ddc
 *
b76ddc
 * The Original Code is the Netscape security libraries.
b76ddc
 *
b76ddc
 * The Initial Developer of the Original Code is
b76ddc
 * Netscape Communications Corporation.
b76ddc
 * Portions created by the Initial Developer are Copyright (C) 1994-2000
b76ddc
 * the Initial Developer. All Rights Reserved.
b76ddc
 *
b76ddc
 * Contributor(s):
b76ddc
 *   Dr Vipul Gupta <vipul.gupta@sun.com>, Sun Microsystems Laboratories
b76ddc
 *
b76ddc
 * Alternatively, the contents of this file may be used under the terms of
b76ddc
 * either the GNU General Public License Version 2 or later (the "GPL"), or
b76ddc
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
b76ddc
 * in which case the provisions of the GPL or the LGPL are applicable instead
b76ddc
 * of those above. If you wish to allow use of your version of this file only
b76ddc
 * under the terms of either the GPL or the LGPL, and not to allow others to
b76ddc
 * use your version of this file under the terms of the MPL, indicate your
b76ddc
 * decision by deleting the provisions above and replace them with the notice
b76ddc
 * and other provisions required by the GPL or the LGPL. If you do not delete
b76ddc
 * the provisions above, a recipient may use your version of this file under
b76ddc
 * the terms of any one of the MPL, the GPL or the LGPL.
b76ddc
 *
b76ddc
 * ***** END LICENSE BLOCK ***** */
b76ddc
b76ddc
b76ddc
/* $Id$ */
b76ddc
b76ddc
/* Certificate expiry warning generation code, based on code from
b76ddc
 * Stronghold.  Joe Orton <jorton@redhat.com> */
b76ddc
b76ddc
/* Replaced usage of OpenSSL with NSS.
b76ddc
 * Elio Maldonado <emaldona@redhat.com> */
b76ddc
b76ddc
#include <nspr.h>
b76ddc
#include <nss.h>
b76ddc
#include <cert.h>
b76ddc
#include <certt.h>
b76ddc
#include <prlong.h>
b76ddc
#include <prtime.h>
b76ddc
#include <pk11func.h>
b76ddc
#include <assert.h>
b76ddc
#include <secmod.h>
b76ddc
#include <base64.h>
b76ddc
#include <seccomon.h>
b76ddc
#include <certt.h>
b76ddc
b76ddc
#include <stdio.h>
b76ddc
#include <string.h>
b76ddc
#include <stdlib.h>
b76ddc
#include <getopt.h>
b76ddc
#include <time.h>
b76ddc
b76ddc
#define TIME_BUF_SIZE 100
b76ddc
b76ddc
/* Return a certificate structure from a pem-encoded cert in a file;
b76ddc
 * or NULL on failure. Semantics similar to the OpenSSL call
b76ddc
 * PEM_read_X509(fp, NULL, NULL, NULL);
b76ddc
 */
b76ddc
extern CERTCertificate *
b76ddc
PEMUTIL_PEM_read_X509(const char *filename);
b76ddc
b76ddc
/* size big enough for formatting time buffer */
b76ddc
#define TIME_SIZE 30
b76ddc
b76ddc
static int warn_period = 30;
b76ddc
static char *warn_address = "root";
b76ddc
b76ddc
/* Uses the password passed in the -f(pwfile) argument of the command line.
b76ddc
 * After use once, null it out otherwise PKCS11 calls us forever.?
b76ddc
 *
b76ddc
 * Code based on SECU_GetModulePassword from the Mozilla NSS secutils
b76ddc
 * internal library.
b76ddc
 */
b76ddc
static char *GetModulePassword(PK11SlotInfo *slot, PRBool retry, void *arg)
b76ddc
{
b76ddc
    int i;
b76ddc
    unsigned char phrase[200];
b76ddc
    PRFileDesc *fd;
b76ddc
    PRInt32 nb;
b76ddc
    char *pwFile = arg;
b76ddc
b76ddc
    if (!pwFile) return 0;
b76ddc
    if (retry) return 0; /* no good retrying - file contents will be the same */
b76ddc
    if (!(fd = PR_Open(pwFile, PR_RDONLY, 0))) return 0;
b76ddc
b76ddc
    nb = PR_Read(fd, phrase, sizeof(phrase));
b76ddc
    PR_Close(fd);
b76ddc
b76ddc
    /* handle the Windows EOL case */
b76ddc
    i = 0;
b76ddc
    while (phrase[i] != '\r' && phrase[i] != '\n' && i < nb) i++;
b76ddc
    phrase[i] = '\0';
b76ddc
    if (nb == 0) return NULL;
b76ddc
b76ddc
    return (char*) PORT_Strdup((char*)phrase);
b76ddc
}
b76ddc
b76ddc
/* Format a PRTime value into a buffer with format "%a %b %d %H:%M:%S %Y";
b76ddc
 * semantics are those of ctime_r(). */
b76ddc
char *pr_ctime(PRTime time, char *buf, int size)
b76ddc
{
b76ddc
    PRUint32 bytesCopied;
b76ddc
    PRExplodedTime et;
b76ddc
    PR_ExplodeTime(time, PR_GMTParameters, &et);
b76ddc
    bytesCopied = PR_FormatTime(buf, size, "%a %b %d %H:%M:%S %Y", &et);
b76ddc
    if (!bytesCopied) return NULL;
b76ddc
    return buf;
b76ddc
}
b76ddc
b76ddc
/* Computes the day difference among two PRTime's */
b76ddc
static int diff_time_days(PRTime aT, PRTime bT)
b76ddc
{
b76ddc
    /* Dividing before substracting to support the desired granularity */
b76ddc
    PRInt64 secs = (aT/PR_USEC_PER_SEC - bT/PR_USEC_PER_SEC);
b76ddc
    return secs / 86400L;
b76ddc
}
b76ddc
b76ddc
/* Print a warning message that the certificate in 'filename', issued
b76ddc
 * to hostname 'hostname', will expire (or has expired). */
b76ddc
static int warning(FILE *out, const char *filename, const char *hostname,
b76ddc
                   SECCertTimeValidity validity,
b76ddc
                   PRTime start, PRTime end, PRTime now, int quiet)
b76ddc
{
b76ddc
    /* Note that filename can be the cert nickname. */
b76ddc
    int renew = 1, days;         /* days till expiry */
b76ddc
    char subj[50];
b76ddc
b76ddc
    switch (validity) {
b76ddc
    case secCertTimeNotValidYet:
b76ddc
        strcpy(subj, "is not yet valid");
b76ddc
        renew = 0;
b76ddc
        break;
b76ddc
    case secCertTimeExpired:
b76ddc
        sprintf(subj, "has expired");
b76ddc
        break;
b76ddc
    case secCertTimeValid:
b76ddc
        days = diff_time_days(end, now);
b76ddc
        if (days == 0) {
b76ddc
            strcpy(subj, "will expire today");
b76ddc
        } else if (days == 1) {
b76ddc
            sprintf(subj, "will expire tomorrow");
b76ddc
        } else if (days < warn_period) {
b76ddc
            sprintf(subj, "will expire in %d days", days);
b76ddc
        } else {
b76ddc
            return 0; /* nothing to warn about. */
b76ddc
        }
b76ddc
        break;
b76ddc
    case secCertTimeUndetermined:
b76ddc
    default:
b76ddc
        /* it will never get here if caller checks validity */
b76ddc
        strcpy(subj, "validity could not be decoded from the cert");
b76ddc
        renew = 0;
b76ddc
        break;
b76ddc
    }
b76ddc
b76ddc
    if (quiet) return 1;
b76ddc
b76ddc
    fprintf(out, "To: %s\n", warn_address);
b76ddc
    fprintf(out, "Subject: The certificate for %s %s\n", hostname, subj);
b76ddc
    fputs("\n", out);
b76ddc
b76ddc
    fprintf(out,
b76ddc
            " ################# SSL Certificate Warning ################\n\n");
b76ddc
b76ddc
    fprintf(out,
b76ddc
            "  Certificate for hostname '%s', in file (or by nickname):\n"
b76ddc
            "     %s\n\n",
b76ddc
            hostname, filename);
b76ddc
b76ddc
    if (renew) {
b76ddc
        fputs("  The certificate needs to be renewed; this can be done\n"
b76ddc
              "  using the 'genkey' program.\n\n"
b76ddc
              "  Browsers will not be able to correctly connect to this\n"
b76ddc
              "  web site using SSL until the certificate is renewed.\n",
b76ddc
              out);
b76ddc
    } else {
b76ddc
        char until[TIME_SIZE];
b76ddc
        char *result = pr_ctime(start, until, TIME_SIZE);
b76ddc
        assert(result == until);
b76ddc
        if (strlen(until) < sizeof(until)) until[strlen(until)] = '\0';
b76ddc
        fprintf(out,
b76ddc
                "  The certificate is not valid until %s.\n\n"
b76ddc
                "  Browsers will not be able to correctly connect to this\n"
b76ddc
                "  web site using SSL until the certificate becomes valid.\n",
b76ddc
                until);
b76ddc
    }
b76ddc
b76ddc
    fputs("\n"
b76ddc
          " ##########################################################\n"
b76ddc
          "                                  Generated by certwatch(1)\n\n",
b76ddc
          out);
b76ddc
    return 1;
b76ddc
}
b76ddc
b76ddc
/* Extract the common name of 'cert' into 'buf'. */
b76ddc
static int get_common_name(CERTCertificate *cert, char *buf, size_t bufsiz)
b76ddc
{
b76ddc
    /* FIXME --- truncating names with spaces */
b76ddc
    size_t namelen;
b76ddc
    char *name = CERT_GetCommonName(&cert->subject);
b76ddc
b76ddc
    if (!name) return -1;
b76ddc
b76ddc
    namelen = strlen(name);
b76ddc
    if (bufsiz < namelen+1) return -1;
b76ddc
b76ddc
    strncpy(buf, name, namelen);
b76ddc
    buf[namelen] = '\0';
b76ddc
    PORT_Free(name);
b76ddc
b76ddc
    return 0;
b76ddc
}
b76ddc
b76ddc
/* Check whether the certificate in filename 'name' has expired;
b76ddc
 * issue a warning message if 'quiet' is zero.  If quiet is non-zero,
b76ddc
 * returns one to indicate that a warning would have been issued, zero
b76ddc
 * to indicate no warning would be issued, or -1 if an error
b76ddc
 * occurred.
b76ddc
 *
b76ddc
 * When byNickname is 1 then 'name' is a nickname to search
b76ddc
 * for in the database otherwise it's the certificate file.
b76ddc
 */
b76ddc
static int check_cert(const char *name, int byNickname, int quiet)
b76ddc
{
b76ddc
    CERTCertificate *cert;
b76ddc
    SECCertTimeValidity validity;
b76ddc
    PRTime notBefore, notAfter;
b76ddc
    char cname[128];
b76ddc
b76ddc
    int doWarning = 0;
b76ddc
b76ddc
    /* parse the cert */
b76ddc
    cert = byNickname
b76ddc
        ? CERT_FindCertByNickname(CERT_GetDefaultCertDB(), (char *)name)
b76ddc
        : PEMUTIL_PEM_read_X509(name);
b76ddc
    if (cert == NULL) return -1;
b76ddc
b76ddc
    /* determine the validity period of the cert. */
b76ddc
    validity = CERT_CheckCertValidTimes(cert, PR_Now(), PR_FALSE);
b76ddc
    if (validity == secCertTimeUndetermined) goto cleanup;
b76ddc
b76ddc
    /* get times out of the cert */
b76ddc
    if (CERT_GetCertTimes(cert, &notBefore, &notAfter)
b76ddc
        != SECSuccess) goto cleanup;
b76ddc
b76ddc
    /* find the subject's commonName attribute */
b76ddc
    if (get_common_name(cert, cname, sizeof cname))
b76ddc
        goto cleanup;
b76ddc
b76ddc
    /* don't warn about the automatically generated certificate */
b76ddc
    if (strcmp(cname, "localhost") == 0 ||
b76ddc
        strcmp(cname, "localhost.localdomain") == 0)
b76ddc
        goto cleanup;
b76ddc
b76ddc
    doWarning = 1; /* ok so far, may do the warning */
b76ddc
b76ddc
cleanup:
b76ddc
    if (cert) CERT_DestroyCertificate(cert);
b76ddc
    if (!doWarning) return -1;
b76ddc
b76ddc
    return warning(stdout, name, cname, validity,
b76ddc
                   notBefore, notAfter, PR_Now(), quiet);
b76ddc
}
b76ddc
b76ddc
int main(int argc, char **argv)
b76ddc
{
b76ddc
    int optc, quiet = 0;
b76ddc
    const char *shortopts = "qp:a:d:w:c:k:";
b76ddc
    static const struct option longopts[] = {
b76ddc
        { "quiet", no_argument, NULL, 'q' },
b76ddc
        { "period", required_argument, NULL, 'p' },
b76ddc
        { "address", required_argument, NULL, 'a' },
b76ddc
        { "configdir", required_argument, NULL, 'd' },
b76ddc
        { "passwordfile", required_argument, NULL, 'w' },
b76ddc
        { "certdbprefix", required_argument, NULL, 'c' },
b76ddc
        { "keydbprexix", required_argument, NULL, 'k' },
b76ddc
        { NULL }
b76ddc
    };
b76ddc
    char *certDBPrefix = "";
b76ddc
    char *keyDBPrefix = "";
b76ddc
    char *configdir = NULL;    /* contains the cert database */
b76ddc
    char *passwordfile = NULL; /* module password file */
b76ddc
    int byNickname = 0;        /* whether to search by nickname */
b76ddc
b76ddc
    /* The 'timezone' global is needed to adjust local times from
b76ddc
     * mktime() back to UTC: */
b76ddc
    tzset();
b76ddc
b76ddc
    while ((optc = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1) {
b76ddc
        switch (optc) {
b76ddc
        case 'q':
b76ddc
            quiet = 1;
b76ddc
            break;
b76ddc
        case 'p':
b76ddc
            warn_period = atoi(optarg);
b76ddc
            break;
b76ddc
        case 'a':
b76ddc
            warn_address = strdup(optarg);
b76ddc
            break;
b76ddc
        case 'd':
b76ddc
            configdir = strdup(optarg);
b76ddc
            byNickname = 1;
b76ddc
            break;
b76ddc
        case 'w':
b76ddc
            passwordfile = strdup(optarg);
b76ddc
            break;
b76ddc
        case 'c':
b76ddc
            certDBPrefix = strdup(optarg);
b76ddc
            break;
b76ddc
        case 'k':
b76ddc
            keyDBPrefix = strdup(optarg);
b76ddc
            break;
b76ddc
        default:
b76ddc
            exit(2);
b76ddc
            break;
b76ddc
        }
b76ddc
    }
b76ddc
b76ddc
    /* NSS initialization */
b76ddc
b76ddc
    if (byNickname) {
b76ddc
        /* cert in database */
b76ddc
        if (NSS_Initialize(configdir, certDBPrefix, keyDBPrefix,
b76ddc
                   SECMOD_DB, NSS_INIT_READONLY) != SECSuccess) {
b76ddc
            return EXIT_FAILURE;
b76ddc
        }
b76ddc
        /* in case module requires a password */
b76ddc
        if (passwordfile) {
b76ddc
            PK11_SetPasswordFunc(GetModulePassword);
b76ddc
        }
b76ddc
    } else {
b76ddc
        /* cert in a pem file */
b76ddc
        char *certDir = getenv("SSL_DIR"); /* Look in $SSL_DIR */
b76ddc
        if (!certDir) {
b76ddc
            certDir = "/etc/pki/nssdb";
b76ddc
        }
b76ddc
        if (NSS_Initialize(certDir, certDBPrefix, keyDBPrefix,
b76ddc
                   SECMOD_DB, NSS_INIT_READONLY) != SECSuccess) {
b76ddc
            printf("NSS_Init(\"%s\") failed\n", certDir);
b76ddc
            return EXIT_FAILURE;
b76ddc
        }
b76ddc
    }
b76ddc
b76ddc
    /* When byNickname is 1 argv[optind] is a nickname otherwise a filename. */
b76ddc
    return check_cert(argv[optind], byNickname, quiet) == 1
b76ddc
                      ? EXIT_SUCCESS : EXIT_FAILURE;
b76ddc
}