Blame SOURCES/certwatch.c

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