/******************************************************************************/
/* */
/* X r d C r y p t o S s l A u x . c c */
/* */
/* (c) 2005 G. Ganis, CERN */
/* */
/* This file is part of the XRootD software suite. */
/* */
/* XRootD is free software: you can redistribute it and/or modify it under */
/* the terms of the GNU Lesser General Public License as published by the */
/* Free Software Foundation, either version 3 of the License, or (at your */
/* option) any later version. */
/* */
/* XRootD 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 Lesser General Public */
/* License for more details. */
/* */
/* You should have received a copy of the GNU Lesser General Public License */
/* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
/* COPYING (GPL license). If not, see . */
/* */
/* The copyright holder's institutional names and contributor's names may not */
/* be used to endorse or promote products derived from this software without */
/* specific prior written permission of the institution or contributor. */
/* */
/******************************************************************************/
/* ************************************************************************** */
/* */
/* OpenSSL utility functions */
/* */
/* ************************************************************************** */
#include
#include
#include
#include
#include
#include "XrdCrypto/XrdCryptoX509Chain.hh"
#include "XrdCrypto/XrdCryptosslAux.hh"
#include "XrdCrypto/XrdCryptosslRSA.hh"
#include "XrdCrypto/XrdCryptosslX509.hh"
#include "XrdCrypto/XrdCryptosslTrace.hh"
#include
// Error code from verification set by verify callback function
static int gErrVerifyChain = 0;
XrdOucTrace *sslTrace = 0;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static RSA *EVP_PKEY_get0_RSA(EVP_PKEY *pkey)
{
if (pkey->type != EVP_PKEY_RSA) {
return NULL;
}
return pkey->pkey.rsa;
}
#endif
//____________________________________________________________________________
int XrdCryptosslX509VerifyCB(int ok, X509_STORE_CTX *ctx)
{
// Verify callback function
// Reset global error
gErrVerifyChain = 0;
if (ok != 0) {
// Error analysis
gErrVerifyChain = 1;
}
// We are done
return ok;
}
//____________________________________________________________________________
int XrdCryptosslKDFunLen()
{
// default buffer length
return kSslKDFunDefLen;
}
//____________________________________________________________________________
int XrdCryptosslKDFun(const char *pass, int plen, const char *salt, int slen,
char *key, int klen)
{
// Password-Based Key Derivation Function 2, specified in PKCS #5
// Following (J.Viega, M.Messier, "Secure programming Cookbook", p.141),
// the default number of iterations is set to 10000 .
// It can be specified at the beginning of the salt using a construct
// like this: salt = "$$$"
klen = (klen <= 0) ? 24 : klen;
// Defaults
char *realsalt = (char *)salt;
int realslen = slen;
int it = 10000;
//
// Extract iteration number from salt, if any
char *ibeg = (char *)memchr(salt+1,'$',slen-1);
if (ibeg) {
char *del = 0;
int newit = strtol(ibeg+1, &del, 10);
if (newit > 0 && del[0] == '$' && errno != ERANGE) {
// found iteration number
it = newit;
realsalt = del+1;
realslen = slen - (int)(realsalt-salt);
}
}
PKCS5_PBKDF2_HMAC_SHA1(pass, plen,
(unsigned char *)realsalt, realslen, it,
klen, (unsigned char *)key);
return klen;
}
//____________________________________________________________________________
bool XrdCryptosslX509VerifyCert(XrdCryptoX509 *cert, XrdCryptoX509 *ref)
{
// Verify signature of cert using public key of ref
// Input must make sense
X509 *c = cert ? (X509 *)(cert->Opaque()) : 0;
X509 *r = ref ? (X509 *)(ref->Opaque()) : 0;
EVP_PKEY *rk = r ? X509_get_pubkey(r) : 0;
if (!c || !rk) return 0;
// Ok: we can verify
return (X509_verify(c, rk) > 0);
}
//____________________________________________________________________________
bool XrdCryptosslX509VerifyChain(XrdCryptoX509Chain *chain, int &errcode)
{
// Verifies crossed signatures of X509 certificate 'chain'
// In case of failure, and error code is returned in errcode.
// Make sure we got a potentially meaningful chain
if (!chain || chain->Size() <= 1)
return 0;
// Create a store
X509_STORE *store = X509_STORE_new();
if (!store)
return 0;
// Set the verify callback function
X509_STORE_set_verify_cb_func(store,0);
// Add the first (the CA) certificate
XrdCryptoX509 *cert = chain->Begin();
if (cert->type != XrdCryptoX509::kCA && cert->Opaque())
return 0;
X509_STORE_add_cert(store, (X509 *)(cert->Opaque()));
// Create a stack
STACK_OF(X509) *stk = sk_X509_new_null();
if (!stk)
return 0;
// Fill it with chain we have
X509 *cref = 0;
while ((cert = chain->Next()) && cert->Opaque()) {
if (!cref)
cref = (X509 *)(cert->Opaque());
sk_X509_push(stk, (X509 *)(cert->Opaque()));
}
// Make sure all the certificates have been inserted
#if OPENSSL_VERSION_NUMBER >= 0x10000000L
if (sk_X509_num(stk) != chain->Size() - 1)
#else /* OPENSSL */
if (sk_num(stk) != chain->Size() - 1)
#endif /* OPENSSL */
return 0;
// Create a store ctx ...
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
if (!ctx)
return 0;
// ... and initialize it
X509_STORE_CTX_init(ctx, store, cref, stk);
// verify ?
bool verify_ok = (X509_verify_cert(ctx) == 1);
// Fill error code, if any
errcode = 0;
if (!verify_ok)
errcode = gErrVerifyChain;
// Free context, stack, and store
X509_STORE_CTX_free(ctx);
sk_X509_pop_free(stk, X509_free);
X509_STORE_free(store);
return verify_ok;
}
//____________________________________________________________________________
XrdSutBucket *XrdCryptosslX509ExportChain(XrdCryptoX509Chain *chain,
bool withprivatekey)
{
// Export non-CA content of 'chain' into a bucket for transfer.
EPNAME("X509ExportChain");
XrdSutBucket *bck = 0;
// Make sure we got something to export
if (!chain || chain->Size() <= 0) {
DEBUG("chain undefined or empty: nothing to export");
return bck;
}
// Do not export CA selfsigned certificates
if (chain->Size() == 1 && chain->Begin()->type == XrdCryptoX509::kCA &&
!strcmp(chain->Begin()->IssuerHash(),chain->Begin()->SubjectHash())) {
DEBUG("chain contains only a CA certificate: nothing to export");
return bck;
}
// Now we create a bio_mem to serialize the certificates
BIO *bmem = BIO_new(BIO_s_mem());
if (!bmem) {
DEBUG("unable to create BIO for memory operations");
return bck;
}
// Reorder the chain
chain->Reorder();
// Write the last cert first
XrdCryptoX509 *c = chain->End();
if (!PEM_write_bio_X509(bmem, (X509 *)c->Opaque())) {
DEBUG("error while writing proxy certificate");
BIO_free(bmem);
return bck;
}
// Write its private key, if any and if asked
if (withprivatekey) {
XrdCryptoRSA *k = c->PKI();
if (k->status == XrdCryptoRSA::kComplete) {
if (!PEM_write_bio_PrivateKey(bmem, (EVP_PKEY *)(k->Opaque()),
0, 0, 0, 0, 0)) {
DEBUG("error while writing proxy private key");
BIO_free(bmem);
return bck;
}
}
}
// Now write all other certificates (except selfsigned CAs ...)
while ((c = chain->SearchBySubject(c->Issuer()))) {
if (c->type == XrdCryptoX509::kCA) {
DEBUG("Encountered CA in chain; breaking. Subject: " << c->Subject());
break;
}
if (strcmp(c->IssuerHash(), c->SubjectHash())) {
// Write to bucket
if (!PEM_write_bio_X509(bmem, (X509 *)c->Opaque())) {
DEBUG("error while writing proxy certificate");
BIO_free(bmem);
return bck;
}
} else {
DEBUG("Encountered self-signed CA in chain; breaking. Subject: " << c->Subject());
break;
}
}
// Extract pointer to BIO data and length of segment
char *bdata = 0;
int blen = BIO_get_mem_data(bmem, &bdata);
DEBUG("BIO data: "<SetBuf(bdata, blen);
DEBUG("result of serialization: "<size<<" bytes");
} else {
DEBUG("unable to create bucket for serialized format");
BIO_free(bmem);
return bck;
}
//
// Free BIO
BIO_free(bmem);
//
// We are done
return bck;
}
//____________________________________________________________________________
int XrdCryptosslX509ChainToFile(XrdCryptoX509Chain *ch, const char *fn)
{
// Dump non-CA content of chain 'c' into file 'fn'
EPNAME("X509ChainToFile");
// Check inputs
if (!ch || !fn) {
DEBUG("Invalid inputs");
return -1;
}
// We proceed only if we can lock for write
FILE *fp = fopen(fn,"w");
if (!fp) {
DEBUG("cannot open file to save chain (file: "<Reorder();
// Write the last cert first
XrdCryptoX509 *c = ch->End();
if (PEM_write_X509(fp, (X509 *)c->Opaque()) != 1) {
DEBUG("error while writing proxy certificate");
fclose(fp);
return -1;
}
// Write its private key, if any
XrdCryptoRSA *k = c->PKI();
if (k->status == XrdCryptoRSA::kComplete) {
if (PEM_write_PrivateKey(fp, (EVP_PKEY *)(k->Opaque()),
0, 0, 0, 0, 0) != 1) {
DEBUG("error while writing proxy private key");
fclose(fp);
return -1;
}
}
// Now write all other certificates
while ((c = ch->SearchBySubject(c->Issuer())) && c->type != XrdCryptoX509::kCA) {
// Write to file
if (PEM_write_X509(fp, (X509 *)c->Opaque()) != 1) {
DEBUG("error while writing proxy certificate");
fclose(fp);
return -1;
}
}
} // Unlocks the file
// CLose the file
fclose(fp);
//
// We are done
return 0;
}
//____________________________________________________________________________
int XrdCryptosslX509ParseFile(const char *fname,
XrdCryptoX509Chain *chain)
{
// Parse content of file 'fname' and add X509 certificates to
// chain (which must be initialized by the caller).
// If a private key matching the public key of one of the certificates
// is found in the file, the certificate key is completed.
EPNAME("X509ParseFile");
int nci = 0;
// Make sure we got a file to import
if (!fname) {
DEBUG("file name undefined: can do nothing");
return nci;
}
// Make sure we got a chain where to add the certificates
if (!chain) {
DEBUG("chain undefined: can do nothing");
return nci;
}
//
// Open file and read the content:
// it should contain blocks on information in PEM form
FILE *fcer = fopen(fname, "r");
if (!fcer) {
DEBUG("unable to open file (errno: "<PushBack(c);
nci++;
DEBUG("certificate for '"<Subject()<<"'added to the chain - ord: "<Size());
} else {
DEBUG("could not create certificate: memory exhausted?");
fclose(fcer);
return nci;
}
xcer = 0;
}
// If we found something, and we are asked to extract a key,
// rewind and look for it
if (nci) {
rewind(fcer);
RSA *rsap = 0;
if (!PEM_read_RSAPrivateKey(fcer, &rsap, 0, 0)) {
DEBUG("no RSA private key found in file "<Begin();
while (cert->Opaque()) {
if (cert->type != XrdCryptoX509::kCA) {
// Get the public key
EVP_PKEY *evpp = X509_get_pubkey((X509 *)(cert->Opaque()));
if (evpp) {
RSA *rsa = 0;
if (PEM_read_bio_RSAPrivateKey(bkey,&rsa,0,0)) {
EVP_PKEY_assign_RSA(evpp, rsa);
DEBUG("RSA key completed for '"<Subject()<<"'");
// Test consistency
int rc = RSA_check_key(EVP_PKEY_get0_RSA(evpp));
if (rc != 0) {
// Update PKI in certificate
cert->SetPKI((XrdCryptoX509data)evpp);
// Update status
cert->PKI()->status = XrdCryptoRSA::kComplete;
break;
}
}
}
}
// Get next
cert = chain->Next();
}
}
// Cleanup
BIO_free(bkey);
}
}
// We can close the file now
fclose(fcer);
// We are done
return nci;
}
//____________________________________________________________________________
int XrdCryptosslX509ParseBucket(XrdSutBucket *b, XrdCryptoX509Chain *chain)
{
// Import certificate(s) from bucket b adding them to 'chain'
// (which must be initialized by the caller).
EPNAME("X509ParseBucket");
int nci = 0;
// Make sure we got something to import
if (!b || b->size <= 0) {
DEBUG("bucket undefined or empty: can do nothing");
return nci;
}
// Make sure we got a chain where to add the certificates
if (!chain) {
DEBUG("chain undefined: can do nothing");
return nci;
}
//
// Now we create a bio_mem to store the certificates
BIO *bmem = BIO_new(BIO_s_mem());
if (!bmem) {
DEBUG("unable to create BIO to import certificates");
return nci;
}
// Write data to BIO
if (BIO_write(bmem,(const void *)(b->buffer),b->size) != b->size) {
DEBUG("problems writing data to BIO");
BIO_free(bmem);
return nci;
}
// Get certificates from BIO
X509 *xcer = 0;
while (PEM_read_bio_X509(bmem,&xcer,0,0)) {
//
// Create container and add to the list
XrdCryptoX509 *c = new XrdCryptosslX509(xcer);
if (c) {
chain->PushBack(c);
nci++;
DEBUG("certificate added to the chain - ord: "<Size());
} else {
DEBUG("could not create certificate: memory exhausted?");
BIO_free(bmem);
return nci;
}
// reset cert otherwise the next one is not fetched
xcer = 0;
}
// If we found something, and we are asked to extract a key,
// refill the BIO and search again for the key (this is mandatory
// as read operations modify the BIO contents; a read-only BIO
// may be more efficient)
if (nci && BIO_write(bmem,(const void *)(b->buffer),b->size) == b->size) {
RSA *rsap = 0;
if (!PEM_read_bio_RSAPrivateKey(bmem, &rsap, 0, 0)) {
DEBUG("no RSA private key found in bucket ");
} else {
DEBUG("found a RSA private key in bucket ");
// We need to complete the key: we save it temporarly
// to a bio and check all the private keys of the
// loaded certificates
bool ok = 1;
BIO *bkey = BIO_new(BIO_s_mem());
if (!bkey) {
DEBUG("unable to create BIO for key completion");
ok = 0;
}
if (ok) {
// Write the private key
if (!PEM_write_bio_RSAPrivateKey(bkey,rsap,0,0,0,0,0)) {
DEBUG("unable to write RSA private key to bio");
ok = 0;
}
}
RSA_free(rsap);
if (ok) {
// Loop over the chain certificates
XrdCryptoX509 *cert = chain->Begin();
while (cert->Opaque()) {
if (cert->type != XrdCryptoX509::kCA) {
// Get the public key
EVP_PKEY *evpp = X509_get_pubkey((X509 *)(cert->Opaque()));
if (evpp) {
RSA *rsa = 0;
if (PEM_read_bio_RSAPrivateKey(bkey,&rsa,0,0)) {
EVP_PKEY_assign_RSA(evpp, rsa);
DEBUG("RSA key completed ");
// Test consistency
int rc = RSA_check_key(EVP_PKEY_get0_RSA(evpp));
if (rc != 0) {
// Update PKI in certificate
cert->SetPKI((XrdCryptoX509data)evpp);
// Update status
cert->PKI()->status = XrdCryptoRSA::kComplete;
break;
}
}
}
}
// Get next
cert = chain->Next();
}
}
// Cleanup
BIO_free(bkey);
}
}
// Cleanup
BIO_free(bmem);
// We are done
return nci;
}
//____________________________________________________________________________
time_t XrdCryptosslASN1toUTC(const ASN1_TIME *tsn1)
{
// Function to convert from ASN1 time format into UTC
// since Epoch (Jan 1, 1970)
// Return -1 if something went wrong
time_t etime = -1;
EPNAME("ASN1toUTC");
//
// Make sure there is something to convert
if (!tsn1) return etime;
//
// Parse the input string: here we basically cut&paste from GRIDSITE
// They finally use timegm to convert to UTC seconds, which is less
// standard and seems to give an offset of 3600 secs.
// Our result is in agreement with 'date +%s`.
struct tm ltm;
char zz;
if ((sscanf((const char *)(tsn1->data),
"%02d%02d%02d%02d%02d%02d%c",
&(ltm.tm_year), &(ltm.tm_mon), &(ltm.tm_mday),
&(ltm.tm_hour), &(ltm.tm_min), &(ltm.tm_sec),
&zz) != 7) || (zz != 'Z')) {
return -1;
}
// Init also the ones not used by mktime
ltm.tm_wday = 0; // day of the week
ltm.tm_yday = 0; // day in the year
ltm.tm_isdst = -1; // daylight saving time
//
// Renormalize some values: year should be modulo 1900
if (ltm.tm_year < 90)
ltm.tm_year += 100;
//
// month should in [0, 11]
(ltm.tm_mon)--;
//
// Calculate UTC
etime = mktime(<m);
// Include DST shift; here, because we have the information
if (ltm.tm_isdst > 0) etime += XrdCryptoDSTShift;
// Notify, if requested
DEBUG(" UTC: "<