WvStreams
wvcrl.cc
00001 /* -*- Mode: C++ -*-
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2005 Net Integration Technologies, Inc.
00004  *
00005  * X.509v3 CRL management classes.
00006  */
00007 
00008 #include <openssl/x509v3.h>
00009 #include <openssl/pem.h>
00010 
00011 #include "wvcrl.h"
00012 #include "wvx509mgr.h"
00013 #include "wvbase64.h"
00014 
00015 
00016 static const char * warning_str_get = "Tried to determine %s, but CRL is blank!\n";
00017 #define CHECK_CRL_EXISTS_GET(x, y)                                      \
00018     if (!crl) {                                                         \
00019         debug(WvLog::Warning, warning_str_get, x);                      \
00020         return y;                                                       \
00021     }
00022 
00023 
00024 static ASN1_INTEGER * serial_to_int(WvStringParm serial)
00025 {
00026     if (!!serial)
00027     {
00028         BIGNUM *bn = NULL;
00029         BN_dec2bn(&bn, serial);
00030         ASN1_INTEGER *retval = ASN1_INTEGER_new();
00031         retval = BN_to_ASN1_INTEGER(bn, retval);
00032         BN_free(bn);
00033         return retval;
00034     }
00035 
00036     return NULL;
00037 }
00038 
00039 
00040 WvCRL::WvCRL()
00041     : debug("X509 CRL", WvLog::Debug5)
00042 {
00043     crl = NULL;
00044 }
00045 
00046 
00047 WvCRL::WvCRL(const WvX509Mgr &ca)
00048     : debug("X509 CRL", WvLog::Debug5)
00049 {
00050     assert(crl = X509_CRL_new());
00051 
00052     // Use Version 2 CRLs - Of COURSE that means
00053     // to set it to 1 here... grumble..
00054     X509_CRL_set_version(crl, 1);
00055     X509_CRL_set_issuer_name(crl, X509_get_issuer_name(ca.cert));
00056 
00057     // most of this copied from wvx509.cc, sigh
00058     ASN1_OCTET_STRING *ikeyid = NULL;
00059     X509_EXTENSION *ext;
00060     int i = X509_get_ext_by_NID(ca.cert, NID_subject_key_identifier, -1);
00061     if ((i >= 0) && (ext = X509_get_ext(ca.cert, i)))
00062         ikeyid = static_cast<ASN1_OCTET_STRING *>(X509V3_EXT_d2i(ext));
00063 
00064     if (ikeyid)
00065     {
00066         AUTHORITY_KEYID *akeyid = AUTHORITY_KEYID_new();
00067         akeyid->issuer = NULL;
00068         akeyid->serial = NULL;
00069         akeyid->keyid = ikeyid;
00070         ext = X509V3_EXT_i2d(NID_authority_key_identifier, 0, akeyid);
00071         X509_CRL_add_ext(crl, ext, -1);
00072         X509_EXTENSION_free(ext); 
00073         AUTHORITY_KEYID_free(akeyid);
00074     }
00075 
00076     // Sign the CRL and set some expiration params
00077     ca.signcrl(*this);
00078 }
00079 
00080 
00081 WvCRL::~WvCRL()
00082 {
00083     debug("Deleting.\n");
00084     if (crl)
00085         X509_CRL_free(crl);
00086 }
00087 
00088 
00089 bool WvCRL::isok() const
00090 {
00091     return crl;
00092 }
00093     
00094 
00095 bool WvCRL::signedbyca(const WvX509 &cacert) const
00096 {
00097     CHECK_CRL_EXISTS_GET("if CRL is signed by CA", false);
00098 
00099     EVP_PKEY *pkey = X509_get_pubkey(cacert.cert);
00100     int result = X509_CRL_verify(crl, pkey);
00101     EVP_PKEY_free(pkey);
00102     if (result < 0)
00103     {
00104         debug("There was an error (%s) determining whether or not we were "
00105               "signed by CA '%s'\n", wvssl_errstr(), cacert.get_subject());
00106         return false;
00107     }
00108     bool issigned = (result > 0);
00109 
00110     debug("CRL was%s signed by CA %s\n", issigned ? "" : " NOT", 
00111           cacert.get_subject());
00112 
00113     return issigned;
00114 }
00115 
00116 
00117 bool WvCRL::issuedbyca(const WvX509 &cacert) const
00118 {
00119     CHECK_CRL_EXISTS_GET("if CRL is issued by CA", false);
00120 
00121     WvString name = get_issuer();
00122     bool issued = (cacert.get_subject() == name);
00123     if (issued)
00124         debug("CRL issuer '%s' matches subject '%s' of cert. We can say "
00125               "that it appears to be issued by this CA.\n",
00126               name, cacert.get_subject());
00127     else
00128         debug("CRL issuer '%s' doesn't match subject '%s' of cert. Doesn't "
00129               "appear to be issued by this CA.\n", name, 
00130               cacert.get_subject());
00131 
00132     return issued;
00133 }
00134 
00135 
00136 bool WvCRL::expired() const
00137 {
00138     CHECK_CRL_EXISTS_GET("if CRL has expired", false);
00139 
00140     if (X509_cmp_current_time(X509_CRL_get_nextUpdate(crl)) < 0)
00141     {
00142         debug("CRL appears to be expired.\n");
00143         return true;
00144     }
00145 
00146     debug("CRL appears not to be expired.\n");
00147     return false;
00148 }
00149 
00150 
00151 bool WvCRL::has_critical_extensions() const
00152 {
00153     CHECK_CRL_EXISTS_GET("if CRL has critical extensions", false);
00154 
00155     int critical = X509_CRL_get_ext_by_critical(crl, 1, 0);
00156     return (critical > 0);
00157 }
00158 
00159 
00160 WvString WvCRL::get_aki() const
00161 {
00162     CHECK_CRL_EXISTS_GET("CRL's AKI", WvString::null);
00163 
00164     AUTHORITY_KEYID *aki = NULL;
00165     int i;
00166 
00167     aki = static_cast<AUTHORITY_KEYID *>(
00168         X509_CRL_get_ext_d2i(crl, NID_authority_key_identifier, 
00169                              &i, NULL));
00170     if (aki)
00171     {
00172         char *tmp = hex_to_string(aki->keyid->data, aki->keyid->length); 
00173         WvString str(tmp);
00174         
00175         OPENSSL_free(tmp);
00176         AUTHORITY_KEYID_free(aki);
00177        
00178         return str;
00179     }
00180 
00181     return WvString::null;
00182 }
00183 
00184 
00185 WvString WvCRL::get_issuer() const
00186 { 
00187     CHECK_CRL_EXISTS_GET("CRL's issuer", WvString::null);
00188 
00189     char *name = X509_NAME_oneline(X509_CRL_get_issuer(crl), 0, 0);
00190     WvString retval(name);
00191     OPENSSL_free(name);
00192 
00193     return retval;
00194 }
00195 
00196 
00197 WvString WvCRL::encode(const DumpMode mode) const
00198 {
00199     WvDynBuf retval;
00200     encode(mode, retval);
00201 
00202     return retval.getstr();
00203 }
00204 
00205 
00206 void WvCRL::encode(const DumpMode mode, WvBuf &buf) const
00207 {
00208     if (mode == CRLFileDER || mode == CRLFilePEM)
00209         return; // file modes are no ops with encode
00210 
00211     if (!crl)
00212     {
00213         debug(WvLog::Warning, "Tried to encode CRL, but CRL is blank!\n");
00214         return;
00215     }
00216 
00217     BIO *bufbio = BIO_new(BIO_s_mem());
00218     BUF_MEM *bm;
00219     switch (mode)
00220     {
00221     case CRLPEM:
00222         debug("Dumping CRL in PEM format.\n");
00223         PEM_write_bio_X509_CRL(bufbio, crl);
00224         break;
00225     case CRLDER:
00226         debug("Dumping CRL in DER format.\n");
00227         i2d_X509_CRL_bio(bufbio, crl);
00228         break;
00229     default:
00230         debug("Tried to dump CRL in unknown format!\n");
00231         break;
00232     }
00233     
00234     BIO_get_mem_ptr(bufbio, &bm);
00235     buf.put(bm->data, bm->length);
00236     BIO_free(bufbio);
00237 }
00238 
00239 
00240 void WvCRL::decode(const DumpMode mode, WvStringParm str)
00241 {
00242     if (crl)
00243     {
00244         debug("Replacing already existant CRL.\n");
00245         X509_CRL_free(crl);
00246         crl = NULL;
00247     }
00248 
00249     if (mode == CRLFileDER)
00250     {
00251         BIO *bio = BIO_new(BIO_s_file());
00252         
00253         if (BIO_read_filename(bio, str.cstr()) <= 0)
00254         {
00255             debug(WvLog::Warning, "Import CRL from '%s': %s\n", 
00256                   str, wvssl_errstr());
00257             BIO_free(bio);
00258             return;
00259         }
00260         
00261         if (!(crl = d2i_X509_CRL_bio(bio, NULL)))
00262             debug(WvLog::Warning, "Read CRL from '%s': %s\n",
00263                   str, wvssl_errstr());
00264         
00265         BIO_free(bio);
00266         return;
00267     }
00268     else if (mode == CRLFilePEM)
00269     {
00270         FILE * fp = fopen(str, "rb");
00271         if (!fp)
00272         {
00273             int errnum = errno;
00274             debug(WvLog::Warning,
00275                   "open '%s': %s\n", 
00276                   str, strerror(errnum));
00277             return;
00278         }
00279 
00280         if (!(crl = PEM_read_X509_CRL(fp, NULL, NULL, NULL)))
00281             debug(WvLog::Warning, "Import CRL from '%s': %s\n",
00282                   str, wvssl_errstr());
00283         
00284         fclose(fp);
00285         return;
00286     }
00287 
00288     // we use the buffer decode functions for everything else
00289     WvDynBuf buf;
00290     buf.putstr(str);
00291     decode(mode, buf);
00292 }
00293 
00294 
00295 void WvCRL::decode(const DumpMode mode, WvBuf &buf)
00296 {
00297     if (crl)
00298     {
00299         debug("Replacing already existant CRL.\n");
00300         X509_CRL_free(crl);
00301         crl = NULL;
00302     }
00303 
00304     if (mode == CRLFileDER || mode == CRLFilePEM)
00305     {
00306         decode(mode, buf.getstr());
00307         return;
00308     }
00309 
00310     BIO *bufbio = BIO_new(BIO_s_mem());
00311     BIO_write(bufbio, buf.get(buf.used()), buf.used());
00312 
00313     if (mode == CRLPEM)
00314     {
00315         debug("Decoding CRL from PEM format.\n");       
00316         crl = PEM_read_bio_X509_CRL(bufbio, NULL, NULL, NULL);
00317     }
00318     else if (mode == CRLDER)
00319     {
00320         debug("Decoding CRL from DER format.\n");
00321         crl = d2i_X509_CRL_bio(bufbio, NULL);
00322     }
00323     else
00324         debug(WvLog::Warning, "Attempted to decode unknown format.\n");
00325 
00326     if (!crl)
00327         debug(WvLog::Warning, "Couldn't decode CRL.\n");
00328 
00329     BIO_free(bufbio);
00330 }
00331 
00332 
00333 bool WvCRL::isrevoked(const WvX509 &cert) const
00334 {
00335     if (cert.cert)
00336     {
00337         debug("Checking to see if certificate with name '%s' and serial "
00338               "number '%s' is revoked.\n", cert.get_subject(), 
00339               cert.get_serial());
00340         return isrevoked(cert.get_serial());
00341     }
00342     else
00343     {
00344         debug(WvLog::Error, "Given certificate to check revocation status, "
00345               "but certificate is blank. Declining.\n");
00346         return true;
00347     }
00348 }
00349 
00350 
00351 bool WvCRL::isrevoked(WvStringParm serial_number) const
00352 {
00353     CHECK_CRL_EXISTS_GET("if certificate is revoked in CRL", false);
00354 
00355     if (!!serial_number)
00356     {
00357         ASN1_INTEGER *serial = serial_to_int(serial_number);
00358         if (serial)
00359         {
00360             X509_REVOKED mayberevoked;
00361             mayberevoked.serialNumber = serial;
00362             if (crl->crl->revoked)
00363             {
00364                 int idx = sk_X509_REVOKED_find(crl->crl->revoked, 
00365                                                &mayberevoked);
00366                 ASN1_INTEGER_free(serial);
00367                 if (idx >= 0)
00368                 {
00369                     debug("Certificate is revoked.\n");
00370                     return true;
00371                 }
00372                 else
00373                 {
00374                     debug("Certificate is not revoked.\n");
00375                     return false;
00376                 }
00377             }
00378             else
00379             {
00380                 ASN1_INTEGER_free(serial);
00381                 debug("CRL does not have revoked list.\n");
00382                 return false;
00383             }
00384             
00385         }
00386         else
00387             debug(WvLog::Warning, "Can't convert serial number to ASN1 format. "
00388                   "Saying it's not revoked.\n");
00389     }
00390     else
00391         debug(WvLog::Warning, "Serial number for certificate is blank.\n");
00392 
00393     debug("Certificate is not revoked (or could not determine whether it "
00394           "was).\n");
00395     return false;
00396 }
00397     
00398 
00399 WvCRL::Valid WvCRL::validate(const WvX509 &cacert) const
00400 {
00401     if (!issuedbyca(cacert))
00402         return NOT_THIS_CA;
00403     
00404     if (!signedbyca(cacert))
00405         return NO_VALID_SIGNATURE;
00406 
00407     if (expired())
00408         return EXPIRED;
00409 
00410     // neither we or openssl handles any critical extensions yet
00411     if (has_critical_extensions())
00412     {
00413         debug("CRL has unhandled critical extensions.\n");
00414         return UNHANDLED_CRITICAL_EXTENSIONS;
00415     }
00416 
00417     return VALID;
00418 }
00419 
00420 
00421 int WvCRL::numcerts() const
00422 {
00423     CHECK_CRL_EXISTS_GET("number of certificates in CRL", 0);
00424 
00425     STACK_OF(X509_REVOKED) *rev;
00426     rev = X509_CRL_get_REVOKED(crl);
00427     int certcount = sk_X509_REVOKED_num(rev);
00428     
00429     if (certcount < 0)
00430         certcount = 0;
00431 
00432     return certcount;
00433 }
00434 
00435 
00436 void WvCRL::addcert(const WvX509 &cert)
00437 {
00438     if (!crl)
00439     {
00440         debug(WvLog::Warning, "Tried to add certificate to CRL, but CRL is "
00441               "blank!\n");
00442         return;
00443     }
00444 
00445     if (cert.isok())
00446     {
00447         ASN1_INTEGER *serial = serial_to_int(cert.get_serial());
00448         X509_REVOKED *revoked = X509_REVOKED_new();
00449         ASN1_GENERALIZEDTIME *now = ASN1_GENERALIZEDTIME_new();
00450         X509_REVOKED_set_serialNumber(revoked, serial);
00451         X509_gmtime_adj(now, 0);
00452         X509_REVOKED_set_revocationDate(revoked, now);
00453         // FIXME: We don't deal with the reason here...
00454         X509_CRL_add0_revoked(crl, revoked);
00455         ASN1_GENERALIZEDTIME_free(now);
00456         ASN1_INTEGER_free(serial);
00457     }
00458     else
00459     {
00460         debug(WvLog::Warning, "Tried to add a certificate to the CRL, but "
00461               "certificate is either bad or broken.\n");
00462     }
00463 }
00464