nsscommon.cxx - systemtap
Global variables defined
Functions defined
Macros defined
Source code
#include "config.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cerrno>
#include <cstdio>
#include <cassert>
extern "C" {
#include <time.h>
#include <termios.h>
#include <unistd.h>
#include <glob.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <nss.h>
#include <nspr.h>
#include <ssl.h>
#include <prerror.h>
#include <secerr.h>
#include <sslerr.h>
#include <cryptohi.h>
#include <keyhi.h>
#include <secder.h>
#include <cert.h>
}
#include "nsscommon.h"
#include "util.h"
using namespace std;
const char *
server_cert_nickname ()
{
return (const char *)"stap-server";
}
string
add_cert_db_prefix (const string &db_path) {
#if (NSS_VMAJOR > 3) || (NSS_VMAJOR == 3 && NSS_VMINOR >= 12)
if (db_path.find (':') == string::npos)
return string("dbm:") + db_path;
#endif
return db_path;
}
string
server_cert_db_path ()
{
string data_path;
const char* s_d = getenv ("SYSTEMTAP_DIR");
if (s_d != NULL)
data_path = s_d;
else
data_path = get_home_directory() + string("/.systemtap");
return data_path + "/ssl/server";
}
string
local_client_cert_db_path ()
{
string data_path;
const char* s_d = getenv ("SYSTEMTAP_DIR");
if (s_d != NULL)
data_path = s_d;
else
data_path = get_home_directory() + string("/.systemtap");
return data_path + "/ssl/client";
}
void
nsscommon_error (const string &msg, int logit)
{
nsscommon_error (msg.c_str (), logit);
}
static ofstream logfile;
void
start_log (const char *arg)
{
if (logfile.is_open ())
logfile.close ();
logfile.open (arg, ios_base::app);
if (! logfile.good ())
nsscommon_error (_F("Could not open log file %s", arg));
}
bool
log_ok ()
{
return logfile.good ();
}
void
log (const string &msg)
{
time_t now;
time (& now);
string nowStr = ctime (& now);
nowStr.erase (nowStr.size () - 1, 1);
if (logfile.good ())
logfile << nowStr << ": " << msg << endl << flush;
else
clog << nowStr << ": " << msg << endl << flush;
}
void
end_log ()
{
if (logfile.is_open ())
logfile.close ();
}
extern "C"
void
nssError (void)
{
PRErrorCode errorNumber = PR_GetError ();
if (errorNumber >= PR_NSPR_ERROR_BASE && errorNumber <= PR_MAX_ERROR)
{
nsscommon_error (_F("(%d) %s", errorNumber, PR_ErrorToString (errorNumber, PR_LANGUAGE_EN)));
return;
}
const char *errorText;
switch (errorNumber) {
default: errorText = "Unknown error"; break;
#define NSSYERROR(code,msg) case code: errorText = msg; break
#include "stapsslerr.h"
#undef NSSYERROR
}
nsscommon_error (_F("(%d) %s", errorNumber, errorText));
}
extern "C"
SECStatus
nssInit (const char *db_path, int readWrite, int issueMessage)
{
SECStatus secStatus;
string full_db_path = add_cert_db_prefix (db_path);
db_path = full_db_path.c_str();
if (readWrite)
secStatus = NSS_InitReadWrite (db_path);
else
secStatus = NSS_Init (db_path);
if (secStatus != SECSuccess && issueMessage)
{
nsscommon_error (_F("Error initializing NSS for %s", db_path));
nssError ();
}
return secStatus;
}
extern "C"
void
nssCleanup (const char *db_path)
{
if (! NSS_IsInitialized ())
{
if (db_path)
{
string full_db_path = add_cert_db_prefix (db_path);
db_path = full_db_path.c_str();
nsscommon_error (_F("WARNING: Attempt to shutdown NSS for database %s, which was never initialized", db_path));
}
return;
}
if (NSS_Shutdown () != SECSuccess)
{
if (db_path)
{
string full_db_path = add_cert_db_prefix (db_path);
db_path = full_db_path.c_str();
nsscommon_error (_F("Unable to shutdown NSS for database %s", db_path));
}
else
nsscommon_error (_("Unable to shutdown NSS"));
nssError ();
}
}
static void
echoOff(int fd)
{
if (isatty(fd)) {
struct termios tio;
tcgetattr(fd, &tio);
tio.c_lflag &= ~ECHO;
tcsetattr(fd, TCSAFLUSH, &tio);
}
}
static void
echoOn(int fd)
{
if (isatty(fd)) {
struct termios tio;
tcgetattr(fd, &tio);
tio.c_lflag |= ECHO;
tcsetattr(fd, TCSAFLUSH, &tio);
}
}
extern "C"
char *
nssPasswordCallback (PK11SlotInfo *info __attribute ((unused)), PRBool retry, void *arg)
{
static int retries = 0;
#define PW_MAX 200
char* password = NULL;
char* password_ret = NULL;
const char *dbname ;
int infd;
int isTTY;
if (! retry)
{
retries = 0;
}
else
{
if (++retries > 2)
return NULL; }
infd = fileno (stdin);
isTTY = isatty (infd);
if (! isTTY)
{
nsscommon_error (_("Cannot prompt for certificate database password. stdin is not a tty"));
return NULL;
}
password = (char *)PORT_Alloc (PW_MAX);
if (! password)
{
nssError ();
return NULL;
}
dbname = (const char *)arg;
cerr << _F("Password for certificate database in %s: ", dbname) << flush;
echoOff (infd);
password_ret = fgets (password, PW_MAX, stdin);
cerr << endl << flush;
echoOn(infd);
if (password_ret)
*strchrnul (password, '\n') = '\0';
else
PORT_Free (password);
return password_ret;
}
static int
create_server_cert_db (const char *db_path)
{
return create_dir (db_path, 0755);
}
static int
create_client_cert_db (const char *db_path)
{
return create_server_cert_db (db_path);
}
static int
clean_cert_db (const string &db_path)
{
glob_t globbuf;
string filespec = db_path + "/*";
int r = glob (filespec.c_str (), 0, NULL, & globbuf);
if (r == GLOB_NOSPACE || r == GLOB_ABORTED)
nsscommon_error (_F("Could not search certificate database directory %s", db_path.c_str ()));
else if (r != GLOB_NOMATCH)
{
for (unsigned i = 0; i < globbuf.gl_pathc; ++i)
{
if (remove_file_or_dir (globbuf.gl_pathv[i]) != 0)
nsscommon_error (_F("Could not remove %s", globbuf.gl_pathv[i]));
}
}
if (remove_file_or_dir (db_path.c_str ()) != 0)
{
nsscommon_error (_F("Could not remove certificate database directory %s\n%s",
db_path.c_str (), strerror (errno)));
return 1;
}
return 0;
}
static int
init_password (PK11SlotInfo *slot, const string &db_path, bool use_password)
{
SECStatus secStatus;
if (use_password)
{
char *pw1 = 0;
int attempts;
const int max_attempts = 3;
for (attempts = 0; attempts < max_attempts; ++attempts)
{
pw1 = nssPasswordCallback (slot, false, (void*)db_path.c_str ());
if (! pw1)
continue;
cerr << "Confirm ";
bool match = false;
char *pw2 = nssPasswordCallback (slot, false, (void*)db_path.c_str ());
if (pw2)
{
if (strcmp (pw1, pw2) == 0)
match = true;
else
nsscommon_error (_("Passwords do not match"));
memset (pw2, 0, strlen (pw2));
PORT_Free (pw2);
}
if (match)
break;
memset (pw1, 0, strlen (pw1));
PORT_Free (pw1);
}
if (attempts >= max_attempts)
{
nsscommon_error (_("Too many password attempts"));
return 1;
}
secStatus = PK11_InitPin (slot, 0, pw1);
memset (pw1, 0, strlen (pw1));
PORT_Free (pw1);
}
else
secStatus = PK11_InitPin (slot, 0, 0);
if (secStatus != SECSuccess)
{
nsscommon_error (_F("Could not initialize pin for certificate database %s", db_path.c_str()));
nssError ();
return 1;
}
return 0;
}
static SECKEYPrivateKey *
generate_private_key (const string &db_path, PK11SlotInfo *slot, SECKEYPublicKey **pubkeyp)
{
if (PK11_Authenticate (slot, PR_TRUE, 0) != SECSuccess)
{
nsscommon_error (_F("Unable to authenticate the default slot for certificate database %s",
db_path.c_str ()));
nssError ();
return 0;
}
TODO srand (time (NULL));
char randbuf[64];
for (unsigned i = 0; i < sizeof (randbuf); ++i)
randbuf[i] = rand ();
PK11_RandomUpdate (randbuf, sizeof (randbuf));
memset (randbuf, 0, sizeof (randbuf));
PK11RSAGenParams rsaparams;
rsaparams.keySizeInBits = 1024;
rsaparams.pe = 0x010001;
CK_MECHANISM_TYPE mechanism = CKM_RSA_PKCS_KEY_PAIR_GEN;
SECKEYPrivateKey *privKey = PK11_GenerateKeyPair (slot, mechanism, & rsaparams, pubkeyp,
PR_TRUE , PR_TRUE ,
0);
if (! privKey)
{
nsscommon_error (_("Unable to generate public/private key pair"));
nssError ();
}
return privKey;
}
static CERTCertificateRequest *
generate_cert_request (SECKEYPublicKey *pubk, CERTName *subject)
{
CERTSubjectPublicKeyInfo *spki = SECKEY_CreateSubjectPublicKeyInfo (pubk);
if (! spki)
{
nsscommon_error (_("Unable to create subject public key info for certificate request"));
nssError ();
return 0;
}
CERTCertificateRequest *cr = CERT_CreateCertificateRequest (subject, spki, 0);
SECKEY_DestroySubjectPublicKeyInfo (spki);
if (! cr)
{
nsscommon_error (_("Unable to create certificate request"));
nssError ();
}
return cr;
}
static CERTCertificate *
create_cert (CERTCertificateRequest *certReq, const string &dnsNames)
{
PRTime now = PR_Now ();
PRExplodedTime printableTime;
PR_ExplodeTime (now, PR_GMTParameters, & printableTime);
printableTime.tm_month += 12;
PRTime after = PR_ImplodeTime (& printableTime);
CERTValidity *validity = CERT_CreateValidity (now, after);
if (! validity)
{
nsscommon_error (_("Unable to create certificate validity dates"));
nssError ();
return 0;
}
PRTime serialNumber = now >> 19;
CERTCertificate *cert = CERT_CreateCertificate (serialNumber, & certReq->subject, validity,
certReq);
CERT_DestroyValidity (validity);
if (! cert)
{
nsscommon_error (_("Unable to create certificate"));
nssError ();
return 0;
}
SECStatus secStatus = SECSuccess;
unsigned char keyUsage = 0x0;
PRArenaPool *arena = 0;
void *extHandle = CERT_StartCertExtensions (cert);
if (! extHandle)
{
nsscommon_error (_("Unable to allocate certificate extensions"));
nssError ();
goto error;
}
keyUsage |= (0x80 >> 1); keyUsage |= (0x80 >> 3); keyUsage |= (0x80 >> 7);
SECItem bitStringValue;
bitStringValue.data = & keyUsage;
bitStringValue.len = 1;
secStatus = CERT_EncodeAndAddBitStrExtension (extHandle,
SEC_OID_NS_CERT_EXT_CERT_TYPE,
& bitStringValue, PR_TRUE);
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable to encode certificate type extensions"));
nssError ();
goto error;
}
if (! dnsNames.empty ())
{
arena = PORT_NewArena (DER_DEFAULT_CHUNKSIZE);
if (! arena)
{
nsscommon_error (_("Unable to allocate alternate DNS name extension for certificate"));
goto error;
}
CERTGeneralName *nameList = 0;
CERTGeneralName *current = 0;
PRCList *prev = 0;
vector<string>components;
tokenize (dnsNames, components, ",");
for (unsigned i = 0; i < components.size (); ++i)
{
char *tbuf = (char *)PORT_ArenaAlloc (arena, components[i].size () + 1);
strcpy (tbuf, components[i].c_str ());
current = (CERTGeneralName *)PORT_ZAlloc (sizeof (CERTGeneralName));
if (! current)
{
nsscommon_error (_("Unable to allocate alternate DNS name extension for certificate"));
goto error;
}
if (prev)
{
current->l.prev = prev;
prev->next = & current->l;
}
else
nameList = current;
current->type = certDNSName;
current->name.other.data = (unsigned char *)tbuf;
current->name.other.len = strlen (tbuf);
prev = & current->l;
}
if (nameList)
{
nameList->l.prev = prev;
current->l.next = & nameList->l;
SECItem item;
secStatus = CERT_EncodeAltNameExtension (arena, nameList, & item);
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable to encode alternate DNS name extension for certificate"));
nssError ();
goto error;
}
secStatus = CERT_AddExtension(extHandle,
SEC_OID_X509_SUBJECT_ALT_NAME,
& item, PR_FALSE, PR_TRUE);
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable to add alternate DNS name extension for certificate"));
nssError ();
goto error;
}
}
}
assert (certReq->attributes != NULL);
assert (certReq->attributes[0] == NULL);
secStatus = CERT_FinishExtensions (extHandle);
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable to complete alternate DNS name extension for certificate"));
nssError ();
goto error;
}
return cert;
error:
if (arena)
PORT_FreeArena (arena, PR_FALSE);
CERT_DestroyCertificate (cert);
return 0;
}
static SECItem *
sign_cert (CERTCertificate *cert, SECKEYPrivateKey *privKey)
{
SECOidTag algID = SEC_GetSignatureAlgorithmOidTag (privKey->keyType,
SEC_OID_UNKNOWN);
if (algID == SEC_OID_UNKNOWN)
{
nsscommon_error (_("Unable to determine the signature algorithm for the signing the certificate"));
nssError ();
return 0;
}
PRArenaPool *arena = cert->arena;
SECStatus rv = SECOID_SetAlgorithmID (arena, & cert->signature, algID, 0);
if (rv != SECSuccess)
{
nsscommon_error (_("Unable to set the signature algorithm for signing the certificate"));
nssError ();
return 0;
}
*(cert->version.data) = 2;
cert->version.len = 1;
SECItem der;
der.len = 0;
der.data = 0;
void *dummy = SEC_ASN1EncodeItem (arena, & der, cert,
SEC_ASN1_GET (CERT_CertificateTemplate));
if (! dummy)
{
nsscommon_error (_("Unable to encode the certificate for signing"));
nssError ();
return 0;
}
SECItem *result = (SECItem *)PORT_ArenaZAlloc (arena, sizeof (SECItem));
if (! result)
{
nsscommon_error (_("Unable to allocate memory for signing the certificate"));
return 0;
}
rv = SEC_DerSignData (arena, result, der.data, der.len, privKey, algID);
if (rv != SECSuccess)
{
nsscommon_error (_("Unable to sign the certificate"));
nssError ();
return 0;
}
cert->derCert = *result;
return result;
}
static SECStatus
add_server_cert (const string &db_path, SECItem *certDER, PK11SlotInfo *slot)
{
CERTCertificate *cert = CERT_DecodeCertFromPackage((char *)certDER->data, certDER->len);
if (! cert)
{
nsscommon_error (_("Unable to decode certificate"));
nssError ();
return SECFailure;
}
CERTCertDBHandle *handle = 0;
CERTCertTrust *trust = NULL;
SECStatus secStatus = PK11_ImportCert (slot, cert, CK_INVALID_HANDLE,
server_cert_nickname (), PR_FALSE);
if (secStatus != SECSuccess)
{
nsscommon_error (_F("Unable to import certificate into the database at %s", db_path.c_str ()));
nssError ();
goto done;
}
trust = (CERTCertTrust *)PORT_ZAlloc (sizeof (CERTCertTrust));
if (! trust)
{
nsscommon_error (_("Unable to allocate certificate trust"));
secStatus = SECFailure;
goto done;
}
secStatus = CERT_DecodeTrustString (trust, "PCu,,PCu");
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable decode trust string 'PCu,,PCu'"));
nssError ();
goto done;
}
handle = CERT_GetDefaultCertDB ();
assert (handle);
secStatus = CERT_ChangeCertTrust (handle, cert, trust);
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable to change certificate trust"));
nssError ();
}
done:
CERT_DestroyCertificate (cert);
if (trust)
PORT_Free (trust);
return secStatus;
}
SECStatus
add_client_cert (const string &inFileName, const string &db_path)
{
FILE *inFile = fopen (inFileName.c_str (), "rb");
if (! inFile)
{
nsscommon_error (_F("Could not open certificate file %s for reading\n%s",
inFileName.c_str (), strerror (errno)));
return SECFailure;
}
int fd = fileno (inFile);
struct stat info;
int rc = fstat (fd, &info);
if (rc != 0)
{
nsscommon_error (_F("Could not obtain information about certificate file %s\n%s",
inFileName.c_str (), strerror (errno)));
fclose (inFile);
return SECFailure;
}
SECItem certDER;
certDER.len = info.st_size;
certDER.data = (unsigned char *)PORT_Alloc (certDER.len);
if (certDER.data == NULL)
{
nsscommon_error (_F("Could not allocate certDER\n%s",
strerror (errno)));
fclose (inFile);
return SECFailure;
}
size_t read = fread (certDER.data, 1, certDER.len, inFile);
fclose (inFile);
if (read != certDER.len)
{
nsscommon_error (_F("Error reading from certificate file %s\n%s",
inFileName.c_str (), strerror (errno)));
return SECFailure;
}
SECStatus secStatus = nssInit (db_path.c_str (), 1, 0);
if (secStatus != SECSuccess)
{
if (clean_cert_db (db_path.c_str ()) != 0)
{
return SECFailure;
}
if (create_client_cert_db (db_path.c_str ()) != 0)
{
nsscommon_error (_F("Could not create certificate database directory %s",
db_path.c_str ()));
return SECFailure;
}
secStatus = nssInit (db_path.c_str (), 1);
if (secStatus != SECSuccess)
{
return SECFailure;
}
}
CERTCertificate *cert = 0;
CERTCertDBHandle *handle = 0;
CERTCertTrust *trust = 0;
PK11SlotInfo *slot = 0;
secStatus = SECFailure;
cert = CERT_DecodeCertFromPackage ((char *)certDER.data, certDER.len);
if (! cert)
{
nsscommon_error (_("Unable to decode certificate"));
nssError ();
goto done;
}
slot = PK11_GetInternalKeySlot ();
if (! slot)
{
nsscommon_error (_F("Could not obtain internal key slot for certificate database %s", db_path.c_str()));
nssError ();
goto done;
}
secStatus = PK11_ImportCert (slot, cert, CK_INVALID_HANDLE,
server_cert_nickname (), PR_FALSE);
if (secStatus != SECSuccess)
{
nsscommon_error (_F("Could not import certificate into the database at %s", db_path.c_str()));
nssError ();
goto done;
}
trust = (CERTCertTrust *)PORT_ZAlloc (sizeof (CERTCertTrust));
if (! trust)
{
nsscommon_error (_("Could not allocate certificate trust"));
goto done;
}
secStatus = CERT_DecodeTrustString (trust, "P,P,P");
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable decode trust string 'P,P,P'"));
nssError ();
goto done;
}
handle = CERT_GetDefaultCertDB ();
assert (handle);
secStatus = CERT_ChangeCertTrust (handle, cert, trust);
if (secStatus != SECSuccess)
{
nsscommon_error (_("Unable to change certificate trust"));
nssError ();
}
done:
if (slot)
PK11_FreeSlot (slot);
if (trust)
PORT_Free (trust);
if (cert)
CERT_DestroyCertificate (cert);
if (certDER.data)
PORT_Free (certDER.data);
nssCleanup (db_path.c_str ());
glob_t globbuf;
string filespec = db_path + "/*";
int r = glob (filespec.c_str (), 0, NULL, & globbuf);
if (r == GLOB_NOSPACE || r == GLOB_ABORTED) {
nsscommon_error (_F("Could not search certificate database directory %s", db_path.c_str ()));
}
else if (r != GLOB_NOMATCH)
{
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
for (unsigned i = 0; i < globbuf.gl_pathc; ++i)
{
if (chmod (globbuf.gl_pathv[i], mode) != 0)
nsscommon_error (_F("Could set file permissions for %s", globbuf.gl_pathv[i]));
}
}
return secStatus;
}
int
gen_cert_db (const string &db_path, const string &extraDnsNames, bool use_password)
{
log (_F("Generating a new certificate database directory in %s",
db_path.c_str ()));
if (clean_cert_db (db_path.c_str ()) != 0)
{
return 1;
}
if (create_server_cert_db (db_path.c_str ()) != 0)
{
nsscommon_error (_F("Could not create certificate database directory %s",
db_path.c_str ()));
return 1;
}
SECStatus secStatus = nssInit (db_path.c_str (), 1);
if (secStatus != SECSuccess)
{
return 1;
}
CERTName *subject = 0;
SECKEYPublicKey *pubkey = 0;
SECKEYPrivateKey *privkey = 0;
CERTCertificateRequest *cr = 0;
CERTCertificate *cert = 0;
SECItem *certDER = 0;
string dnsNames;
string hostname;
int rc;
string outFileName;
FILE *outFile = 0;
PK11SlotInfo *slot = PK11_GetInternalKeySlot ();
if (! slot)
{
nsscommon_error (_F("Could not obtain internal key slot for certificate database %s", db_path.c_str()));
nssError ();
goto error;
}
rc = init_password (slot, db_path, use_password);
if (rc != 0)
{
goto error;
}
subject = CERT_AsciiToName ((char *)"CN=Systemtap Compile Server, OU=Systemtap");
if (! subject)
{
nsscommon_error (_("Unable to encode certificate common header"));
nssError ();
goto error;
}
privkey = generate_private_key (db_path, slot, & pubkey);
if (! privkey)
{
goto error;
}
cr = generate_cert_request (pubkey, subject);
if (! cr)
{
goto error;
}
struct utsname utsname;
uname (& utsname);
dnsNames = utsname.nodename;
hostname = dnsNames.substr (0, dnsNames.find ('.'));
dnsNames += string(",") + hostname + ".local";
if (! extraDnsNames.empty ())
dnsNames += "," + extraDnsNames;
cert = create_cert (cr, dnsNames);
CERT_DestroyCertificateRequest (cr);
if (! cert)
{
nsscommon_error (_("Unable to create certificate"));
goto error;
}
certDER = sign_cert (cert, privkey);
if (! certDER)
{
goto error;
}
outFileName = db_path + "/stap.cert";
outFile = fopen (outFileName.c_str (), "wb");
if (outFile)
{
size_t written = fwrite (certDER->data, 1, certDER->len, outFile);
if (written != certDER->len)
{
nsscommon_error (_F("Error writing to certificate file %s\n%s",
outFileName.c_str (), strerror (errno)));
}
fclose (outFile);
}
else
{
nsscommon_error (_F("Could not open certificate file %s for writing\n%s",
outFileName.c_str (), strerror (errno)));
}
secStatus = add_server_cert (db_path, certDER, slot);
CERT_DestroyCertificate (cert);
if (secStatus != SECSuccess)
{
nsscommon_error (_F("Unable to add certificate to %s", db_path.c_str ()));
goto error;
}
PK11_FreeSlot (slot);
CERT_DestroyName (subject);
SECKEY_DestroyPublicKey (pubkey);
SECKEY_DestroyPrivateKey (privkey);
goto done;
error:
if (slot)
PK11_FreeSlot (slot);
if (subject)
CERT_DestroyName (subject);
if (pubkey)
SECKEY_DestroyPublicKey (pubkey);
if (privkey)
SECKEY_DestroyPrivateKey (privkey);
if (cert)
CERT_DestroyCertificate (cert);
done:
nssCleanup (db_path.c_str ());
return secStatus != SECSuccess;
}
CERTCertList *get_cert_list_from_db (const string &cert_nickname)
{
CERTCertDBHandle *handle = CERT_GetDefaultCertDB ();
assert (handle);
CERTCertificate *db_cert = PK11_FindCertFromNickname (cert_nickname.c_str (), 0);
if (! db_cert)
{
return 0;
}
CERTCertList *certs = CERT_CreateSubjectCertList (0, handle, & db_cert->derSubject,
PR_Now (), PR_FALSE);
CERT_DestroyCertificate (db_cert);
if (! certs)
{
nsscommon_error (_("NSS library failure in CERT_CreateSubjectCertList"));
nssError ();
}
return certs;
}
static int
format_cert_validity_time (SECItem &vTime, char *timeString, size_t ts_size)
{
int64 time;
SECStatus secStatus;
switch (vTime.type) {
case siUTCTime:
secStatus = DER_UTCTimeToTime (& time, & vTime);
break;
case siGeneralizedTime:
secStatus = DER_GeneralizedTimeToTime (& time, & vTime);
break;
default:
nsscommon_error (_("Could not decode certificate validity"));
return 1;
}
if (secStatus != SECSuccess)
{
nsscommon_error (_("Could not decode certificate validity time"));
return 1;
}
PRExplodedTime printableTime;
PR_ExplodeTime (time, PR_GMTParameters, & printableTime);
if (! PR_FormatTime (timeString, ts_size, "%a %b %d %H:%M:%S %Y", & printableTime))
{
nsscommon_error (_("Could not format certificate validity time"));
return 1;
}
return 0;
}
static bool
cert_is_valid (CERTCertificate *cert)
{
CERTCertDBHandle *handle = CERT_GetDefaultCertDB ();
assert (handle);
SECCertificateUsage usage = certificateUsageSSLServer | certificateUsageObjectSigner;
SECStatus secStatus = CERT_VerifyCertificate (handle, cert, PR_TRUE, usage,
PR_Now (), NULL, NULL, & usage);
return secStatus == SECSuccess;
}
static bool
cert_db_is_valid (const string &db_path, const string &nss_cert_name)
{
if (! file_exists (db_path))
{
log (_F("Certificate database %s does not exist", db_path.c_str ()));
return false;
}
if (file_exists (db_path + "/pw"))
{
log (_F("Certificate database %s is obsolete", db_path.c_str ()));
return false;
}
SECStatus secStatus = nssInit (db_path.c_str ());
if (secStatus != SECSuccess)
{
return false;
}
bool valid_p = false;
CERTCertList *certs = get_cert_list_from_db (nss_cert_name);
if (! certs)
{
log (_F("No certificate found in database %s", db_path.c_str ()));
goto done;
}
log (_F("Certificate found in database %s", db_path.c_str ()));
for (CERTCertListNode *node = CERT_LIST_HEAD (certs);
! CERT_LIST_END (node, certs);
node = CERT_LIST_NEXT (node))
{
CERTCertificate *c = node->cert;
CERTValidity &v = c->validity;
char timeString[256];
if (format_cert_validity_time (v.notBefore, timeString, sizeof (timeString)) == 0)
log (_F(" Not Valid Before: %s UTC", timeString));
if (format_cert_validity_time (v.notAfter, timeString, sizeof (timeString)) == 0)
log (_F(" Not Valid After: %s UTC", timeString));
if (cert_is_valid (c))
{
log (_("Certificate is valid"));
valid_p = true;
break;
}
log (_("Certificate is not valid"));
}
CERT_DestroyCertList (certs);
done:
nssCleanup (db_path.c_str ());
return valid_p;
}
int
check_cert (const string &db_path, const string &nss_cert_name, bool use_db_password)
{
if (! cert_db_is_valid (db_path, nss_cert_name))
{
if (gen_cert_db (db_path, "", use_db_password) != 0)
{
nsscommon_error (_("Unable to generate new certificate"));
return 1;
}
}
return 0;
}
void sign_file (
const string &db_path,
const string &nss_cert_name,
const string &inputName,
const string &outputName
) {
CERTCertificate *cert = PK11_FindCertFromNickname (nss_cert_name.c_str (), NULL);
if (cert == NULL)
{
nsscommon_error (_F("Unable to find certificate with nickname %s in %s.",
nss_cert_name.c_str (), db_path.c_str()));
nssError ();
return;
}
unsigned char buffer[4096];
PRFileDesc *local_file_fd = NULL;
PRInt32 numBytes;
SECStatus secStatus;
SGNContext *sgn;
SECItem signedData;
SECKEYPrivateKey *privKey = PK11_FindKeyByAnyCert (cert, (void *)db_path.c_str ());
if (privKey == NULL)
{
nsscommon_error (_F("Unable to obtain private key from the certificate with nickname %s in %s.",
nss_cert_name.c_str (), db_path.c_str()));
nssError ();
goto done;
}
sgn = SGN_NewContext (SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION, privKey);
if (! sgn)
{
nsscommon_error (_("Could not create signing context"));
nssError ();
return;
}
secStatus = SGN_Begin (sgn);
if (secStatus != SECSuccess)
{
nsscommon_error (_("Could not initialize signing context."));
nssError ();
return;
}
local_file_fd = PR_Open (inputName.c_str(), PR_RDONLY, 0);
if (local_file_fd == NULL)
{
nsscommon_error (_F("Could not open module file %s", inputName.c_str ()));
nssError ();
return;
}
for (;;)
{
numBytes = PR_Read (local_file_fd, buffer, sizeof (buffer));
if (numBytes == 0)
break;
if (numBytes < 0)
{
nsscommon_error (_F("Error reading module file %s", inputName.c_str ()));
nssError ();
goto done;
}
secStatus = SGN_Update (sgn, buffer, numBytes);
if (secStatus != SECSuccess)
{
nsscommon_error (_F("Error while signing module file %s", inputName.c_str ()));
nssError ();
goto done;
}
}
secStatus = SGN_End (sgn, & signedData);
if (secStatus != SECSuccess)
{
nsscommon_error (_F("Could not complete signature of module file %s", inputName.c_str ()));
nssError ();
goto done;
}
SGN_DestroyContext (sgn, PR_TRUE);
if(local_file_fd != NULL)
PR_Close (local_file_fd);
local_file_fd = PR_Open (outputName.c_str(), PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
PR_IRUSR | PR_IWUSR | PR_IRGRP | PR_IWGRP | PR_IROTH);
if (local_file_fd == NULL)
{
nsscommon_error (_F("Could not open signature file %s", outputName.c_str ()));
nssError ();
goto done;
}
numBytes = PR_Write (local_file_fd, signedData.data, signedData.len);
if (numBytes < 0 || numBytes != (PRInt32)signedData.len)
{
nsscommon_error (_F("Error writing to signature file %s", outputName.c_str ()));
nssError ();
}
done:
if (privKey)
SECKEY_DestroyPrivateKey (privKey);
CERT_DestroyCertificate (cert);
if(local_file_fd != NULL)
PR_Close (local_file_fd);
}
PRInt32 PR_Read_Complete (PRFileDesc *fd, void *buf, PRInt32 requestedBytes)
{
char *buffer = (char *)buf;
PRInt32 totalBytes;
PRInt32 bytesRead;
for (totalBytes = 0; totalBytes < requestedBytes; totalBytes += bytesRead)
{
bytesRead = PR_Read (fd, (void *)(buffer + totalBytes), requestedBytes - totalBytes);
if (bytesRead == 0)
break; if (bytesRead < 0)
return bytesRead; }
return totalBytes;
}
SECStatus
read_cert_info_from_file (const string &certPath, string &fingerprint)
{
FILE *certFile = fopen (certPath.c_str (), "rb");
SECStatus secStatus = SECFailure;
if (! certFile)
{
nsscommon_error (_F("Could not open certificate file %s for reading\n%s",
certPath.c_str (), strerror (errno)));
return SECFailure;
}
int fd = fileno (certFile);
struct stat info;
int rc = fstat (fd, &info);
if (rc != 0)
{
nsscommon_error (_F("Could not obtain information about certificate file %s\n%s",
certPath.c_str (), strerror (errno)));
fclose (certFile);
return SECFailure;
}
PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
if (!arena)
{
nsscommon_error (_F("Could not create arena while decoding certificate from file %s",
certPath.c_str ()));
fclose (certFile);
goto done;
}
SECItem derCert;
if (!SECITEM_AllocItem(arena, &derCert, info.st_size))
{
nsscommon_error (_F("Could not allocate DER cert\n%s",
strerror (errno)));
fclose (certFile);
goto done;
}
size_t read;
read = fread (derCert.data, 1, derCert.len, certFile);
fclose (certFile);
if (read != derCert.len)
{
nsscommon_error (_F("Error reading from certificate file %s\n%s",
certPath.c_str (), strerror (errno)));
goto done;
}
derCert.type = siDERCertBuffer;
CERTCertificate *cert;
int rv;
char *str;
CERTSignedData *sd;
sd = PORT_ArenaZNew(arena, CERTSignedData);
if (!sd)
{
nsscommon_error (_F("Could not allocate signed data while decoding certificate from file %s",
certPath.c_str ()));
goto done;
}
rv = SEC_ASN1DecodeItem(arena, sd, SEC_ASN1_GET(CERT_SignedDataTemplate),
&derCert);
if (rv)
{
nsscommon_error (_F("Could not decode signature while decoding certificate from file %s",
certPath.c_str ()));
goto done;
}
cert = PORT_ArenaZNew(arena, CERTCertificate);
if (!cert)
{
nsscommon_error (_F("Could not allocate cert while decoding certificate from file %s",
certPath.c_str ()));
goto done;
}
cert->arena = arena;
rv = SEC_ASN1DecodeItem(arena, cert,
SEC_ASN1_GET(CERT_CertificateTemplate), &sd->data);
if (rv)
{
nsscommon_error (_F("Could not decode certificate from file %s",
certPath.c_str ()));
goto done;
}
unsigned char fingerprint_buf[SHA1_LENGTH];
SECItem fpItem;
rv = PK11_HashBuf(SEC_OID_SHA1, fingerprint_buf, derCert.data, derCert.len);
if (rv)
{
nsscommon_error (_F("Could not decode SHA1 fingerprint from file %s",
certPath.c_str ()));
goto done;
}
fpItem.data = fingerprint_buf;
fpItem.len = sizeof(fingerprint_buf);
str = CERT_Hexify(&fpItem, 1);
if (! str)
{
nsscommon_error (_F("Could not hexify SHA1 fingerprint from file %s",
certPath.c_str ()));
goto done;
}
fingerprint = str;
transform(fingerprint.begin(), fingerprint.end(), fingerprint.begin(),
::tolower);
PORT_Free(str);
secStatus = SECSuccess;
done:
if (arena)
PORT_FreeArena(arena, PR_FALSE);
return secStatus;
}