summaryrefslogtreecommitdiff
path: root/indra/newview/llsechandler_basic.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview/llsechandler_basic.cpp')
-rw-r--r--indra/newview/llsechandler_basic.cpp913
1 files changed, 837 insertions, 76 deletions
diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp
index 4180f578b9..097cc395d7 100644
--- a/indra/newview/llsechandler_basic.cpp
+++ b/indra/newview/llsechandler_basic.cpp
@@ -20,7 +20,7 @@
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at http://secondlife.com/developers/opensource/flossexception
*
- * By copying, modifying or distributing this software, you acknowledge
+LLS * By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
@@ -42,17 +42,26 @@
#include "llviewercontrol.h"
#include <vector>
#include <ios>
+#include <openssl/ossl_typ.h>
#include <openssl/x509.h>
+#include <openssl/x509v3.h>
#include <openssl/pem.h>
#include <openssl/asn1.h>
#include <openssl/rand.h>
+#include <openssl/err.h>
#include <iostream>
#include <iomanip>
#include <time.h>
+
+
// 128 bits of salt data...
#define STORE_SALT_SIZE 16
#define BUFFER_READ_SIZE 256
+std::string cert_string_from_asn1_string(ASN1_STRING* value);
+LLSD _basic_constraints_ext(X509* cert);
+LLSD _key_usage_ext(X509* cert);
+LLSD _ext_key_usage_ext(X509* cert);
LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert)
@@ -61,14 +70,19 @@ LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert)
// BIO_new_mem_buf returns a read only bio, but takes a void* which isn't const
// so we need to cast it.
BIO * pem_bio = BIO_new_mem_buf((void*)pem_cert.c_str(), pem_cert.length());
-
+ if(pem_bio == NULL)
+ {
+ LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL;
+ throw LLInvalidCertificate(this);
+ }
mCert = NULL;
PEM_read_bio_X509(pem_bio, &mCert, 0, NULL);
BIO_free(pem_bio);
if (!mCert)
{
- throw LLCertException("Error parsing certificate");
+ throw LLInvalidCertificate(this);
}
+ _initLLSD();
}
@@ -76,20 +90,23 @@ LLBasicCertificate::LLBasicCertificate(X509* pCert)
{
if (!pCert || !pCert->cert_info)
{
- throw LLCertException("Invalid certificate");
+ throw LLInvalidCertificate(this);
}
mCert = X509_dup(pCert);
+ _initLLSD();
}
LLBasicCertificate::~LLBasicCertificate()
{
-
- X509_free(mCert);
+ if(mCert)
+ {
+ X509_free(mCert);
+ }
}
//
// retrieve the pem using the openssl functionality
-std::string LLBasicCertificate::getPem()
+std::string LLBasicCertificate::getPem() const
{
char * pem_bio_chars = NULL;
// a BIO is the equivalent of a 'std::stream', and
@@ -98,7 +115,8 @@ std::string LLBasicCertificate::getPem()
BIO *pem_bio = BIO_new(BIO_s_mem());
if (!pem_bio)
{
- throw LLCertException("couldn't allocate memory buffer");
+ LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL;
+ return std::string();
}
PEM_write_bio_X509(pem_bio, mCert);
int length = BIO_get_mem_data(pem_bio, &pem_bio_chars);
@@ -109,14 +127,15 @@ std::string LLBasicCertificate::getPem()
// get the DER encoding for the cert
// DER is a binary encoding format for certs...
-std::vector<U8> LLBasicCertificate::getBinary()
+std::vector<U8> LLBasicCertificate::getBinary() const
{
U8 * der_bio_data = NULL;
// get a memory bio
BIO *der_bio = BIO_new(BIO_s_mem());
if (!der_bio)
{
- throw LLCertException("couldn't allocate memory buffer");
+ LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL;
+ return std::vector<U8>();
}
i2d_X509_bio(der_bio, mCert);
int length = BIO_get_mem_data(der_bio, &der_bio_data);
@@ -128,32 +147,131 @@ std::vector<U8> LLBasicCertificate::getBinary()
}
-LLSD LLBasicCertificate::getLLSD()
+LLSD LLBasicCertificate::getLLSD() const
+{
+ return mLLSDInfo;
+}
+
+// Initialize the LLSD info for the certificate
+LLSD& LLBasicCertificate::_initLLSD()
{
- LLSD result;
// call the various helpers to build the LLSD
- result[CERT_SUBJECT_NAME] = cert_name_from_X509_NAME(X509_get_subject_name(mCert));
- result[CERT_ISSUER_NAME] = cert_name_from_X509_NAME(X509_get_issuer_name(mCert));
- result[CERT_SUBJECT_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_subject_name(mCert));
- result[CERT_ISSUER_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_issuer_name(mCert));
+ mLLSDInfo[CERT_SUBJECT_NAME] = cert_name_from_X509_NAME(X509_get_subject_name(mCert));
+ mLLSDInfo[CERT_ISSUER_NAME] = cert_name_from_X509_NAME(X509_get_issuer_name(mCert));
+ mLLSDInfo[CERT_SUBJECT_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_subject_name(mCert));
+ mLLSDInfo[CERT_ISSUER_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_issuer_name(mCert));
ASN1_INTEGER *sn = X509_get_serialNumber(mCert);
if (sn != NULL)
{
- result[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn);
+ mLLSDInfo[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn);
}
- result[CERT_VALID_TO] = cert_date_from_asn1_time(X509_get_notAfter(mCert));
- result[CERT_VALID_FROM] = cert_date_from_asn1_time(X509_get_notBefore(mCert));
- result[CERT_SHA1_DIGEST] = cert_get_digest("sha1", mCert);
- result[CERT_MD5_DIGEST] = cert_get_digest("md5", mCert);
+ mLLSDInfo[CERT_VALID_TO] = cert_date_from_asn1_time(X509_get_notAfter(mCert));
+ mLLSDInfo[CERT_VALID_FROM] = cert_date_from_asn1_time(X509_get_notBefore(mCert));
+ mLLSDInfo[CERT_SHA1_DIGEST] = cert_get_digest("sha1", mCert);
+ mLLSDInfo[CERT_MD5_DIGEST] = cert_get_digest("md5", mCert);
+ // add the known extensions
+ mLLSDInfo[CERT_BASIC_CONSTRAINTS] = _basic_constraints_ext(mCert);
+ mLLSDInfo[CERT_KEY_USAGE] = _key_usage_ext(mCert);
+ mLLSDInfo[CERT_EXTENDED_KEY_USAGE] = _ext_key_usage_ext(mCert);
+ return mLLSDInfo;
+}
+
+// Retrieve the basic constraints info
+LLSD _basic_constraints_ext(X509* cert)
+{
+ LLSD result;
+ BASIC_CONSTRAINTS *bs = (BASIC_CONSTRAINTS *)X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL);
+ if(bs)
+ {
+ result = LLSD::emptyMap();
+ // Determines whether the cert can be used as a CA
+ result[CERT_BASIC_CONSTRAINTS_CA] = (bool)bs->ca;
+
+ if(bs->pathlen)
+ {
+ // the pathlen determines how deep a certificate chain can be from
+ // this CA
+ if((bs->pathlen->type == V_ASN1_NEG_INTEGER)
+ || !bs->ca)
+ {
+ result[CERT_BASIC_CONSTRAINTS_PATHLEN] = 0;
+ }
+ else
+ {
+ result[CERT_BASIC_CONSTRAINTS_PATHLEN] = (int)ASN1_INTEGER_get(bs->pathlen);
+ }
+ }
+ }
+ return result;
+}
+// retrieve the key usage, which specifies how the cert can be used.
+//
+LLSD _key_usage_ext(X509* cert)
+{
+ LLSD result;
+ ASN1_STRING *usage_str = (ASN1_STRING *)X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL);
+ if(usage_str)
+ {
+ result = LLSD::emptyArray();
+ long usage = 0;
+ if(usage_str->length > 0)
+ {
+ usage = usage_str->data[0];
+ if(usage_str->length > 1)
+ {
+ usage |= usage_str->data[1] << 8;
+ }
+ }
+ ASN1_STRING_free(usage_str);
+ if(usage)
+ {
+ if(usage & KU_DIGITAL_SIGNATURE) result.append(LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE));
+ if(usage & KU_NON_REPUDIATION) result.append(LLSD((std::string)CERT_KU_NON_REPUDIATION));
+ if(usage & KU_KEY_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT));
+ if(usage & KU_DATA_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_DATA_ENCIPHERMENT));
+ if(usage & KU_KEY_AGREEMENT) result.append(LLSD((std::string)CERT_KU_KEY_AGREEMENT));
+ if(usage & KU_KEY_CERT_SIGN) result.append(LLSD((std::string)CERT_KU_CERT_SIGN));
+ if(usage & KU_CRL_SIGN) result.append(LLSD((std::string)CERT_KU_CRL_SIGN));
+ if(usage & KU_ENCIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_ENCIPHER_ONLY));
+ if(usage & KU_DECIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_DECIPHER_ONLY));
+ }
+ }
+ return result;
+}
- return result;
+// retrieve the extended key usage for the cert
+LLSD _ext_key_usage_ext(X509* cert)
+{
+ LLSD result;
+ EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)X509_get_ext_d2i(cert, NID_ext_key_usage, NULL, NULL);
+ if(eku)
+ {
+ result = LLSD::emptyArray();
+ while(sk_ASN1_OBJECT_num(eku))
+ {
+ ASN1_OBJECT *usage = sk_ASN1_OBJECT_pop(eku);
+ if(usage)
+ {
+ int nid = OBJ_obj2nid(usage);
+ if (nid)
+ {
+ std::string sn = OBJ_nid2sn(nid);
+ result.append(sn);
+ }
+ ASN1_OBJECT_free(usage);
+ }
+ }
+ }
+ return result;
}
-X509* LLBasicCertificate::getOpenSSLX509()
+// retrieve an openssl x509 object,
+// which must be freed by X509_free
+X509* LLBasicCertificate::getOpenSSLX509() const
{
return X509_dup(mCert);
}
@@ -203,13 +321,46 @@ LLSD cert_name_from_X509_NAME(X509_NAME* name)
std::string cert_string_from_asn1_integer(ASN1_INTEGER* value)
{
+ std::string result;
BIGNUM *bn = ASN1_INTEGER_to_BN(value, NULL);
- char * ascii_bn = BN_bn2hex(bn);
+ if(bn)
+ {
+ char * ascii_bn = BN_bn2hex(bn);
- BN_free(bn);
+ if(ascii_bn)
+ {
+ result = ascii_bn;
+ OPENSSL_free(ascii_bn);
+ }
+ BN_free(bn);
+ }
+ return result;
+}
+
+// Generate a string from an ASN1 integer. ASN1 Integers are
+// bignums, so they can be 'infinitely' long, therefore we
+// cannot simply use a conversion to U64 or something.
+// We retrieve as a readable string for UI
- std::string result(ascii_bn);
- OPENSSL_free(ascii_bn);
+std::string cert_string_from_asn1_string(ASN1_STRING* value)
+{
+ char * string_bio_chars = NULL;
+ std::string result;
+ // get a memory bio
+ BIO *string_bio = BIO_new(BIO_s_mem());
+ if(!string_bio)
+ {
+ // stream the name into the bio. The name will be in the 'short name' format
+ ASN1_STRING_print_ex(string_bio, value, ASN1_STRFLGS_RFC2253);
+ int length = BIO_get_mem_data(string_bio, &string_bio_chars);
+ result = std::string(string_bio_chars, length);
+ BIO_free(string_bio);
+ }
+ else
+ {
+ LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL;
+ }
+
return result;
}
@@ -222,8 +373,9 @@ LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time)
int i = asn1_time->length;
if (i < 10)
- throw LLCertException("invalid certificate time value");
-
+ {
+ return LLDate();
+ }
// convert the date from the ASN1 time (which is a string in ZULU time), to
// a timeval.
timestruct.tm_year = (asn1_time->data[0]-'0') * 10 + (asn1_time->data[1]-'0');
@@ -237,19 +389,23 @@ LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time)
timestruct.tm_hour = (asn1_time->data[6]-'0') * 10 + (asn1_time->data[7]-'0');
timestruct.tm_min = (asn1_time->data[8]-'0') * 10 + (asn1_time->data[9]-'0');
timestruct.tm_sec = (asn1_time->data[10]-'0') * 10 + (asn1_time->data[11]-'0');
-
- return LLDate((F64)mktime(&timestruct));
+
+#if LL_WINDOWS
+ return LLDate((F64)_mkgmtime(&timestruct));
+#else // LL_WINDOWS
+ return LLDate((F64)timegm(&timestruct));
+#endif // LL_WINDOWS
}
+
// Generate a string containing a digest. The digest time is 'ssh1' or
// 'md5', and the resulting string is of the form "aa:12:5c:' and so on
std::string cert_get_digest(const std::string& digest_type, X509 *cert)
{
- unsigned char digest_data[1024];
- unsigned int len = 1024;
+ unsigned char digest_data[BUFFER_READ_SIZE];
+ unsigned int len = sizeof(digest_data);
std::stringstream result;
const EVP_MD* digest = NULL;
- OpenSSL_add_all_digests();
// we could use EVP_get_digestbyname, but that requires initializer code which
// would require us to complicate things by plumbing it into the system.
if (digest_type == "md5")
@@ -262,7 +418,7 @@ std::string cert_get_digest(const std::string& digest_type, X509 *cert)
}
else
{
- throw LLCertException("Invalid digest");
+ return std::string();
}
X509_digest(cert, digest, digest_data, &len);
@@ -279,75 +435,588 @@ std::string cert_get_digest(const std::string& digest_type, X509 *cert)
}
+// class LLBasicCertificateVector
+// This class represents a list of certificates, implemented by a vector of certificate pointers.
+// it contains implementations of the virtual functions for iterators, search, add, remove, etc.
+//
+
+// Find a certificate in the list.
+// It will find a cert that has minimally the params listed, with the values being the same
+LLBasicCertificateVector::iterator LLBasicCertificateVector::find(const LLSD& params)
+{
+ BOOL found = FALSE;
+ // loop through the entire vector comparing the values in the certs
+ // against those passed in via the params.
+ // params should be a map. Only the items specified in the map will be
+ // checked, but they must match exactly, even if they're maps or arrays.
+
+ for(iterator cert = begin();
+ cert != end();
+ cert++)
+ {
+
+ found= TRUE;
+ LLSD cert_info = (*cert)->getLLSD();
+ for (LLSD::map_const_iterator param = params.beginMap();
+ param != params.endMap();
+ param++)
+ {
+
+ if (!cert_info.has((std::string)param->first) ||
+ (!valueCompareLLSD(cert_info[(std::string)param->first], param->second)))
+ {
+ found = FALSE;
+ break;
+ }
+ }
+ if (found)
+ {
+ return (cert);
+ }
+ }
+ return end();
+}
+
+// Insert a certificate into the store. If the certificate already
+// exists in the store, nothing is done.
+void LLBasicCertificateVector::insert(iterator _iter,
+ LLPointer<LLCertificate> cert)
+{
+ LLSD cert_info = cert->getLLSD();
+ if (cert_info.isMap() && cert_info.has(CERT_SHA1_DIGEST))
+ {
+ LLSD existing_cert_info = LLSD::emptyMap();
+ existing_cert_info[CERT_MD5_DIGEST] = cert_info[CERT_MD5_DIGEST];
+ if(find(existing_cert_info) == end())
+ {
+ BasicIteratorImpl *basic_iter = dynamic_cast<BasicIteratorImpl*>(_iter.mImpl.get());
+ mCerts.insert(basic_iter->mIter, cert);
+ }
+ }
+}
+
+// remove a certificate from the store
+LLPointer<LLCertificate> LLBasicCertificateVector::erase(iterator _iter)
+{
+
+ if (_iter != end())
+ {
+ BasicIteratorImpl *basic_iter = dynamic_cast<BasicIteratorImpl*>(_iter.mImpl.get());
+ LLPointer<LLCertificate> result = (*_iter);
+ mCerts.erase(basic_iter->mIter);
+ return result;
+ }
+ return NULL;
+}
+
+
//
// LLBasicCertificateStore
-//
+// This class represents a store of CA certificates. The basic implementation
+// uses a pem file such as the legacy CA.pem stored in the existing
+// SL implementation.
LLBasicCertificateStore::LLBasicCertificateStore(const std::string& filename)
{
+ mFilename = filename;
+ load_from_file(filename);
}
-LLBasicCertificateStore::LLBasicCertificateStore(const X509_STORE* store)
+
+void LLBasicCertificateStore::load_from_file(const std::string& filename)
{
+ // scan the PEM file extracting each certificate
+ BIO* file_bio = BIO_new(BIO_s_file());
+ if(file_bio)
+ {
+ if (BIO_read_filename(file_bio, filename.c_str()) > 0)
+ {
+ X509 *cert_x509 = NULL;
+ while((PEM_read_bio_X509(file_bio, &cert_x509, 0, NULL)) &&
+ (cert_x509 != NULL))
+ {
+ try
+ {
+ add(new LLBasicCertificate(cert_x509));
+ }
+ catch (...)
+ {
+ LL_WARNS("SECAPI") << "Failure creating certificate from the certificate store file." << LL_ENDL;
+ }
+ X509_free(cert_x509);
+ cert_x509 = NULL;
+ }
+ BIO_free(file_bio);
+ }
+ }
+ else
+ {
+ LL_WARNS("SECAPI") << "Could not allocate a file BIO" << LL_ENDL;
+ }
}
+
LLBasicCertificateStore::~LLBasicCertificateStore()
{
}
-
-X509_STORE* LLBasicCertificateStore::getOpenSSLX509Store()
+
+// persist the store
+void LLBasicCertificateStore::save()
{
- return NULL;
+ llofstream file_store(mFilename, llofstream::binary);
+ if(!file_store.fail())
+ {
+ for(iterator cert = begin();
+ cert != end();
+ cert++)
+ {
+ std::string pem = (*cert)->getPem();
+ if(!pem.empty())
+ {
+ file_store << (*cert)->getPem() << std::endl;
+ }
+ }
+ file_store.close();
+ }
+ else
+ {
+ LL_WARNS("SECAPI") << "Could not open certificate store " << mFilename << "for save" << LL_ENDL;
+ }
}
-
- // add a copy of a cert to the store
-void LLBasicCertificateStore::append(const LLCertificate& cert)
+
+// return the store id
+std::string LLBasicCertificateStore::storeId() const
{
+ // this is the basic handler which uses the CA.pem store,
+ // so we ignore this.
+ return std::string("");
}
-
- // add a copy of a cert to the store
-void LLBasicCertificateStore::insert(const int index, const LLCertificate& cert)
+
+
+//
+// LLBasicCertificateChain
+// This class represents a chain of certs, each cert being signed by the next cert
+// in the chain. Certs must be properly signed by the parent
+LLBasicCertificateChain::LLBasicCertificateChain(const X509_STORE_CTX* store)
{
+
+ // we're passed in a context, which contains a cert, and a blob of untrusted
+ // certificates which compose the chain.
+ if((store == NULL) || (store->cert == NULL))
+ {
+ LL_WARNS("SECAPI") << "An invalid store context was passed in when trying to create a certificate chain" << LL_ENDL;
+ return;
+ }
+ // grab the child cert
+ LLPointer<LLCertificate> current = new LLBasicCertificate(store->cert);
+
+ add(current);
+ if(store->untrusted != NULL)
+ {
+ // if there are other certs in the chain, we build up a vector
+ // of untrusted certs so we can search for the parents of each
+ // consecutive cert.
+ LLBasicCertificateVector untrusted_certs;
+ for(int i = 0; i < sk_X509_num(store->untrusted); i++)
+ {
+ LLPointer<LLCertificate> cert = new LLBasicCertificate(sk_X509_value(store->untrusted, i));
+ untrusted_certs.add(cert);
+
+ }
+ while(untrusted_certs.size() > 0)
+ {
+ LLSD find_data = LLSD::emptyMap();
+ LLSD cert_data = current->getLLSD();
+ // we simply build the chain via subject/issuer name as the
+ // client should not have passed in multiple CA's with the same
+ // subject name. If they did, it'll come out in the wash during
+ // validation.
+ find_data[CERT_SUBJECT_NAME_STRING] = cert_data[CERT_ISSUER_NAME_STRING];
+ LLBasicCertificateVector::iterator issuer = untrusted_certs.find(find_data);
+ if (issuer != untrusted_certs.end())
+ {
+ current = untrusted_certs.erase(issuer);
+ add(current);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
}
-
- // remove a certificate from the store
-void LLBasicCertificateStore::remove(int index)
+
+
+// subdomain wildcard specifiers can be divided into 3 parts
+// the part before the first *, the part after the first * but before
+// the second *, and the part after the second *.
+// It then iterates over the second for each place in the string
+// that it matches. ie if the subdomain was testfoofoobar, and
+// the wildcard was test*foo*bar, it would match test, then
+// recursively match foofoobar and foobar
+
+bool _cert_subdomain_wildcard_match(const std::string& subdomain,
+ const std::string& wildcard)
{
+ // split wildcard into the portion before the *, and the portion after
+
+ int wildcard_pos = wildcard.find_first_of('*');
+ // check the case where there is no wildcard.
+ if(wildcard_pos == wildcard.npos)
+ {
+ return (subdomain == wildcard);
+ }
+
+ // we need to match the first part of the subdomain string up to the wildcard
+ // position
+ if(subdomain.substr(0, wildcard_pos) != wildcard.substr(0, wildcard_pos))
+ {
+ // the first portions of the strings didn't match
+ return FALSE;
+ }
+
+ // as the portion of the wildcard string before the * matched, we need to check the
+ // portion afterwards. Grab that portion.
+ std::string new_wildcard_string = wildcard.substr( wildcard_pos+1, wildcard.npos);
+ if(new_wildcard_string.empty())
+ {
+ // we had nothing after the *, so it's an automatic match
+ return TRUE;
+ }
+
+ // grab the portion of the remaining wildcard string before the next '*'. We need to find this
+ // within the remaining subdomain string. and then recursively check.
+ std::string new_wildcard_match_string = new_wildcard_string.substr(0, new_wildcard_string.find_first_of('*'));
+
+ // grab the portion of the subdomain after the part that matched the initial wildcard portion
+ std::string new_subdomain = subdomain.substr(wildcard_pos, subdomain.npos);
+
+ // iterate through the current subdomain, finding instances of the match string.
+ int sub_pos = new_subdomain.find_first_of(new_wildcard_match_string);
+ while(sub_pos != std::string::npos)
+ {
+ new_subdomain = new_subdomain.substr(sub_pos, std::string::npos);
+ if(_cert_subdomain_wildcard_match(new_subdomain, new_wildcard_string))
+ {
+ return TRUE;
+ }
+ sub_pos = new_subdomain.find_first_of(new_wildcard_match_string, 1);
+
+
+ }
+ // didn't find any instances of the match string that worked in the subdomain, so fail.
+ return FALSE;
}
-
- // return a certificate at the index
-LLPointer<LLCertificate> LLBasicCertificateStore::operator[](int index)
+
+
+// RFC2459 does not address wildcards as part of it's name matching
+// specification, and there is no RFC specifying wildcard matching,
+// RFC2818 does a few statements about wildcard matching, but is very
+// general. Generally, wildcard matching is per implementation, although
+// it's pretty similar.
+// in our case, we use the '*' wildcard character only, within each
+// subdomain. The hostname and the CN specification should have the
+// same number of subdomains.
+// We then iterate that algorithm over each subdomain.
+bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& common_name)
{
- LLPointer<LLCertificate> result = NULL;
- return result;
+ std::string new_hostname = hostname;
+ std::string new_cn = common_name;
+ int subdomain_pos = new_hostname.find_first_of('.');
+ int subcn_pos = new_cn.find_first_of('.');
+
+ while((subcn_pos != std::string::npos) && (subdomain_pos != std::string::npos))
+ {
+ // snip out the first subdomain and cn element
+
+ if(!_cert_subdomain_wildcard_match(new_hostname.substr(0, subdomain_pos),
+ new_cn.substr(0, subcn_pos)))
+ {
+ return FALSE;
+ }
+ new_hostname = new_hostname.substr(subdomain_pos+1, std::string::npos);
+ new_cn = new_cn.substr(subcn_pos+1, std::string::npos);
+ subdomain_pos = new_hostname.find_first_of('.');
+ subcn_pos = new_cn.find_first_of('.');
+ }
+ return _cert_subdomain_wildcard_match(new_hostname, new_cn);
+
}
- // return the number of certs in the store
-int LLBasicCertificateStore::len() const
+
+// validate that the LLSD array in llsd_set contains the llsd_value
+bool _LLSDArrayIncludesValue(const LLSD& llsd_set, LLSD llsd_value)
{
- return 0;
+ for(LLSD::array_const_iterator set_value = llsd_set.beginArray();
+ set_value != llsd_set.endArray();
+ set_value++)
+ {
+ if(valueCompareLLSD((*set_value), llsd_value))
+ {
+ return TRUE;
+ }
+ }
+ return FALSE;
}
-
- // load the store from a persisted location
-void LLBasicCertificateStore::load(const std::string& store_id)
+
+void _validateCert(int validation_policy,
+ const LLPointer<LLCertificate> cert,
+ const LLSD& validation_params,
+ int depth)
{
-}
+
+ LLSD current_cert_info = cert->getLLSD();
+ // check basic properties exist in the cert
+ if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info.has(CERT_SUBJECT_NAME_STRING))
+ {
+ throw LLCertException(cert, "Cert doesn't have a Subject Name");
+ }
+
+ if(!current_cert_info.has(CERT_ISSUER_NAME_STRING))
+ {
+ throw LLCertException(cert, "Cert doesn't have an Issuer Name");
+ }
+
+ // check basic properties exist in the cert
+ if(!current_cert_info.has(CERT_VALID_FROM) || !current_cert_info.has(CERT_VALID_TO))
+ {
+ throw LLCertException(cert, "Cert doesn't have an expiration period");
+ }
+ if (!current_cert_info.has(CERT_SHA1_DIGEST))
+ {
+ throw LLCertException(cert, "No SHA1 digest");
+ }
+
+ if (validation_policy & VALIDATION_POLICY_TIME)
+ {
+
+ LLDate validation_date(time(NULL));
+ if(validation_params.has(CERT_VALIDATION_DATE))
+ {
+ validation_date = validation_params[CERT_VALIDATION_DATE];
+ }
- // persist the store
-void LLBasicCertificateStore::save()
-{
+ if((validation_date < current_cert_info[CERT_VALID_FROM].asDate()) ||
+ (validation_date > current_cert_info[CERT_VALID_TO].asDate()))
+ {
+ throw LLCertValidationExpirationException(cert, validation_date);
+ }
+ }
+ if (validation_policy & VALIDATION_POLICY_SSL_KU)
+ {
+ if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() &&
+ (!(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE],
+ LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE))) ||
+ !(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE],
+ LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT)))))
+ {
+ throw LLCertKeyUsageValidationException(cert);
+ }
+ // only validate EKU if the cert has it
+ if(current_cert_info.has(CERT_EXTENDED_KEY_USAGE) && current_cert_info[CERT_EXTENDED_KEY_USAGE].isArray() &&
+ (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE],
+ LLSD((std::string)CERT_EKU_SERVER_AUTH))))
+ {
+ throw LLCertKeyUsageValidationException(cert);
+ }
+ }
+ if (validation_policy & VALIDATION_POLICY_CA_KU)
+ {
+ if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() &&
+ (!_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE],
+ (std::string)CERT_KU_CERT_SIGN)))
+ {
+ throw LLCertKeyUsageValidationException(cert);
+ }
+ }
+
+ // validate basic constraints
+ if ((validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS) &&
+ current_cert_info.has(CERT_BASIC_CONSTRAINTS) &&
+ current_cert_info[CERT_BASIC_CONSTRAINTS].isMap())
+ {
+ if(!current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_CA) ||
+ !current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_CA])
+ {
+ throw LLCertBasicConstraintsValidationException(cert);
+ }
+ if (current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_PATHLEN) &&
+ ((current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger() != 0) &&
+ (depth > current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger())))
+ {
+ throw LLCertBasicConstraintsValidationException(cert);
+ }
+ }
}
-
- // return the store id
-std::string LLBasicCertificateStore::storeId()
+
+bool _verify_signature(LLPointer<LLCertificate> parent,
+ LLPointer<LLCertificate> child)
{
- return std::string("");
-}
+ bool verify_result = FALSE;
+ LLSD cert1 = parent->getLLSD();
+ LLSD cert2 = child->getLLSD();
+ X509 *signing_cert = parent->getOpenSSLX509();
+ X509 *child_cert = child->getOpenSSLX509();
+ if((signing_cert != NULL) && (child_cert != NULL))
+ {
+ EVP_PKEY *pkey = X509_get_pubkey(signing_cert);
+
- // validate a cert chain
-bool LLBasicCertificateStore::validate(const LLCertificateChain& cert_chain) const
+ if(pkey)
+ {
+ int verify_code = X509_verify(child_cert, pkey);
+ verify_result = ( verify_code > 0);
+ EVP_PKEY_free(pkey);
+ }
+ else
+ {
+ LL_WARNS("SECAPI") << "Could not validate the cert chain signature, as the public key of the signing cert could not be retrieved" << LL_ENDL;
+ }
+
+ }
+ else
+ {
+ LL_WARNS("SECAPI") << "Signature verification failed as there are no certs in the chain" << LL_ENDL;
+ }
+ if(child_cert)
+ {
+ X509_free(child_cert);
+ }
+ if(signing_cert)
+ {
+ X509_free(signing_cert);
+ }
+ return verify_result;
+}
+
+// validate the certificate chain against a store.
+// There are many aspects of cert validatioin policy involved in
+// trust validation. The policies in this validation algorithm include
+// * Hostname matching for SSL certs
+// * Expiration time matching
+// * Signature validation
+// * Chain trust (is the cert chain trusted against the store)
+// * Basic constraints
+// * key usage and extended key usage
+// TODO: We should add 'authority key identifier' for chaining.
+// This algorithm doesn't simply validate the chain by itself
+// and verify the last cert is in the certificate store, or points
+// to a cert in the store. It validates whether any cert in the chain
+// is trusted in the store, even if it's not the last one.
+void LLBasicCertificateChain::validate(int validation_policy,
+ LLPointer<LLCertificateStore> ca_store,
+ const LLSD& validation_params)
{
- return FALSE;
+
+ if(size() < 1)
+ {
+ throw LLCertException(NULL, "No certs in chain");
+ }
+ iterator current_cert = begin();
+ LLSD current_cert_info = (*current_cert)->getLLSD();
+ LLSD validation_date;
+ if (validation_params.has(CERT_VALIDATION_DATE))
+ {
+ validation_date = validation_params[CERT_VALIDATION_DATE];
+ }
+
+ if (validation_policy & VALIDATION_POLICY_HOSTNAME)
+ {
+ if(!validation_params.has(CERT_HOSTNAME))
+ {
+ throw LLCertException((*current_cert), "No hostname passed in for validation");
+ }
+ if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info[CERT_SUBJECT_NAME].has(CERT_NAME_CN))
+ {
+ throw LLInvalidCertificate((*current_cert));
+ }
+
+ LL_INFOS("SECAPI") << "Validating the hostname " << validation_params[CERT_HOSTNAME].asString() <<
+ "against the cert CN " << current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString() << LL_ENDL;
+ if(!_cert_hostname_wildcard_match(validation_params[CERT_HOSTNAME].asString(),
+ current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString()))
+ {
+ throw LLCertValidationHostnameException(validation_params[CERT_HOSTNAME].asString(),
+ (*current_cert));
+ }
+ }
+
+
+ int depth = 0;
+ LLPointer<LLCertificate> previous_cert;
+ // loop through the cert chain, validating the current cert against the next one.
+ while(current_cert != end())
+ {
+
+ int local_validation_policy = validation_policy;
+ if(current_cert == begin())
+ {
+ // for the child cert, we don't validate CA stuff
+ local_validation_policy &= ~(VALIDATION_POLICY_CA_KU |
+ VALIDATION_POLICY_CA_BASIC_CONSTRAINTS);
+ }
+ else
+ {
+ // for non-child certs, we don't validate SSL Key usage
+ local_validation_policy &= ~VALIDATION_POLICY_SSL_KU;
+ if(!_verify_signature((*current_cert),
+ previous_cert))
+ {
+ throw LLCertValidationInvalidSignatureException(previous_cert);
+ }
+ }
+ _validateCert(local_validation_policy,
+ (*current_cert),
+ validation_params,
+ depth);
+
+ // look for a CA in the CA store that may belong to this chain.
+ LLSD cert_llsd = (*current_cert)->getLLSD();
+ LLSD cert_search_params = LLSD::emptyMap();
+ // is the cert itself in the store?
+ cert_search_params[CERT_SHA1_DIGEST] = cert_llsd[CERT_SHA1_DIGEST];
+ LLCertificateStore::iterator found_store_cert = ca_store->find(cert_search_params);
+ if(found_store_cert != ca_store->end())
+ {
+ return;
+ }
+
+ // is the parent in the cert store?
+
+ cert_search_params = LLSD::emptyMap();
+ cert_search_params[CERT_SUBJECT_NAME_STRING] = cert_llsd[CERT_ISSUER_NAME_STRING];
+ found_store_cert = ca_store->find(cert_search_params);
+
+ if(found_store_cert != ca_store->end())
+ {
+ LLSD foo = (*found_store_cert)->getLLSD();
+ // validate the store cert against the depth
+ _validateCert(validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS,
+ (*found_store_cert),
+ LLSD(),
+ depth);
+
+ // verify the signature of the CA
+ if(!_verify_signature((*found_store_cert),
+ (*current_cert)))
+ {
+ throw LLCertValidationInvalidSignatureException(*current_cert);
+ }
+ // successfully validated.
+ return;
+ }
+ previous_cert = (*current_cert);
+ current_cert++;
+ depth++;
+ }
+ if (validation_policy & VALIDATION_POLICY_TRUSTED)
+ {
+ LLPointer<LLCertificate> untrusted_ca_cert = (*this)[size()-1];
+ // we reached the end without finding a trusted cert.
+ throw LLCertValidationTrustException((*this)[size()-1]);
+
+ }
}
+
// LLSecAPIBasicHandler Class
// Interface handler class for the various security storage handlers.
@@ -369,7 +1038,39 @@ LLSecAPIBasicHandler::LLSecAPIBasicHandler()
mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat");
mProtectedDataMap = LLSD::emptyMap();
+
+ mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,
+ "bin_conf.dat");
_readProtectedData();
+
+ std::string store_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,
+ "CA.pem");
+ // copy the CA file to a user writable location so we can manipulate it.
+ // for this provider, by using a user writable file, there is a risk that
+ // an attacking program can modify the file, but OS dependent providers
+ // will reduce that risk.
+ // by using a user file, modifications will be limited to one user if
+ // we read-only the main file
+
+
+ if (!LLFile::isfile(store_file))
+ {
+
+ std::string ca_file_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem");
+ llifstream ca_file(ca_file_path.c_str(), llifstream::binary | llifstream::in);
+ llofstream copied_store_file(store_file.c_str(), llofstream::binary | llofstream::out);
+
+ while(!ca_file.fail())
+ {
+ char buffer[BUFFER_READ_SIZE];
+ ca_file.read(buffer, sizeof(buffer));
+ copied_store_file.write(buffer, ca_file.gcount());
+ }
+ ca_file.close();
+ copied_store_file.close();
+ }
+ LL_INFOS("SECAPI") << "Loading certificate store from " << store_file << LL_ENDL;
+ mStore = new LLBasicCertificateStore(store_file);
}
LLSecAPIBasicHandler::~LLSecAPIBasicHandler()
@@ -537,7 +1238,7 @@ LLPointer<LLCertificate> LLSecAPIBasicHandler::getCertificate(X509* openssl_cert
// instantiate a chain from an X509_STORE_CTX
LLPointer<LLCertificateChain> LLSecAPIBasicHandler::getCertificateChain(const X509_STORE_CTX* chain)
{
- LLPointer<LLCertificateChain> result = NULL;
+ LLPointer<LLCertificateChain> result = new LLBasicCertificateChain(chain);
return result;
}
@@ -546,8 +1247,7 @@ LLPointer<LLCertificateChain> LLSecAPIBasicHandler::getCertificateChain(const X5
// persisted)
LLPointer<LLCertificateStore> LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id)
{
- LLPointer<LLCertificateStore> result;
- return result;
+ return mStore;
}
// retrieve protected data
@@ -659,7 +1359,7 @@ void LLSecAPIBasicHandler::saveCredential(LLPointer<LLCredential> cred, bool sav
{
credential["authenticator"] = cred->getAuthenticator();
}
- LL_INFOS("Credentials") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL;
+ LL_INFOS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL;
setProtectedData("credential", cred->getGrid(), credential);
//*TODO: If we're saving Agni credentials, should we write the
// credentials to the legacy password.dat/etc?
@@ -742,3 +1442,64 @@ std::string LLSecAPIBasicCredential::asString() const
}
+bool valueCompareLLSD(const LLSD& lhs, const LLSD& rhs)
+{
+ if (lhs.type() != rhs.type())
+ {
+ return FALSE;
+ }
+ if (lhs.isMap())
+ {
+ // iterate through the map, verifying the right hand side has all of the
+ // values that the left hand side has.
+ for (LLSD::map_const_iterator litt = lhs.beginMap();
+ litt != lhs.endMap();
+ litt++)
+ {
+ if (!rhs.has(litt->first))
+ {
+ return FALSE;
+ }
+ }
+
+ // Now validate that the left hand side has everything the
+ // right hand side has, and that the values are equal.
+ for (LLSD::map_const_iterator ritt = rhs.beginMap();
+ ritt != rhs.endMap();
+ ritt++)
+ {
+ if (!lhs.has(ritt->first))
+ {
+ return FALSE;
+ }
+ if (!valueCompareLLSD(lhs[ritt->first], ritt->second))
+ {
+ return FALSE;
+ }
+ }
+ return TRUE;
+ }
+ else if (lhs.isArray())
+ {
+ LLSD::array_const_iterator ritt = rhs.beginArray();
+ // iterate through the array, comparing
+ for (LLSD::array_const_iterator litt = lhs.beginArray();
+ litt != lhs.endArray();
+ litt++)
+ {
+ if (!valueCompareLLSD(*ritt, *litt))
+ {
+ return FALSE;
+ }
+ ritt++;
+ }
+
+ return (ritt == rhs.endArray());
+ }
+ else
+ {
+ // simple type, compare as string
+ return (lhs.asString() == rhs.asString());
+ }
+
+}