diff options
Diffstat (limited to 'indra/newview/llsechandler_basic.cpp')
| -rw-r--r-- | indra/newview/llsechandler_basic.cpp | 3944 | 
1 files changed, 1972 insertions, 1972 deletions
diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp index 1a6b70c45a..dffe81d4b1 100644 --- a/indra/newview/llsechandler_basic.cpp +++ b/indra/newview/llsechandler_basic.cpp @@ -1,1972 +1,1972 @@ -/**  - * @file llsechandler_basic.cpp - * @brief Security API for services such as certificate handling - * secure local storage, etc. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - *  - * This library 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; - * version 2.1 of the License only. - *  - * This library 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 this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA - *  - * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" -#include "llsecapi.h" -#include "llsechandler_basic.h" -#include "llsdserialize.h" -#include "llviewernetwork.h" -#include "llxorcipher.h" -#include "llfile.h" -#include "lldir.h" -#include "llviewercontrol.h" -#include "llexception.h" -#include "stringize.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> -#include "llmachineid.h" - - -static const std::string DEFAULT_CREDENTIAL_STORAGE = "credential"; - -// 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); -std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value); - -LLSD _basic_constraints_ext(X509* cert); -LLSD _key_usage_ext(X509* cert); -LLSD _ext_key_usage_ext(X509* cert); -std::string _subject_key_identifier(X509 *cert); -LLSD _authority_key_identifier(X509* cert); -void _validateCert(int validation_policy, -                   LLPointer<LLCertificate> cert, -                   const LLSD& validation_params, -                   int depth); - -LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert, -                                       const LLSD* validation_params)  -{ -	// 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; -        LLTHROW(LLAllocationCertException(LLSD::emptyMap())); -	} -	mCert = NULL; -	PEM_read_bio_X509(pem_bio, &mCert, 0, NULL); -	BIO_free(pem_bio); -	if (!mCert) -	{ -        LL_WARNS("SECAPI") << "Could not decode certificate to x509." << LL_ENDL; -        LLTHROW(LLInvalidCertificate(LLSD::emptyMap())); -	} -} - - -LLBasicCertificate::LLBasicCertificate(X509* pCert, -                                       const LLSD* validation_params)  -{ -	if (!pCert) -	{ -		LLTHROW(LLInvalidCertificate(LLSD::emptyMap())); -	}	 -	mCert = X509_dup(pCert); -    // it is tempting to run _validateCert here, but doing so causes problems -    // the trick is figuring out which aspects to validate. TBD -} - -LLBasicCertificate::~LLBasicCertificate()  -{ -	if(mCert) -	{ -		X509_free(mCert); -        mCert = NULL; -	} -} - -// -// retrieve the pem using the openssl functionality -std::string LLBasicCertificate::getPem() const -{  -	char * pem_bio_chars = NULL; -	// a BIO is the equivalent of a 'std::stream', and -	// can be a file, mem stream, whatever.  Grab a memory based -	// BIO for the result -	BIO *pem_bio = BIO_new(BIO_s_mem()); -	if (!pem_bio) -	{ -		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); -	std::string result = std::string(pem_bio_chars, length); -	BIO_free(pem_bio); -	return result; -} - -// get the DER encoding for the cert -// DER is a binary encoding format for certs... -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) -	{ -		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); -	std::vector<U8> result(length); -	// vectors are guranteed to be a contiguous chunk of memory. -	memcpy(&result[0], der_bio_data,  length); -	BIO_free(der_bio); -	return result; -} - - -void LLBasicCertificate::getLLSD(LLSD &llsd) -{ -	if (mLLSDInfo.isUndefined()) -	{ -		_initLLSD(); -	} -	llsd = mLLSDInfo; -} - -// Initialize the LLSD info for the certificate -LLSD& LLBasicCertificate::_initLLSD() -{  - -	// call the various helpers to build the LLSD -	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) -	{ -		mLLSDInfo[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn); -	} -	 -	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)); -	// 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); -	mLLSDInfo[CERT_SUBJECT_KEY_IDENTFIER] = _subject_key_identifier(mCert); -	mLLSDInfo[CERT_AUTHORITY_KEY_IDENTIFIER] = _authority_key_identifier(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); -			} -		} - -		BASIC_CONSTRAINTS_free( bs ); -	} -	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; -} - -// 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); -			} -		} - -		EXTENDED_KEY_USAGE_free( eku ); -	} -	return result; -} - -// retrieve the subject key identifier of the cert -std::string _subject_key_identifier(X509 *cert) -{ -    std::string result; -	ASN1_OCTET_STRING *skeyid = (ASN1_OCTET_STRING *)X509_get_ext_d2i(cert, NID_subject_key_identifier, NULL, NULL); -	if(skeyid) -	{ -		result = cert_string_from_octet_string(skeyid); -		ASN1_OCTET_STRING_free( skeyid ); -	} -	return result; -} - -// retrieve the authority key identifier of the cert -LLSD _authority_key_identifier(X509* cert) -{ -	LLSD result; -	AUTHORITY_KEYID *akeyid = (AUTHORITY_KEYID *)X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); -	if(akeyid) -	{ -		result = LLSD::emptyMap(); -		if(akeyid->keyid) -		{ -			result[CERT_AUTHORITY_KEY_IDENTIFIER_ID] = cert_string_from_octet_string(akeyid->keyid); -		} -		if(akeyid->serial) -		{ -			result[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL] = cert_string_from_asn1_integer(akeyid->serial); -		}	 - -		AUTHORITY_KEYID_free( akeyid ); -	} -	// we ignore the issuer name in the authority key identifier, we check the issue name via -	// the the issuer name entry in the cert. -	return result; -} - -// retrieve an openssl x509 object, -// which must be freed by X509_free -X509* LLBasicCertificate::getOpenSSLX509() const -{  -	return X509_dup(mCert);  -}   - -// generate a single string containing the subject or issuer -// name of the cert. -std::string cert_string_name_from_X509_NAME(X509_NAME* name) -{ -	char * name_bio_chars = NULL; -	// get a memory bio -	BIO *name_bio = BIO_new(BIO_s_mem()); -	// stream the name into the bio.  The name will be in the 'short name' format -	X509_NAME_print_ex(name_bio, name, 0, XN_FLAG_RFC2253); -	int length = BIO_get_mem_data(name_bio, &name_bio_chars); -	std::string result = std::string(name_bio_chars, length); -	BIO_free(name_bio); -	return result; -} - -// generate an LLSD from a certificate name (issuer or subject name).   -// the name will be strings indexed by the 'long form' -LLSD cert_name_from_X509_NAME(X509_NAME* name) -{ -	LLSD result = LLSD::emptyMap(); -	int name_entries = X509_NAME_entry_count(name); -	for (int entry_index=0; entry_index < name_entries; entry_index++)  -	{ -		char buffer[32]; -		X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, entry_index); -		 -		std::string name_value = std::string((const char*)ASN1_STRING_get0_data(X509_NAME_ENTRY_get_data(entry)),  -											 ASN1_STRING_length(X509_NAME_ENTRY_get_data(entry))); - -		ASN1_OBJECT* name_obj = X509_NAME_ENTRY_get_object(entry);		 -		OBJ_obj2txt(buffer, sizeof(buffer), name_obj, 0); -		std::string obj_buffer_str = std::string(buffer); -		result[obj_buffer_str] = name_value; -	} -	 -	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 cert_string_from_asn1_integer(ASN1_INTEGER* value) -{ -	std::string result; -	BIGNUM *bn = ASN1_INTEGER_to_BN(value, NULL); -	if(bn) -	{ -		char * ascii_bn = BN_bn2hex(bn); - -		if(ascii_bn) -		{ -			result = ascii_bn; -			OPENSSL_free(ascii_bn); -		} -		BN_free(bn); -	} -	return result; -} - -// Generate a string from an OCTET string. -// we retrieve as a  - -std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value) -{ -	 -	std::stringstream result; -	result << std::hex << std::setprecision(2); -	for (int i=0; i < value->length; i++) -	{ -		if (i != 0)  -		{ -			result << ":"; -		} -		result  << std::setfill('0') << std::setw(2) << (int)value->data[i]; -	} -	return result.str(); -} - -// 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 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; -} - -// retrieve a date structure from an ASN1 time, for  -// validity checking. -LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time) -{ -	 -	struct tm timestruct = {0}; -	int i = asn1_time->length; -	 -	if (i < 10) -	{ -		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'); -	 -	/* Deal with Year 2000 */ -	if (timestruct.tm_year < 70) -		timestruct.tm_year += 100; -	 -	timestruct.tm_mon = (asn1_time->data[2]-'0') * 10 + (asn1_time->data[3]-'0') - 1; -	timestruct.tm_mday = (asn1_time->data[4]-'0') * 10 + (asn1_time->data[5]-'0'); -	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'); - -#if LL_WINDOWS -	return LLDate((F64)_mkgmtime(×truct)); -#else // LL_WINDOWS -	return LLDate((F64)timegm(×truct)); -#endif // LL_WINDOWS -} - -// 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) -{ -	// 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. -    bool found = false; -	iterator cert = begin(); -	while ( !found && cert != end() ) -	{ -		found = true; -		LLSD cert_info; -		(*cert)->getLLSD(cert_info); -		for (LLSD::map_const_iterator param = params.beginMap(); -			 found && param != params.endMap(); -			 param++) -		{ -			if (   !cert_info.has((std::string)param->first) -                || !valueCompareLLSD(cert_info[(std::string)param->first], param->second)) -			{ -				found = false; -			} -		} -        if (!found) -        { -            cert++; -        } -	} -	return cert; -} - -// 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(cert_info); -	if (cert_info.isMap() && cert_info.has(CERT_SUBJECT_KEY_IDENTFIER)) -	{ -		LLSD existing_cert_info = LLSD::emptyMap(); -		existing_cert_info[CERT_SUBJECT_KEY_IDENTFIER] = cert_info[CERT_SUBJECT_KEY_IDENTFIER]; -		if(find(existing_cert_info) == end()) -		{ -			BasicIteratorImpl *basic_iter = dynamic_cast<BasicIteratorImpl*>(_iter.mImpl.get()); -			if (basic_iter) -			{ -				mCerts.insert(basic_iter->mIter, cert); -			} -            else -            { -                LL_WARNS("SECAPI") << "Invalid certificate postion vector" -                                   << LL_ENDL; -            } -		} -        else -        { -            LL_DEBUGS("SECAPI") << "Certificate already in vector: " -                                << "'" << cert_info << "'" -                                << LL_ENDL; -        } - -	} -    else -    { -        LL_WARNS("SECAPI") << "Certificate does not have Subject Key Identifier; not inserted: " -                           << "'" << cert_info << "'" -                           << LL_ENDL; -    } -} - -// 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 crt file such as the ca-bundle.crt in the existing SL implementation. -LLBasicCertificateStore::LLBasicCertificateStore(const std::string& filename) -{ -	mFilename = filename; -	load_from_file(filename); -} - -void LLBasicCertificateStore::load_from_file(const std::string& filename) -{ -    int loaded = 0; -    int rejected = 0; - -	// scan the PEM file extracting each certificate -	if (LLFile::isfile(filename)) -	{ -        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 -                    { -                        LLPointer<LLBasicCertificate> new_cert(new LLBasicCertificate(cert_x509)); -                        LLSD validation_params; -                        _validateCert(VALIDATION_POLICY_TIME, -                                      new_cert, -                                      validation_params, -                                      0); -                        add(new_cert); -                        LL_DEBUGS("SECAPI") << "Loaded valid cert for " -                                            << "Name '" << cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509)) << "'"; -                        std::string skeyid(_subject_key_identifier(cert_x509)); -                        LL_CONT << " Id '" << skeyid << "'" -                                << LL_ENDL; -                        loaded++; -                    } -                    catch (LLCertException& cert_exception) -                    { -                        LLSD cert_info(cert_exception.getCertData()); -                        LL_DEBUGS("SECAPI_BADCERT","SECAPI") << "invalid certificate (" << cert_exception.what() << "): " << cert_info << LL_ENDL; -                        rejected++; -                    } -                    catch (...) -                    { -                        LOG_UNHANDLED_EXCEPTION("creating certificate from the certificate store file"); -                        rejected++; -                    } -                    X509_free(cert_x509); -                    cert_x509 = NULL; -                } -                BIO_free(file_bio); -            } -            else -            { -                LL_WARNS("SECAPI") << "BIO read failed for " << filename << LL_ENDL; -            } - -            LL_INFOS("SECAPI") << "loaded " << loaded << " good certificates (rejected " << rejected << ") from " << filename << LL_ENDL; -        } -        else -        { -            LL_WARNS("SECAPI") << "Could not allocate a file BIO" << LL_ENDL; -        } -    } -    else -    { -        // since the user certificate store may not be there, this is not a warning -        LL_INFOS("SECAPI") << "Certificate store not found at " << filename << LL_ENDL; -    } -} - - -LLBasicCertificateStore::~LLBasicCertificateStore() -{ -} - - -// persist the store -void LLBasicCertificateStore::save() -{ -	llofstream file_store(mFilename.c_str(), std::ios_base::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; -	} -} - -// return the store id -std::string LLBasicCertificateStore::storeId() const -{ -	// this is the basic handler which uses the ca-bundle.crt store, -	// so we ignore this. -	return std::string(""); -} - - -// -// 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(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) || X509_STORE_CTX_get0_cert(store) == 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(X509_STORE_CTX_get0_cert(store)); - -	add(current); -	if(X509_STORE_CTX_get0_untrusted(store) != 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(X509_STORE_CTX_get0_untrusted(store)); i++) -		{ -			LLPointer<LLCertificate> cert = new LLBasicCertificate(sk_X509_value(X509_STORE_CTX_get0_untrusted(store), i)); -			untrusted_certs.add(cert); - -		}		 -		while(untrusted_certs.size() > 0) -		{ -			LLSD find_data = LLSD::emptyMap(); -			LLSD cert_data; -			current->getLLSD(cert_data); -			// 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; -			} -		} -	} -} - - -// 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; -} - - -// 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) -{ -	std::string new_hostname = hostname; -	std::string new_cn = common_name; -	 -	// find the last '.' in the hostname and the match name. -	int subdomain_pos = new_hostname.find_last_of('.'); -	int subcn_pos = new_cn.find_last_of('.'); -	 -	// if the last char is a '.', strip it -	if(subdomain_pos == (new_hostname.length()-1)) -	{ -		new_hostname = new_hostname.substr(0, subdomain_pos); -		subdomain_pos = new_hostname.find_last_of('.'); -	} -	if(subcn_pos == (new_cn.length()-1)) -	{ -		new_cn = new_cn.substr(0, subcn_pos); -		subcn_pos = new_cn.find_last_of('.'); -	}	 - -	// Check to see if there are any further '.' in the string.   -	while((subcn_pos != std::string::npos) && (subdomain_pos != std::string::npos)) -	{ -		// snip out last subdomain in both the match string and the hostname -		// The last bit for 'my.current.host.com' would be 'com'   -		std::string cn_part = new_cn.substr(subcn_pos+1, std::string::npos); -		std::string hostname_part = new_hostname.substr(subdomain_pos+1, std::string::npos); -		 -		if(!_cert_subdomain_wildcard_match(new_hostname.substr(subdomain_pos+1, std::string::npos), -										   cn_part)) -		{ -			return false; -		} -		new_hostname = new_hostname.substr(0, subdomain_pos); -		new_cn = new_cn.substr(0, subcn_pos); -		subdomain_pos = new_hostname.find_last_of('.'); -		subcn_pos = new_cn.find_last_of('.'); -	}	 -	// check to see if the most significant portion of the common name is '*'.  If so, we can -	// simply return success as child domains are also matched. -	if(new_cn == "*") -	{ -		// if it's just a '*' we support all child domains as well, so '*. -		return true; -	} -	 -	return _cert_subdomain_wildcard_match(new_hostname, new_cn); - -} - -// validate that the LLSD array in llsd_set contains the llsd_value  -bool _LLSDArrayIncludesValue(const LLSD& llsd_set, LLSD llsd_value) -{ -	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; -} - -void _validateCert(int validation_policy, -				  LLPointer<LLCertificate> cert, -				  const LLSD& validation_params, -				  int depth) -{ -	LLSD current_cert_info; -	cert->getLLSD(current_cert_info);		 -	// check basic properties exist in the cert -	if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info.has(CERT_SUBJECT_NAME_STRING)) -	{ -		LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Name")); -	} -	 -	if(!current_cert_info.has(CERT_ISSUER_NAME_STRING)) -	{ -		LLTHROW(LLCertException(current_cert_info, "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)) -	{ -		LLTHROW(LLCertException(current_cert_info, "Cert doesn't have an expiration period")); -	} -	if (!current_cert_info.has(CERT_SUBJECT_KEY_IDENTFIER)) -	{ -		LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Key Id")); -	} - -	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]; -		} -		 -		if((validation_date < current_cert_info[CERT_VALID_FROM].asDate()) || -		   (validation_date > current_cert_info[CERT_VALID_TO].asDate())) -		{ -			LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date)); -		} -	} -	if (validation_policy & VALIDATION_POLICY_SSL_KU) -	{ -		// This stanza of code was changed 2021-06-09 as per details in SL-15370. -		// Brief summary: a renewed certificate from Akamai only contains the -		// 'Digital Signature' field and not the 'Key Encipherment' one. This code  -		// used to look for both and throw an exception at startup (ignored) and  -		// (for example) when buying L$ in the Viewer (fails with a UI message -		// and an entry in the Viewer log). This modified code removes the second  -		// check for the 'Key Encipherment' field. If Akamai can provide a  -		// replacement certificate that has both fields, then this modified code  -		// will not be required. -		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))) -			) -		{ -			LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); -		} -		// 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_TLS_SERVER_AUTH))) -           && (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE],  -                                         LLSD((std::string)CERT_EKU_SERVER_AUTH))) -           ) -		{ -			LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); -		} -	} -	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))) -			{ -				LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); -			} -	} -	 -	// 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]) -		{ -				LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info)); -		} -		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()))) -		{ -			LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info)); -		} -	} -} - -bool _verify_signature(LLPointer<LLCertificate> parent,  -					   LLPointer<LLCertificate> child) -{ -	bool verify_result = false;  -	LLSD cert1, cert2; -	parent->getLLSD(cert1); -	child->getLLSD(cert2); -	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); -		 -		 -		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 LLBasicCertificateStore::validate(int validation_policy, -									   LLPointer<LLCertificateChain> cert_chain, -									   const LLSD& validation_params) -{ -	// If --no-verify-ssl-cert was passed on the command line, stop right now. -	if (gSavedSettings.getBOOL("NoVerifySSLCert")) -    { -        LL_WARNS_ONCE("SECAPI") << "All Certificate validation disabled; viewer operation is insecure" << LL_ENDL; -        return; -    } - -	if(cert_chain->size() < 1) -	{ -		LLTHROW(LLCertException(LLSD::emptyMap(), "No certs in chain")); -	} -	iterator current_cert = cert_chain->begin(); -	LLSD validation_date; -	if (validation_params.has(CERT_VALIDATION_DATE)) -	{ -		validation_date = validation_params[CERT_VALIDATION_DATE]; -	} - -    // get LLSD info from the cert to throw in any exception -	LLSD 	current_cert_info; -    (*current_cert)->getLLSD(current_cert_info); - -	if (validation_policy & VALIDATION_POLICY_HOSTNAME) -	{ -		if(!validation_params.has(CERT_HOSTNAME)) -		{ -			LLTHROW(LLCertException(current_cert_info, "No hostname passed in for validation")); -		} -		if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info[CERT_SUBJECT_NAME].has(CERT_NAME_CN)) -		{ -			LLTHROW(LLInvalidCertificate(current_cert_info)); -		} -		 -		LL_DEBUGS("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_info); -		} -	} -     -	// check the cache of already validated certs -	X509* cert_x509 = (*current_cert)->getOpenSSLX509(); -	if(!cert_x509) -	{ -		LLTHROW(LLInvalidCertificate(current_cert_info)); -	} - -    std::string subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509))); -    std::string skeyid(_subject_key_identifier(cert_x509)); - -    LL_DEBUGS("SECAPI") << "attempting to validate cert " -                        << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'" -                        << " as subject name '" << subject_name << "'" -                        << " subject key id '" << skeyid << "'" -                        << LL_ENDL; - -	X509_free( cert_x509 ); -	cert_x509 = NULL; -    if (skeyid.empty()) -    { -        LLTHROW(LLCertException(current_cert_info, "No Subject Key Id")); -    } -     -	t_cert_cache::iterator cache_entry = mTrustedCertCache.find(skeyid); -	if(cache_entry != mTrustedCertCache.end()) -	{ -		// this cert is in the cache, so validate the time. -		if (validation_policy & VALIDATION_POLICY_TIME) -		{ -			LLDate validation_date; -			if(validation_params.has(CERT_VALIDATION_DATE)) -			{ -				validation_date = validation_params[CERT_VALIDATION_DATE]; -			} -            else -            { -                validation_date = LLDate(time(NULL)); // current time -            } -			 -			if((validation_date < cache_entry->second.first) || -			   (validation_date > cache_entry->second.second)) -			{ -				LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date)); -			} -		} -		// successfully found in cache -		LL_DEBUGS("SECAPI") << "Valid cert for '" << validation_params[CERT_HOSTNAME].asString() << "'" -                            << " skeyid '" << skeyid << "'" -                            << " found in cache" -                            << LL_ENDL; -		return; -	} -	if(current_cert_info.isUndefined()) -	{ -		(*current_cert)->getLLSD(current_cert_info); -	} -	LLDate from_time = current_cert_info[CERT_VALID_FROM].asDate(); -	LLDate to_time = current_cert_info[CERT_VALID_TO].asDate(); -	int depth = 0; -	LLPointer<LLCertificate> previous_cert; -	// loop through the cert chain, validating the current cert against the next one. -	while(current_cert != cert_chain->end()) -	{ -		int local_validation_policy = validation_policy; -		if(current_cert == cert_chain->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)) -			{ -                LLSD previous_cert_info; -                previous_cert->getLLSD(previous_cert_info); -                LLTHROW(LLCertValidationInvalidSignatureException(previous_cert_info)); -			} -		} -		_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_search_params = LLSD::emptyMap();		 -		// is the cert itself in the store? -		cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = current_cert_info[CERT_SUBJECT_KEY_IDENTFIER]; -		LLCertificateStore::iterator found_store_cert = find(cert_search_params); -		if(found_store_cert != end()) -		{ -			mTrustedCertCache[skeyid] = std::pair<LLDate, LLDate>(from_time, to_time); -            LL_DEBUGS("SECAPI") << "Valid cert " -                                << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'"; -            X509* cert_x509 = (*found_store_cert)->getOpenSSLX509(); -            std::string found_cert_subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509))); -            X509_free(cert_x509); -            LL_CONT << " as '" << found_cert_subject_name << "'" -                    << " skeyid '" << current_cert_info[CERT_SUBJECT_KEY_IDENTFIER].asString() << "'" -                    << " found in cert store" -                    << LL_ENDL;	 -			return; -		} -		 -		// is the parent in the cert store? -			 -		cert_search_params = LLSD::emptyMap(); -		cert_search_params[CERT_SUBJECT_NAME_STRING] = current_cert_info[CERT_ISSUER_NAME_STRING]; -		if (current_cert_info.has(CERT_AUTHORITY_KEY_IDENTIFIER)) -		{ -			LLSD cert_aki = current_cert_info[CERT_AUTHORITY_KEY_IDENTIFIER]; -			if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_ID)) -			{ -				cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_ID]; -			} -			if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL)) -			{ -				cert_search_params[CERT_SERIAL_NUMBER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL]; -			} -		} -		found_store_cert = find(cert_search_params); -		 -		if(found_store_cert != end()) -		{ -			// 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))) -			{ -				LLTHROW(LLCertValidationInvalidSignatureException(current_cert_info)); -			}			 -			// successfully validated. -			mTrustedCertCache[skeyid] = std::pair<LLDate, LLDate>(from_time, to_time);		 -            LL_DEBUGS("SECAPI") << "Verified and cached cert for '" << validation_params[CERT_HOSTNAME].asString() << "'" -                                << " as '" << subject_name << "'" -                                << " id '" << skeyid << "'" -                                << " using CA '" << cert_search_params[CERT_SUBJECT_NAME_STRING] << "'" -                                << " with id '" <<  cert_search_params[CERT_SUBJECT_KEY_IDENTFIER].asString() << "' found in cert store" -                                << LL_ENDL;	 -			return; -		} -		previous_cert = (*current_cert); -		current_cert++; -		depth++; -		if(current_cert != cert_chain->end()) -		{ -			(*current_cert)->getLLSD(current_cert_info); -		} -	} -	if (validation_policy & VALIDATION_POLICY_TRUSTED) -	{ -		// we reached the end without finding a trusted cert. -        LLSD last_cert_info; -        ((*cert_chain)[cert_chain->size()-1])->getLLSD(last_cert_info); -		LLTHROW(LLCertValidationTrustException(last_cert_info)); -	} -    else -    { -        LL_DEBUGS("SECAPI") << "! Caching untrusted cert for '" << subject_name << "'" -                            << " skeyid '" << skeyid << "' in cert store because ! VALIDATION_POLICY_TRUSTED" -                            << LL_ENDL;	 -        mTrustedCertCache[skeyid] = std::pair<LLDate, LLDate>(from_time, to_time);	 -    } -} - - -// LLSecAPIBasicHandler Class -// Interface handler class for the various security storage handlers. - -// We read the file on construction, and write it on destruction.  This -// means multiple processes cannot modify the datastore. -LLSecAPIBasicHandler::LLSecAPIBasicHandler(const std::string& protected_data_file, -										   const std::string& legacy_password_path) -{ -	mProtectedDataFilename = protected_data_file; -	mProtectedDataMap = LLSD::emptyMap(); -	mLegacyPasswordPath = legacy_password_path; - -} - -LLSecAPIBasicHandler::LLSecAPIBasicHandler() -{ -} - - -void LLSecAPIBasicHandler::init() -{ -	mProtectedDataMap = LLSD::emptyMap(); -	if (mProtectedDataFilename.length() == 0) -	{ -		mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, -															"bin_conf.dat"); -		mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat"); -	 -		mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, -															"bin_conf.dat");	 -		std::string store_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, -														"CA.pem"); -		 -		 -		LL_INFOS("SECAPI") << "Loading user certificate store from " << store_file << LL_ENDL; -		mStore = new LLBasicCertificateStore(store_file); -		 -		// grab the application ca-bundle.crt file that contains the well-known certs shipped -		// with the product -		std::string ca_file_path = gDirUtilp->getCAFile(); -		LL_INFOS("SECAPI") << "Loading application certificate store from " << ca_file_path << LL_ENDL; -		LLPointer<LLBasicCertificateStore> app_ca_store = new LLBasicCertificateStore(ca_file_path); -		 -		// push the applicate CA files into the store, therefore adding any new CA certs that  -		// updated -		for(LLCertificateVector::iterator i = app_ca_store->begin(); -			i != app_ca_store->end(); -			i++) -		{ -			mStore->add(*i); -		} -		 -	} -	_readProtectedData(); // initialize mProtectedDataMap -						  // may throw LLProtectedDataException if saved datamap is not decryptable -} -LLSecAPIBasicHandler::~LLSecAPIBasicHandler() -{ -	_writeProtectedData(); -} - -void LLSecAPIBasicHandler::_readProtectedData(unsigned char *unique_id, U32 id_len) -{ -	// attempt to load the file into our map -	LLPointer<LLSDParser> parser = new LLSDXMLParser(); -	llifstream protected_data_stream(mProtectedDataFilename.c_str(),  -									llifstream::binary); - -	if (!protected_data_stream.fail()) { -		U8 salt[STORE_SALT_SIZE]; -		U8 buffer[BUFFER_READ_SIZE]; -		U8 decrypted_buffer[BUFFER_READ_SIZE]; -		int decrypted_length;	 -		LLXORCipher cipher(unique_id, id_len); - -		// read in the salt and key -		protected_data_stream.read((char *)salt, STORE_SALT_SIZE); -		if (protected_data_stream.gcount() < STORE_SALT_SIZE) -		{ -			LLTHROW(LLProtectedDataException("Config file too short.")); -		} - -		cipher.decrypt(salt, STORE_SALT_SIZE);		 - -		// totally lame.  As we're not using the OS level protected data, we need to -		// at least obfuscate the data.  We do this by using a salt stored at the head of the file -		// to encrypt the data, therefore obfuscating it from someone using simple existing tools. -		// We do include the MAC address as part of the obfuscation, which would require an -		// attacker to get the MAC address as well as the protected store, which improves things -		// somewhat.  It would be better to use the password, but as this store -		// will be used to store the SL password when the user decides to have SL remember it,  -		// so we can't use that.  OS-dependent store implementations will use the OS password/storage  -		// mechanisms and are considered to be more secure. -		// We've a strong intent to move to OS dependent protected data stores. -		 - -		// read in the rest of the file. -		EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); -        // todo: ctx error handling - -		EVP_DecryptInit(ctx, EVP_rc4(), salt, NULL); -		// allocate memory: -		std::string decrypted_data;	 -		 -		while(protected_data_stream.good()) { -			// read data as a block: -			protected_data_stream.read((char *)buffer, BUFFER_READ_SIZE); -			 -			EVP_DecryptUpdate(ctx, decrypted_buffer, &decrypted_length,  -							  buffer, protected_data_stream.gcount()); -			decrypted_data.append((const char *)decrypted_buffer, protected_data_stream.gcount()); -		} -		 -		// RC4 is a stream cipher, so we don't bother to EVP_DecryptFinal, as there is -		// no block padding. -        EVP_CIPHER_CTX_free(ctx); -		std::istringstream parse_stream(decrypted_data); -		if (parser->parse(parse_stream, mProtectedDataMap,  -						  LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) -		{ -			LLTHROW(LLProtectedDataException("Config file cannot be decrypted.")); -		} -	} -} - -void LLSecAPIBasicHandler::_readProtectedData() -{ -    unsigned char unique_id[MAC_ADDRESS_BYTES]; -    try -    { -        // try default id -        LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); -        _readProtectedData(unique_id, sizeof(unique_id)); -    } -    catch(LLProtectedDataException&) -    { -        // try with legacy id, it will return false if it is identical to getUniqueID -        // or if it is not assigned/not in use -        if (LLMachineID::getLegacyID(unique_id, sizeof(unique_id))) -        { -            _readProtectedData(unique_id, sizeof(unique_id)); -        } -        else -        { -            throw; -        } -    } -} - -void LLSecAPIBasicHandler::_writeProtectedData() -{	 -	std::ostringstream formatted_data_ostream; -	U8 salt[STORE_SALT_SIZE]; -	U8 buffer[BUFFER_READ_SIZE]; -	U8 encrypted_buffer[BUFFER_READ_SIZE]; - -	 -	if(mProtectedDataMap.isUndefined()) -	{ -		LLFile::remove(mProtectedDataFilename); -		return; -	} -	// create a string with the formatted data. -	LLSDSerialize::toXML(mProtectedDataMap, formatted_data_ostream); -	std::istringstream formatted_data_istream(formatted_data_ostream.str()); -	// generate the seed -	RAND_bytes(salt, STORE_SALT_SIZE); - -	 -	// write to a temp file so we don't clobber the initial file if there is -	// an error. -	std::string tmp_filename = mProtectedDataFilename + ".tmp"; -	 -	llofstream protected_data_stream(tmp_filename.c_str(),  -                                     std::ios_base::binary); -    EVP_CIPHER_CTX *ctx = NULL; -	try -	{ -		 -		ctx = EVP_CIPHER_CTX_new(); -        // todo: ctx error handling - -		EVP_EncryptInit(ctx, EVP_rc4(), salt, NULL); -		unsigned char unique_id[MAC_ADDRESS_BYTES]; -        LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); -		LLXORCipher cipher(unique_id, sizeof(unique_id)); -		cipher.encrypt(salt, STORE_SALT_SIZE); -		protected_data_stream.write((const char *)salt, STORE_SALT_SIZE); - -		while (formatted_data_istream.good()) -		{ -			formatted_data_istream.read((char *)buffer, BUFFER_READ_SIZE); -			if(formatted_data_istream.gcount() == 0) -			{ -				break; -			} -			int encrypted_length; -			EVP_EncryptUpdate(ctx, encrypted_buffer, &encrypted_length,  -						  buffer, formatted_data_istream.gcount()); -			protected_data_stream.write((const char *)encrypted_buffer, encrypted_length); -		} -		 -		// no EVP_EncrypteFinal, as this is a stream cipher -        EVP_CIPHER_CTX_free(ctx); - -		protected_data_stream.close(); -	} -	catch (...) -	{ -		LOG_UNHANDLED_EXCEPTION("LLProtectedDataException(Error writing Protected Data Store)"); -		// it's good practice to clean up any secure information on error -		// (even though this file isn't really secure.  Perhaps in the future -		// it may be, however. -		LLFile::remove(tmp_filename); - -        if (ctx) -        { -            EVP_CIPHER_CTX_free(ctx); -        } - -		// EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData() -		// Decided throwing an exception here was overkill until we figure out why this happens -		//LLTHROW(LLProtectedDataException("Error writing Protected Data Store")); -	} - -	try -	{ -		// move the temporary file to the specified file location. -		if(((	(LLFile::isfile(mProtectedDataFilename) != 0) -			 && (LLFile::remove(mProtectedDataFilename) != 0))) -		   || (LLFile::rename(tmp_filename, mProtectedDataFilename))) -		{ -			LL_WARNS() << "LLProtectedDataException(Could not overwrite protected data store)" << LL_ENDL; -			LLFile::remove(tmp_filename); - -			// EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData() -			// Decided throwing an exception here was overkill until we figure out why this happens -			//LLTHROW(LLProtectedDataException("Could not overwrite protected data store")); -		} -	} -	catch (...) -	{ -		LOG_UNHANDLED_EXCEPTION(STRINGIZE("renaming '" << tmp_filename << "' to '" -										  << mProtectedDataFilename << "'")); -		// it's good practice to clean up any secure information on error -		// (even though this file isn't really secure.  Perhaps in the future -		// it may be, however). -		LLFile::remove(tmp_filename); - -		//crash in LLSecAPIBasicHandler::_writeProtectedData() -		// Decided throwing an exception here was overkill until we figure out why this happens -		//LLTHROW(LLProtectedDataException("Error writing Protected Data Store")); -	} -} - -// instantiate a certificate from a pem string -LLPointer<LLCertificate> LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert) -{ -	LLPointer<LLCertificate> result = new LLBasicCertificate(pem_cert); -	return result; -} -		 - -		 -// instiate a certificate from an openssl X509 structure -LLPointer<LLCertificate> LLSecAPIBasicHandler::getCertificate(X509* openssl_cert) -{ -	LLPointer<LLCertificate> result = new LLBasicCertificate(openssl_cert); -	return result;		 -} -		 -// instantiate a chain from an X509_STORE_CTX -LLPointer<LLCertificateChain> LLSecAPIBasicHandler::getCertificateChain(X509_STORE_CTX* chain) -{ -	LLPointer<LLCertificateChain> result = new LLBasicCertificateChain(chain); -	return result; -} -		 -// instantiate a cert store given it's id.  if a persisted version -// exists, it'll be loaded.  If not, one will be created (but not -// persisted) -LLPointer<LLCertificateStore> LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id) -{ -	return mStore; -} -		 -// retrieve protected data -LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type, -											const std::string& data_id) -{ - -	if (mProtectedDataMap.has(data_type) &&  -		mProtectedDataMap[data_type].isMap() &&  -		mProtectedDataMap[data_type].has(data_id)) -	{ -		return mProtectedDataMap[data_type][data_id]; -	} -																				 -	return LLSD(); -} - -void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type, -											   const std::string& data_id) -{ -	if (mProtectedDataMap.has(data_type) && -		mProtectedDataMap[data_type].isMap() && -		mProtectedDataMap[data_type].has(data_id)) -		{ -			mProtectedDataMap[data_type].erase(data_id); -		} -} - - -// -// persist data in a protected store -// -void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type, -											const std::string& data_id, -											const LLSD& data) -{ -	if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) { -		mProtectedDataMap[data_type] = LLSD::emptyMap(); -	} -	 -	mProtectedDataMap[data_type][data_id] = data;  -} - -// persist data in a protected store's map -void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type, -											 const std::string& data_id, -											 const std::string& map_elem, -											 const LLSD& data) -{ -    if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) { -        mProtectedDataMap[data_type] = LLSD::emptyMap(); -    } - -    if (!mProtectedDataMap[data_type].has(data_id) || !mProtectedDataMap[data_type][data_id].isMap()) { -        mProtectedDataMap[data_type][data_id] = LLSD::emptyMap(); -    } - -    mProtectedDataMap[data_type][data_id][map_elem] = data; -} - -// remove data from protected store's map -void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type, -												  const std::string& data_id, -												  const std::string& map_elem) -{ -    if (mProtectedDataMap.has(data_type) && -        mProtectedDataMap[data_type].isMap() && -        mProtectedDataMap[data_type].has(data_id) && -        mProtectedDataMap[data_type][data_id].isMap() && -        mProtectedDataMap[data_type][data_id].has(map_elem)) -    { -        mProtectedDataMap[data_type][data_id].erase(map_elem); -    } -} - -void LLSecAPIBasicHandler::syncProtectedMap() -{ -    // TODO - consider unifing these functions -    _writeProtectedData(); -} -// -// Create a credential object from an identifier and authenticator.  credentials are -// per grid. -LLPointer<LLCredential> LLSecAPIBasicHandler::createCredential(const std::string& grid, -															   const LLSD& identifier,  -															   const LLSD& authenticator) -{ -	LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid); -	result->setCredentialData(identifier, authenticator); -	return result; -} - -// Load a credential from default credential store, given the grid -LLPointer<LLCredential> LLSecAPIBasicHandler::loadCredential(const std::string& grid) -{ -	LLSD credential = getProtectedData(DEFAULT_CREDENTIAL_STORAGE, grid); -	LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid); -	if(credential.isMap() &&  -	   credential.has("identifier")) -	{ - -		LLSD identifier = credential["identifier"]; -		LLSD authenticator; -		if (credential.has("authenticator")) -		{ -			authenticator = credential["authenticator"]; -		} -		result->setCredentialData(identifier, authenticator); -	} -	else -	{ -		// credential was not in protected storage, so pull the credential -		// from the legacy store. -		std::string first_name = gSavedSettings.getString("FirstName"); -		std::string last_name = gSavedSettings.getString("LastName"); -		 -		if ((first_name != "") && -			(last_name != "")) -		{ -			LLSD identifier = LLSD::emptyMap(); -			LLSD authenticator; -			identifier["type"] = "agent"; -			identifier["first_name"] = first_name; -			identifier["last_name"] = last_name; -			 -			std::string legacy_password = _legacyLoadPassword(); -			if (legacy_password.length() > 0) -			{ -				authenticator = LLSD::emptyMap(); -				authenticator["type"] = "hash"; -				authenticator["algorithm"] = "md5"; -				authenticator["secret"] = legacy_password; -			} -			result->setCredentialData(identifier, authenticator); -		}		 -	} -	return result; -} - -// Save the credential to the credential store.  Save the authenticator also if requested. -// That feature is used to implement the 'remember password' functionality. -void LLSecAPIBasicHandler::saveCredential(LLPointer<LLCredential> cred, bool save_authenticator) -{ -	LLSD credential = LLSD::emptyMap(); -	credential["identifier"] = cred->getIdentifier();  -	if (save_authenticator)  -	{ -		credential["authenticator"] = cred->getAuthenticator(); -	} -	LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; -	setProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid(), credential); -	//*TODO: If we're saving Agni credentials, should we write the -	// credentials to the legacy password.dat/etc? -	_writeProtectedData(); -} - -// Remove a credential from the credential store. -void LLSecAPIBasicHandler::deleteCredential(LLPointer<LLCredential> cred) -{ -	LLSD undefVal; -	deleteProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid()); -	cred->setCredentialData(undefVal, undefVal); -	_writeProtectedData(); -} - -// has map of credentials declared as specific storage -bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const std::string& grid) -{ -    if (storage == DEFAULT_CREDENTIAL_STORAGE) -    { -        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; -    } - -    LLSD credential = getProtectedData(storage, grid); - -    return credential.isMap(); -} - -// returns true if map is empty or does not exist -bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid) -{ -    if (storage == DEFAULT_CREDENTIAL_STORAGE) -    { -        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; -    } - -    LLSD credential = getProtectedData(storage, grid); - -    return !credential.isMap() || credential.size() == 0; -} - -// Load map of credentials from specified credential store, given the grid -void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map) -{ -    if (storage == DEFAULT_CREDENTIAL_STORAGE) -    { -        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; -    } - -    LLSD credential = getProtectedData(storage, grid); -    if (credential.isMap()) -    { -        LLSD::map_const_iterator crd_it = credential.beginMap(); -        for (; crd_it != credential.endMap(); crd_it++) -        { -            LLSD::String name = crd_it->first; -            const LLSD &link_map = crd_it->second; -            LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid); -            if (link_map.has("identifier")) -            { -                LLSD identifier = link_map["identifier"]; -                LLSD authenticator; -                if (link_map.has("authenticator")) -                { -                    authenticator = link_map["authenticator"]; -                } -                result->setCredentialData(identifier, authenticator); -            } -            credential_map[name] = result; -        } -    } -} - -LLPointer<LLCredential> LLSecAPIBasicHandler::loadFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) -{ -    if (storage == DEFAULT_CREDENTIAL_STORAGE) -    { -        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; -    } - -    LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid); - -    LLSD credential = getProtectedData(storage, grid); -    if (credential.isMap() && credential.has(userkey) && credential[userkey].has("identifier")) -    { -        LLSD identifier = credential[userkey]["identifier"]; -        LLSD authenticator; -        if (credential[userkey].has("authenticator")) -        { -            authenticator = credential[userkey]["authenticator"]; -        } -        result->setCredentialData(identifier, authenticator); -    } - -    return result; -} - -// add item to map of credentials from specific storage -void LLSecAPIBasicHandler::addToCredentialMap(const std::string& storage, LLPointer<LLCredential> cred, bool save_authenticator) -{ -    if (storage == DEFAULT_CREDENTIAL_STORAGE) -    { -        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; -    } - -    std::string user_id = cred->userID(); -    LLSD credential = LLSD::emptyMap(); -    credential["identifier"] = cred->getIdentifier(); -    if (save_authenticator) -    { -        credential["authenticator"] = cred->getAuthenticator(); -    } -    LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; -    addToProtectedMap(storage, cred->getGrid(), user_id, credential); - -    _writeProtectedData(); -} - -// remove item from map of credentials from specific storage -void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, LLPointer<LLCredential> cred) -{ -    if (storage == DEFAULT_CREDENTIAL_STORAGE) -    { -        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; -    } - -    LLSD undefVal; -    removeFromProtectedMap(storage, cred->getGrid(), cred->userID()); -    cred->setCredentialData(undefVal, undefVal); -    _writeProtectedData(); -} - -// remove item from map of credentials from specific storage -void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) -{ -    if (storage == DEFAULT_CREDENTIAL_STORAGE) -    { -        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; -    } - -    LLSD undefVal; -    LLPointer<LLCredential> cred = loadFromCredentialMap(storage, grid, userkey); -    removeFromProtectedMap(storage, grid, userkey); -    cred->setCredentialData(undefVal, undefVal); -    _writeProtectedData(); -} - -// remove item from map of credentials from specific storage -void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const std::string& grid) -{ -    deleteProtectedData(storage, grid); -    _writeProtectedData(); -} - -// load the legacy hash for agni, and decrypt it given the  -// mac address -std::string LLSecAPIBasicHandler::_legacyLoadPassword() -{ -	const S32 HASHED_LENGTH = 32;	 -	std::vector<U8> buffer(HASHED_LENGTH); -	llifstream password_file(mLegacyPasswordPath.c_str(), llifstream::binary); -	 -	if(password_file.fail()) -	{ -		return std::string(""); -	} -	 -	password_file.read((char*)&buffer[0], buffer.size()); -	if(password_file.gcount() != buffer.size()) -	{ -		return std::string(""); -	} -	 -	// Decipher with MAC address -	unsigned char unique_id[MAC_ADDRESS_BYTES]; -    LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); -	LLXORCipher cipher(unique_id, sizeof(unique_id)); -	cipher.decrypt(&buffer[0], buffer.size()); -	 -	return std::string((const char*)&buffer[0], buffer.size()); -} - - -// return an identifier for the user -std::string LLSecAPIBasicCredential::userID() const -{ -	if (!mIdentifier.isMap()) -	{ -		return mGrid + "(null)"; -	} -	else if ((std::string)mIdentifier["type"] == "agent") -	{ -		std::string id = (std::string)mIdentifier["first_name"] + "_" + (std::string)mIdentifier["last_name"]; -		LLStringUtil::toLower(id); -		return id; -	} -	else if ((std::string)mIdentifier["type"] == "account") -	{ -		std::string id = (std::string)mIdentifier["account_name"]; -		LLStringUtil::toLower(id); -		return id; -	} - -	return "unknown"; -} - -// return a printable user identifier -std::string LLSecAPIBasicCredential::asString() const -{ -	if (!mIdentifier.isMap()) -	{ -		return mGrid + ":(null)"; -	} -	else if ((std::string)mIdentifier["type"] == "agent") -	{ -		return mGrid + ":" + (std::string)mIdentifier["first_name"] + " " + (std::string)mIdentifier["last_name"]; -	} -	else if ((std::string)mIdentifier["type"] == "account") -	{ -		return mGrid + ":" + (std::string)mIdentifier["account_name"]; -	} - -	return mGrid + ":(unknown type)"; -} - - -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()); -	} -	 -} +/**
 + * @file llsechandler_basic.cpp
 + * @brief Security API for services such as certificate handling
 + * secure local storage, etc.
 + *
 + * $LicenseInfo:firstyear=2003&license=viewerlgpl$
 + * Second Life Viewer Source Code
 + * Copyright (C) 2010, Linden Research, Inc.
 + *
 + * This library 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;
 + * version 2.1 of the License only.
 + *
 + * This library 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 this library; if not, write to the Free Software
 + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 + *
 + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 + * $/LicenseInfo$
 + */
 +
 +
 +#include "llviewerprecompiledheaders.h"
 +#include "llsecapi.h"
 +#include "llsechandler_basic.h"
 +#include "llsdserialize.h"
 +#include "llviewernetwork.h"
 +#include "llxorcipher.h"
 +#include "llfile.h"
 +#include "lldir.h"
 +#include "llviewercontrol.h"
 +#include "llexception.h"
 +#include "stringize.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>
 +#include "llmachineid.h"
 +
 +
 +static const std::string DEFAULT_CREDENTIAL_STORAGE = "credential";
 +
 +// 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);
 +std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value);
 +
 +LLSD _basic_constraints_ext(X509* cert);
 +LLSD _key_usage_ext(X509* cert);
 +LLSD _ext_key_usage_ext(X509* cert);
 +std::string _subject_key_identifier(X509 *cert);
 +LLSD _authority_key_identifier(X509* cert);
 +void _validateCert(int validation_policy,
 +                   LLPointer<LLCertificate> cert,
 +                   const LLSD& validation_params,
 +                   int depth);
 +
 +LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert,
 +                                       const LLSD* validation_params)
 +{
 +    // 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;
 +        LLTHROW(LLAllocationCertException(LLSD::emptyMap()));
 +    }
 +    mCert = NULL;
 +    PEM_read_bio_X509(pem_bio, &mCert, 0, NULL);
 +    BIO_free(pem_bio);
 +    if (!mCert)
 +    {
 +        LL_WARNS("SECAPI") << "Could not decode certificate to x509." << LL_ENDL;
 +        LLTHROW(LLInvalidCertificate(LLSD::emptyMap()));
 +    }
 +}
 +
 +
 +LLBasicCertificate::LLBasicCertificate(X509* pCert,
 +                                       const LLSD* validation_params)
 +{
 +    if (!pCert)
 +    {
 +        LLTHROW(LLInvalidCertificate(LLSD::emptyMap()));
 +    }
 +    mCert = X509_dup(pCert);
 +    // it is tempting to run _validateCert here, but doing so causes problems
 +    // the trick is figuring out which aspects to validate. TBD
 +}
 +
 +LLBasicCertificate::~LLBasicCertificate()
 +{
 +    if(mCert)
 +    {
 +        X509_free(mCert);
 +        mCert = NULL;
 +    }
 +}
 +
 +//
 +// retrieve the pem using the openssl functionality
 +std::string LLBasicCertificate::getPem() const
 +{
 +    char * pem_bio_chars = NULL;
 +    // a BIO is the equivalent of a 'std::stream', and
 +    // can be a file, mem stream, whatever.  Grab a memory based
 +    // BIO for the result
 +    BIO *pem_bio = BIO_new(BIO_s_mem());
 +    if (!pem_bio)
 +    {
 +        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);
 +    std::string result = std::string(pem_bio_chars, length);
 +    BIO_free(pem_bio);
 +    return result;
 +}
 +
 +// get the DER encoding for the cert
 +// DER is a binary encoding format for certs...
 +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)
 +    {
 +        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);
 +    std::vector<U8> result(length);
 +    // vectors are guranteed to be a contiguous chunk of memory.
 +    memcpy(&result[0], der_bio_data,  length);
 +    BIO_free(der_bio);
 +    return result;
 +}
 +
 +
 +void LLBasicCertificate::getLLSD(LLSD &llsd)
 +{
 +    if (mLLSDInfo.isUndefined())
 +    {
 +        _initLLSD();
 +    }
 +    llsd = mLLSDInfo;
 +}
 +
 +// Initialize the LLSD info for the certificate
 +LLSD& LLBasicCertificate::_initLLSD()
 +{
 +
 +    // call the various helpers to build the LLSD
 +    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)
 +    {
 +        mLLSDInfo[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn);
 +    }
 +
 +    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));
 +    // 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);
 +    mLLSDInfo[CERT_SUBJECT_KEY_IDENTFIER] = _subject_key_identifier(mCert);
 +    mLLSDInfo[CERT_AUTHORITY_KEY_IDENTIFIER] = _authority_key_identifier(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);
 +            }
 +        }
 +
 +        BASIC_CONSTRAINTS_free( bs );
 +    }
 +    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;
 +}
 +
 +// 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);
 +            }
 +        }
 +
 +        EXTENDED_KEY_USAGE_free( eku );
 +    }
 +    return result;
 +}
 +
 +// retrieve the subject key identifier of the cert
 +std::string _subject_key_identifier(X509 *cert)
 +{
 +    std::string result;
 +    ASN1_OCTET_STRING *skeyid = (ASN1_OCTET_STRING *)X509_get_ext_d2i(cert, NID_subject_key_identifier, NULL, NULL);
 +    if(skeyid)
 +    {
 +        result = cert_string_from_octet_string(skeyid);
 +        ASN1_OCTET_STRING_free( skeyid );
 +    }
 +    return result;
 +}
 +
 +// retrieve the authority key identifier of the cert
 +LLSD _authority_key_identifier(X509* cert)
 +{
 +    LLSD result;
 +    AUTHORITY_KEYID *akeyid = (AUTHORITY_KEYID *)X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL);
 +    if(akeyid)
 +    {
 +        result = LLSD::emptyMap();
 +        if(akeyid->keyid)
 +        {
 +            result[CERT_AUTHORITY_KEY_IDENTIFIER_ID] = cert_string_from_octet_string(akeyid->keyid);
 +        }
 +        if(akeyid->serial)
 +        {
 +            result[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL] = cert_string_from_asn1_integer(akeyid->serial);
 +        }
 +
 +        AUTHORITY_KEYID_free( akeyid );
 +    }
 +    // we ignore the issuer name in the authority key identifier, we check the issue name via
 +    // the the issuer name entry in the cert.
 +    return result;
 +}
 +
 +// retrieve an openssl x509 object,
 +// which must be freed by X509_free
 +X509* LLBasicCertificate::getOpenSSLX509() const
 +{
 +    return X509_dup(mCert);
 +}
 +
 +// generate a single string containing the subject or issuer
 +// name of the cert.
 +std::string cert_string_name_from_X509_NAME(X509_NAME* name)
 +{
 +    char * name_bio_chars = NULL;
 +    // get a memory bio
 +    BIO *name_bio = BIO_new(BIO_s_mem());
 +    // stream the name into the bio.  The name will be in the 'short name' format
 +    X509_NAME_print_ex(name_bio, name, 0, XN_FLAG_RFC2253);
 +    int length = BIO_get_mem_data(name_bio, &name_bio_chars);
 +    std::string result = std::string(name_bio_chars, length);
 +    BIO_free(name_bio);
 +    return result;
 +}
 +
 +// generate an LLSD from a certificate name (issuer or subject name).
 +// the name will be strings indexed by the 'long form'
 +LLSD cert_name_from_X509_NAME(X509_NAME* name)
 +{
 +    LLSD result = LLSD::emptyMap();
 +    int name_entries = X509_NAME_entry_count(name);
 +    for (int entry_index=0; entry_index < name_entries; entry_index++)
 +    {
 +        char buffer[32];
 +        X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, entry_index);
 +
 +        std::string name_value = std::string((const char*)ASN1_STRING_get0_data(X509_NAME_ENTRY_get_data(entry)),
 +                                             ASN1_STRING_length(X509_NAME_ENTRY_get_data(entry)));
 +
 +        ASN1_OBJECT* name_obj = X509_NAME_ENTRY_get_object(entry);
 +        OBJ_obj2txt(buffer, sizeof(buffer), name_obj, 0);
 +        std::string obj_buffer_str = std::string(buffer);
 +        result[obj_buffer_str] = name_value;
 +    }
 +
 +    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 cert_string_from_asn1_integer(ASN1_INTEGER* value)
 +{
 +    std::string result;
 +    BIGNUM *bn = ASN1_INTEGER_to_BN(value, NULL);
 +    if(bn)
 +    {
 +        char * ascii_bn = BN_bn2hex(bn);
 +
 +        if(ascii_bn)
 +        {
 +            result = ascii_bn;
 +            OPENSSL_free(ascii_bn);
 +        }
 +        BN_free(bn);
 +    }
 +    return result;
 +}
 +
 +// Generate a string from an OCTET string.
 +// we retrieve as a
 +
 +std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value)
 +{
 +
 +    std::stringstream result;
 +    result << std::hex << std::setprecision(2);
 +    for (int i=0; i < value->length; i++)
 +    {
 +        if (i != 0)
 +        {
 +            result << ":";
 +        }
 +        result  << std::setfill('0') << std::setw(2) << (int)value->data[i];
 +    }
 +    return result.str();
 +}
 +
 +// 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 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;
 +}
 +
 +// retrieve a date structure from an ASN1 time, for
 +// validity checking.
 +LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time)
 +{
 +
 +    struct tm timestruct = {0};
 +    int i = asn1_time->length;
 +
 +    if (i < 10)
 +    {
 +        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');
 +
 +    /* Deal with Year 2000 */
 +    if (timestruct.tm_year < 70)
 +        timestruct.tm_year += 100;
 +
 +    timestruct.tm_mon = (asn1_time->data[2]-'0') * 10 + (asn1_time->data[3]-'0') - 1;
 +    timestruct.tm_mday = (asn1_time->data[4]-'0') * 10 + (asn1_time->data[5]-'0');
 +    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');
 +
 +#if LL_WINDOWS
 +    return LLDate((F64)_mkgmtime(×truct));
 +#else // LL_WINDOWS
 +    return LLDate((F64)timegm(×truct));
 +#endif // LL_WINDOWS
 +}
 +
 +// 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)
 +{
 +    // 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.
 +    bool found = false;
 +    iterator cert = begin();
 +    while ( !found && cert != end() )
 +    {
 +        found = true;
 +        LLSD cert_info;
 +        (*cert)->getLLSD(cert_info);
 +        for (LLSD::map_const_iterator param = params.beginMap();
 +             found && param != params.endMap();
 +             param++)
 +        {
 +            if (   !cert_info.has((std::string)param->first)
 +                || !valueCompareLLSD(cert_info[(std::string)param->first], param->second))
 +            {
 +                found = false;
 +            }
 +        }
 +        if (!found)
 +        {
 +            cert++;
 +        }
 +    }
 +    return cert;
 +}
 +
 +// 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(cert_info);
 +    if (cert_info.isMap() && cert_info.has(CERT_SUBJECT_KEY_IDENTFIER))
 +    {
 +        LLSD existing_cert_info = LLSD::emptyMap();
 +        existing_cert_info[CERT_SUBJECT_KEY_IDENTFIER] = cert_info[CERT_SUBJECT_KEY_IDENTFIER];
 +        if(find(existing_cert_info) == end())
 +        {
 +            BasicIteratorImpl *basic_iter = dynamic_cast<BasicIteratorImpl*>(_iter.mImpl.get());
 +            if (basic_iter)
 +            {
 +                mCerts.insert(basic_iter->mIter, cert);
 +            }
 +            else
 +            {
 +                LL_WARNS("SECAPI") << "Invalid certificate postion vector"
 +                                   << LL_ENDL;
 +            }
 +        }
 +        else
 +        {
 +            LL_DEBUGS("SECAPI") << "Certificate already in vector: "
 +                                << "'" << cert_info << "'"
 +                                << LL_ENDL;
 +        }
 +
 +    }
 +    else
 +    {
 +        LL_WARNS("SECAPI") << "Certificate does not have Subject Key Identifier; not inserted: "
 +                           << "'" << cert_info << "'"
 +                           << LL_ENDL;
 +    }
 +}
 +
 +// 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 crt file such as the ca-bundle.crt in the existing SL implementation.
 +LLBasicCertificateStore::LLBasicCertificateStore(const std::string& filename)
 +{
 +    mFilename = filename;
 +    load_from_file(filename);
 +}
 +
 +void LLBasicCertificateStore::load_from_file(const std::string& filename)
 +{
 +    int loaded = 0;
 +    int rejected = 0;
 +
 +    // scan the PEM file extracting each certificate
 +    if (LLFile::isfile(filename))
 +    {
 +        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
 +                    {
 +                        LLPointer<LLBasicCertificate> new_cert(new LLBasicCertificate(cert_x509));
 +                        LLSD validation_params;
 +                        _validateCert(VALIDATION_POLICY_TIME,
 +                                      new_cert,
 +                                      validation_params,
 +                                      0);
 +                        add(new_cert);
 +                        LL_DEBUGS("SECAPI") << "Loaded valid cert for "
 +                                            << "Name '" << cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509)) << "'";
 +                        std::string skeyid(_subject_key_identifier(cert_x509));
 +                        LL_CONT << " Id '" << skeyid << "'"
 +                                << LL_ENDL;
 +                        loaded++;
 +                    }
 +                    catch (LLCertException& cert_exception)
 +                    {
 +                        LLSD cert_info(cert_exception.getCertData());
 +                        LL_DEBUGS("SECAPI_BADCERT","SECAPI") << "invalid certificate (" << cert_exception.what() << "): " << cert_info << LL_ENDL;
 +                        rejected++;
 +                    }
 +                    catch (...)
 +                    {
 +                        LOG_UNHANDLED_EXCEPTION("creating certificate from the certificate store file");
 +                        rejected++;
 +                    }
 +                    X509_free(cert_x509);
 +                    cert_x509 = NULL;
 +                }
 +                BIO_free(file_bio);
 +            }
 +            else
 +            {
 +                LL_WARNS("SECAPI") << "BIO read failed for " << filename << LL_ENDL;
 +            }
 +
 +            LL_INFOS("SECAPI") << "loaded " << loaded << " good certificates (rejected " << rejected << ") from " << filename << LL_ENDL;
 +        }
 +        else
 +        {
 +            LL_WARNS("SECAPI") << "Could not allocate a file BIO" << LL_ENDL;
 +        }
 +    }
 +    else
 +    {
 +        // since the user certificate store may not be there, this is not a warning
 +        LL_INFOS("SECAPI") << "Certificate store not found at " << filename << LL_ENDL;
 +    }
 +}
 +
 +
 +LLBasicCertificateStore::~LLBasicCertificateStore()
 +{
 +}
 +
 +
 +// persist the store
 +void LLBasicCertificateStore::save()
 +{
 +    llofstream file_store(mFilename.c_str(), std::ios_base::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;
 +    }
 +}
 +
 +// return the store id
 +std::string LLBasicCertificateStore::storeId() const
 +{
 +    // this is the basic handler which uses the ca-bundle.crt store,
 +    // so we ignore this.
 +    return std::string("");
 +}
 +
 +
 +//
 +// 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(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) || X509_STORE_CTX_get0_cert(store) == 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(X509_STORE_CTX_get0_cert(store));
 +
 +    add(current);
 +    if(X509_STORE_CTX_get0_untrusted(store) != 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(X509_STORE_CTX_get0_untrusted(store)); i++)
 +        {
 +            LLPointer<LLCertificate> cert = new LLBasicCertificate(sk_X509_value(X509_STORE_CTX_get0_untrusted(store), i));
 +            untrusted_certs.add(cert);
 +
 +        }
 +        while(untrusted_certs.size() > 0)
 +        {
 +            LLSD find_data = LLSD::emptyMap();
 +            LLSD cert_data;
 +            current->getLLSD(cert_data);
 +            // 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;
 +            }
 +        }
 +    }
 +}
 +
 +
 +// 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;
 +}
 +
 +
 +// 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)
 +{
 +    std::string new_hostname = hostname;
 +    std::string new_cn = common_name;
 +
 +    // find the last '.' in the hostname and the match name.
 +    int subdomain_pos = new_hostname.find_last_of('.');
 +    int subcn_pos = new_cn.find_last_of('.');
 +
 +    // if the last char is a '.', strip it
 +    if(subdomain_pos == (new_hostname.length()-1))
 +    {
 +        new_hostname = new_hostname.substr(0, subdomain_pos);
 +        subdomain_pos = new_hostname.find_last_of('.');
 +    }
 +    if(subcn_pos == (new_cn.length()-1))
 +    {
 +        new_cn = new_cn.substr(0, subcn_pos);
 +        subcn_pos = new_cn.find_last_of('.');
 +    }
 +
 +    // Check to see if there are any further '.' in the string.
 +    while((subcn_pos != std::string::npos) && (subdomain_pos != std::string::npos))
 +    {
 +        // snip out last subdomain in both the match string and the hostname
 +        // The last bit for 'my.current.host.com' would be 'com'
 +        std::string cn_part = new_cn.substr(subcn_pos+1, std::string::npos);
 +        std::string hostname_part = new_hostname.substr(subdomain_pos+1, std::string::npos);
 +
 +        if(!_cert_subdomain_wildcard_match(new_hostname.substr(subdomain_pos+1, std::string::npos),
 +                                           cn_part))
 +        {
 +            return false;
 +        }
 +        new_hostname = new_hostname.substr(0, subdomain_pos);
 +        new_cn = new_cn.substr(0, subcn_pos);
 +        subdomain_pos = new_hostname.find_last_of('.');
 +        subcn_pos = new_cn.find_last_of('.');
 +    }
 +    // check to see if the most significant portion of the common name is '*'.  If so, we can
 +    // simply return success as child domains are also matched.
 +    if(new_cn == "*")
 +    {
 +        // if it's just a '*' we support all child domains as well, so '*.
 +        return true;
 +    }
 +
 +    return _cert_subdomain_wildcard_match(new_hostname, new_cn);
 +
 +}
 +
 +// validate that the LLSD array in llsd_set contains the llsd_value
 +bool _LLSDArrayIncludesValue(const LLSD& llsd_set, LLSD llsd_value)
 +{
 +    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;
 +}
 +
 +void _validateCert(int validation_policy,
 +                  LLPointer<LLCertificate> cert,
 +                  const LLSD& validation_params,
 +                  int depth)
 +{
 +    LLSD current_cert_info;
 +    cert->getLLSD(current_cert_info);
 +    // check basic properties exist in the cert
 +    if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info.has(CERT_SUBJECT_NAME_STRING))
 +    {
 +        LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Name"));
 +    }
 +
 +    if(!current_cert_info.has(CERT_ISSUER_NAME_STRING))
 +    {
 +        LLTHROW(LLCertException(current_cert_info, "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))
 +    {
 +        LLTHROW(LLCertException(current_cert_info, "Cert doesn't have an expiration period"));
 +    }
 +    if (!current_cert_info.has(CERT_SUBJECT_KEY_IDENTFIER))
 +    {
 +        LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Key Id"));
 +    }
 +
 +    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];
 +        }
 +
 +        if((validation_date < current_cert_info[CERT_VALID_FROM].asDate()) ||
 +           (validation_date > current_cert_info[CERT_VALID_TO].asDate()))
 +        {
 +            LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date));
 +        }
 +    }
 +    if (validation_policy & VALIDATION_POLICY_SSL_KU)
 +    {
 +        // This stanza of code was changed 2021-06-09 as per details in SL-15370.
 +        // Brief summary: a renewed certificate from Akamai only contains the
 +        // 'Digital Signature' field and not the 'Key Encipherment' one. This code
 +        // used to look for both and throw an exception at startup (ignored) and
 +        // (for example) when buying L$ in the Viewer (fails with a UI message
 +        // and an entry in the Viewer log). This modified code removes the second
 +        // check for the 'Key Encipherment' field. If Akamai can provide a
 +        // replacement certificate that has both fields, then this modified code
 +        // will not be required.
 +        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)))
 +            )
 +        {
 +            LLTHROW(LLCertKeyUsageValidationException(current_cert_info));
 +        }
 +        // 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_TLS_SERVER_AUTH)))
 +           && (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE],
 +                                         LLSD((std::string)CERT_EKU_SERVER_AUTH)))
 +           )
 +        {
 +            LLTHROW(LLCertKeyUsageValidationException(current_cert_info));
 +        }
 +    }
 +    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)))
 +            {
 +                LLTHROW(LLCertKeyUsageValidationException(current_cert_info));
 +            }
 +    }
 +
 +    // 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])
 +        {
 +                LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info));
 +        }
 +        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())))
 +        {
 +            LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info));
 +        }
 +    }
 +}
 +
 +bool _verify_signature(LLPointer<LLCertificate> parent,
 +                       LLPointer<LLCertificate> child)
 +{
 +    bool verify_result = false;
 +    LLSD cert1, cert2;
 +    parent->getLLSD(cert1);
 +    child->getLLSD(cert2);
 +    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);
 +
 +
 +        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 LLBasicCertificateStore::validate(int validation_policy,
 +                                       LLPointer<LLCertificateChain> cert_chain,
 +                                       const LLSD& validation_params)
 +{
 +    // If --no-verify-ssl-cert was passed on the command line, stop right now.
 +    if (gSavedSettings.getBOOL("NoVerifySSLCert"))
 +    {
 +        LL_WARNS_ONCE("SECAPI") << "All Certificate validation disabled; viewer operation is insecure" << LL_ENDL;
 +        return;
 +    }
 +
 +    if(cert_chain->size() < 1)
 +    {
 +        LLTHROW(LLCertException(LLSD::emptyMap(), "No certs in chain"));
 +    }
 +    iterator current_cert = cert_chain->begin();
 +    LLSD validation_date;
 +    if (validation_params.has(CERT_VALIDATION_DATE))
 +    {
 +        validation_date = validation_params[CERT_VALIDATION_DATE];
 +    }
 +
 +    // get LLSD info from the cert to throw in any exception
 +    LLSD    current_cert_info;
 +    (*current_cert)->getLLSD(current_cert_info);
 +
 +    if (validation_policy & VALIDATION_POLICY_HOSTNAME)
 +    {
 +        if(!validation_params.has(CERT_HOSTNAME))
 +        {
 +            LLTHROW(LLCertException(current_cert_info, "No hostname passed in for validation"));
 +        }
 +        if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info[CERT_SUBJECT_NAME].has(CERT_NAME_CN))
 +        {
 +            LLTHROW(LLInvalidCertificate(current_cert_info));
 +        }
 +
 +        LL_DEBUGS("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_info);
 +        }
 +    }
 +
 +    // check the cache of already validated certs
 +    X509* cert_x509 = (*current_cert)->getOpenSSLX509();
 +    if(!cert_x509)
 +    {
 +        LLTHROW(LLInvalidCertificate(current_cert_info));
 +    }
 +
 +    std::string subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509)));
 +    std::string skeyid(_subject_key_identifier(cert_x509));
 +
 +    LL_DEBUGS("SECAPI") << "attempting to validate cert "
 +                        << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'"
 +                        << " as subject name '" << subject_name << "'"
 +                        << " subject key id '" << skeyid << "'"
 +                        << LL_ENDL;
 +
 +    X509_free( cert_x509 );
 +    cert_x509 = NULL;
 +    if (skeyid.empty())
 +    {
 +        LLTHROW(LLCertException(current_cert_info, "No Subject Key Id"));
 +    }
 +
 +    t_cert_cache::iterator cache_entry = mTrustedCertCache.find(skeyid);
 +    if(cache_entry != mTrustedCertCache.end())
 +    {
 +        // this cert is in the cache, so validate the time.
 +        if (validation_policy & VALIDATION_POLICY_TIME)
 +        {
 +            LLDate validation_date;
 +            if(validation_params.has(CERT_VALIDATION_DATE))
 +            {
 +                validation_date = validation_params[CERT_VALIDATION_DATE];
 +            }
 +            else
 +            {
 +                validation_date = LLDate(time(NULL)); // current time
 +            }
 +
 +            if((validation_date < cache_entry->second.first) ||
 +               (validation_date > cache_entry->second.second))
 +            {
 +                LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date));
 +            }
 +        }
 +        // successfully found in cache
 +        LL_DEBUGS("SECAPI") << "Valid cert for '" << validation_params[CERT_HOSTNAME].asString() << "'"
 +                            << " skeyid '" << skeyid << "'"
 +                            << " found in cache"
 +                            << LL_ENDL;
 +        return;
 +    }
 +    if(current_cert_info.isUndefined())
 +    {
 +        (*current_cert)->getLLSD(current_cert_info);
 +    }
 +    LLDate from_time = current_cert_info[CERT_VALID_FROM].asDate();
 +    LLDate to_time = current_cert_info[CERT_VALID_TO].asDate();
 +    int depth = 0;
 +    LLPointer<LLCertificate> previous_cert;
 +    // loop through the cert chain, validating the current cert against the next one.
 +    while(current_cert != cert_chain->end())
 +    {
 +        int local_validation_policy = validation_policy;
 +        if(current_cert == cert_chain->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))
 +            {
 +                LLSD previous_cert_info;
 +                previous_cert->getLLSD(previous_cert_info);
 +                LLTHROW(LLCertValidationInvalidSignatureException(previous_cert_info));
 +            }
 +        }
 +        _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_search_params = LLSD::emptyMap();
 +        // is the cert itself in the store?
 +        cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = current_cert_info[CERT_SUBJECT_KEY_IDENTFIER];
 +        LLCertificateStore::iterator found_store_cert = find(cert_search_params);
 +        if(found_store_cert != end())
 +        {
 +            mTrustedCertCache[skeyid] = std::pair<LLDate, LLDate>(from_time, to_time);
 +            LL_DEBUGS("SECAPI") << "Valid cert "
 +                                << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'";
 +            X509* cert_x509 = (*found_store_cert)->getOpenSSLX509();
 +            std::string found_cert_subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509)));
 +            X509_free(cert_x509);
 +            LL_CONT << " as '" << found_cert_subject_name << "'"
 +                    << " skeyid '" << current_cert_info[CERT_SUBJECT_KEY_IDENTFIER].asString() << "'"
 +                    << " found in cert store"
 +                    << LL_ENDL;
 +            return;
 +        }
 +
 +        // is the parent in the cert store?
 +
 +        cert_search_params = LLSD::emptyMap();
 +        cert_search_params[CERT_SUBJECT_NAME_STRING] = current_cert_info[CERT_ISSUER_NAME_STRING];
 +        if (current_cert_info.has(CERT_AUTHORITY_KEY_IDENTIFIER))
 +        {
 +            LLSD cert_aki = current_cert_info[CERT_AUTHORITY_KEY_IDENTIFIER];
 +            if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_ID))
 +            {
 +                cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_ID];
 +            }
 +            if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL))
 +            {
 +                cert_search_params[CERT_SERIAL_NUMBER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL];
 +            }
 +        }
 +        found_store_cert = find(cert_search_params);
 +
 +        if(found_store_cert != end())
 +        {
 +            // 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)))
 +            {
 +                LLTHROW(LLCertValidationInvalidSignatureException(current_cert_info));
 +            }
 +            // successfully validated.
 +            mTrustedCertCache[skeyid] = std::pair<LLDate, LLDate>(from_time, to_time);
 +            LL_DEBUGS("SECAPI") << "Verified and cached cert for '" << validation_params[CERT_HOSTNAME].asString() << "'"
 +                                << " as '" << subject_name << "'"
 +                                << " id '" << skeyid << "'"
 +                                << " using CA '" << cert_search_params[CERT_SUBJECT_NAME_STRING] << "'"
 +                                << " with id '" <<  cert_search_params[CERT_SUBJECT_KEY_IDENTFIER].asString() << "' found in cert store"
 +                                << LL_ENDL;
 +            return;
 +        }
 +        previous_cert = (*current_cert);
 +        current_cert++;
 +        depth++;
 +        if(current_cert != cert_chain->end())
 +        {
 +            (*current_cert)->getLLSD(current_cert_info);
 +        }
 +    }
 +    if (validation_policy & VALIDATION_POLICY_TRUSTED)
 +    {
 +        // we reached the end without finding a trusted cert.
 +        LLSD last_cert_info;
 +        ((*cert_chain)[cert_chain->size()-1])->getLLSD(last_cert_info);
 +        LLTHROW(LLCertValidationTrustException(last_cert_info));
 +    }
 +    else
 +    {
 +        LL_DEBUGS("SECAPI") << "! Caching untrusted cert for '" << subject_name << "'"
 +                            << " skeyid '" << skeyid << "' in cert store because ! VALIDATION_POLICY_TRUSTED"
 +                            << LL_ENDL;
 +        mTrustedCertCache[skeyid] = std::pair<LLDate, LLDate>(from_time, to_time);
 +    }
 +}
 +
 +
 +// LLSecAPIBasicHandler Class
 +// Interface handler class for the various security storage handlers.
 +
 +// We read the file on construction, and write it on destruction.  This
 +// means multiple processes cannot modify the datastore.
 +LLSecAPIBasicHandler::LLSecAPIBasicHandler(const std::string& protected_data_file,
 +                                           const std::string& legacy_password_path)
 +{
 +    mProtectedDataFilename = protected_data_file;
 +    mProtectedDataMap = LLSD::emptyMap();
 +    mLegacyPasswordPath = legacy_password_path;
 +
 +}
 +
 +LLSecAPIBasicHandler::LLSecAPIBasicHandler()
 +{
 +}
 +
 +
 +void LLSecAPIBasicHandler::init()
 +{
 +    mProtectedDataMap = LLSD::emptyMap();
 +    if (mProtectedDataFilename.length() == 0)
 +    {
 +        mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,
 +                                                            "bin_conf.dat");
 +        mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat");
 +
 +        mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,
 +                                                            "bin_conf.dat");
 +        std::string store_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS,
 +                                                        "CA.pem");
 +
 +
 +        LL_INFOS("SECAPI") << "Loading user certificate store from " << store_file << LL_ENDL;
 +        mStore = new LLBasicCertificateStore(store_file);
 +
 +        // grab the application ca-bundle.crt file that contains the well-known certs shipped
 +        // with the product
 +        std::string ca_file_path = gDirUtilp->getCAFile();
 +        LL_INFOS("SECAPI") << "Loading application certificate store from " << ca_file_path << LL_ENDL;
 +        LLPointer<LLBasicCertificateStore> app_ca_store = new LLBasicCertificateStore(ca_file_path);
 +
 +        // push the applicate CA files into the store, therefore adding any new CA certs that
 +        // updated
 +        for(LLCertificateVector::iterator i = app_ca_store->begin();
 +            i != app_ca_store->end();
 +            i++)
 +        {
 +            mStore->add(*i);
 +        }
 +
 +    }
 +    _readProtectedData(); // initialize mProtectedDataMap
 +                          // may throw LLProtectedDataException if saved datamap is not decryptable
 +}
 +LLSecAPIBasicHandler::~LLSecAPIBasicHandler()
 +{
 +    _writeProtectedData();
 +}
 +
 +void LLSecAPIBasicHandler::_readProtectedData(unsigned char *unique_id, U32 id_len)
 +{
 +    // attempt to load the file into our map
 +    LLPointer<LLSDParser> parser = new LLSDXMLParser();
 +    llifstream protected_data_stream(mProtectedDataFilename.c_str(),
 +                                    llifstream::binary);
 +
 +    if (!protected_data_stream.fail()) {
 +        U8 salt[STORE_SALT_SIZE];
 +        U8 buffer[BUFFER_READ_SIZE];
 +        U8 decrypted_buffer[BUFFER_READ_SIZE];
 +        int decrypted_length;
 +        LLXORCipher cipher(unique_id, id_len);
 +
 +        // read in the salt and key
 +        protected_data_stream.read((char *)salt, STORE_SALT_SIZE);
 +        if (protected_data_stream.gcount() < STORE_SALT_SIZE)
 +        {
 +            LLTHROW(LLProtectedDataException("Config file too short."));
 +        }
 +
 +        cipher.decrypt(salt, STORE_SALT_SIZE);
 +
 +        // totally lame.  As we're not using the OS level protected data, we need to
 +        // at least obfuscate the data.  We do this by using a salt stored at the head of the file
 +        // to encrypt the data, therefore obfuscating it from someone using simple existing tools.
 +        // We do include the MAC address as part of the obfuscation, which would require an
 +        // attacker to get the MAC address as well as the protected store, which improves things
 +        // somewhat.  It would be better to use the password, but as this store
 +        // will be used to store the SL password when the user decides to have SL remember it,
 +        // so we can't use that.  OS-dependent store implementations will use the OS password/storage
 +        // mechanisms and are considered to be more secure.
 +        // We've a strong intent to move to OS dependent protected data stores.
 +
 +
 +        // read in the rest of the file.
 +        EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
 +        // todo: ctx error handling
 +
 +        EVP_DecryptInit(ctx, EVP_rc4(), salt, NULL);
 +        // allocate memory:
 +        std::string decrypted_data;
 +
 +        while(protected_data_stream.good()) {
 +            // read data as a block:
 +            protected_data_stream.read((char *)buffer, BUFFER_READ_SIZE);
 +
 +            EVP_DecryptUpdate(ctx, decrypted_buffer, &decrypted_length,
 +                              buffer, protected_data_stream.gcount());
 +            decrypted_data.append((const char *)decrypted_buffer, protected_data_stream.gcount());
 +        }
 +
 +        // RC4 is a stream cipher, so we don't bother to EVP_DecryptFinal, as there is
 +        // no block padding.
 +        EVP_CIPHER_CTX_free(ctx);
 +        std::istringstream parse_stream(decrypted_data);
 +        if (parser->parse(parse_stream, mProtectedDataMap,
 +                          LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE)
 +        {
 +            LLTHROW(LLProtectedDataException("Config file cannot be decrypted."));
 +        }
 +    }
 +}
 +
 +void LLSecAPIBasicHandler::_readProtectedData()
 +{
 +    unsigned char unique_id[MAC_ADDRESS_BYTES];
 +    try
 +    {
 +        // try default id
 +        LLMachineID::getUniqueID(unique_id, sizeof(unique_id));
 +        _readProtectedData(unique_id, sizeof(unique_id));
 +    }
 +    catch(LLProtectedDataException&)
 +    {
 +        // try with legacy id, it will return false if it is identical to getUniqueID
 +        // or if it is not assigned/not in use
 +        if (LLMachineID::getLegacyID(unique_id, sizeof(unique_id)))
 +        {
 +            _readProtectedData(unique_id, sizeof(unique_id));
 +        }
 +        else
 +        {
 +            throw;
 +        }
 +    }
 +}
 +
 +void LLSecAPIBasicHandler::_writeProtectedData()
 +{
 +    std::ostringstream formatted_data_ostream;
 +    U8 salt[STORE_SALT_SIZE];
 +    U8 buffer[BUFFER_READ_SIZE];
 +    U8 encrypted_buffer[BUFFER_READ_SIZE];
 +
 +
 +    if(mProtectedDataMap.isUndefined())
 +    {
 +        LLFile::remove(mProtectedDataFilename);
 +        return;
 +    }
 +    // create a string with the formatted data.
 +    LLSDSerialize::toXML(mProtectedDataMap, formatted_data_ostream);
 +    std::istringstream formatted_data_istream(formatted_data_ostream.str());
 +    // generate the seed
 +    RAND_bytes(salt, STORE_SALT_SIZE);
 +
 +
 +    // write to a temp file so we don't clobber the initial file if there is
 +    // an error.
 +    std::string tmp_filename = mProtectedDataFilename + ".tmp";
 +
 +    llofstream protected_data_stream(tmp_filename.c_str(),
 +                                     std::ios_base::binary);
 +    EVP_CIPHER_CTX *ctx = NULL;
 +    try
 +    {
 +
 +        ctx = EVP_CIPHER_CTX_new();
 +        // todo: ctx error handling
 +
 +        EVP_EncryptInit(ctx, EVP_rc4(), salt, NULL);
 +        unsigned char unique_id[MAC_ADDRESS_BYTES];
 +        LLMachineID::getUniqueID(unique_id, sizeof(unique_id));
 +        LLXORCipher cipher(unique_id, sizeof(unique_id));
 +        cipher.encrypt(salt, STORE_SALT_SIZE);
 +        protected_data_stream.write((const char *)salt, STORE_SALT_SIZE);
 +
 +        while (formatted_data_istream.good())
 +        {
 +            formatted_data_istream.read((char *)buffer, BUFFER_READ_SIZE);
 +            if(formatted_data_istream.gcount() == 0)
 +            {
 +                break;
 +            }
 +            int encrypted_length;
 +            EVP_EncryptUpdate(ctx, encrypted_buffer, &encrypted_length,
 +                          buffer, formatted_data_istream.gcount());
 +            protected_data_stream.write((const char *)encrypted_buffer, encrypted_length);
 +        }
 +
 +        // no EVP_EncrypteFinal, as this is a stream cipher
 +        EVP_CIPHER_CTX_free(ctx);
 +
 +        protected_data_stream.close();
 +    }
 +    catch (...)
 +    {
 +        LOG_UNHANDLED_EXCEPTION("LLProtectedDataException(Error writing Protected Data Store)");
 +        // it's good practice to clean up any secure information on error
 +        // (even though this file isn't really secure.  Perhaps in the future
 +        // it may be, however.
 +        LLFile::remove(tmp_filename);
 +
 +        if (ctx)
 +        {
 +            EVP_CIPHER_CTX_free(ctx);
 +        }
 +
 +        // EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData()
 +        // Decided throwing an exception here was overkill until we figure out why this happens
 +        //LLTHROW(LLProtectedDataException("Error writing Protected Data Store"));
 +    }
 +
 +    try
 +    {
 +        // move the temporary file to the specified file location.
 +        if(((   (LLFile::isfile(mProtectedDataFilename) != 0)
 +             && (LLFile::remove(mProtectedDataFilename) != 0)))
 +           || (LLFile::rename(tmp_filename, mProtectedDataFilename)))
 +        {
 +            LL_WARNS() << "LLProtectedDataException(Could not overwrite protected data store)" << LL_ENDL;
 +            LLFile::remove(tmp_filename);
 +
 +            // EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData()
 +            // Decided throwing an exception here was overkill until we figure out why this happens
 +            //LLTHROW(LLProtectedDataException("Could not overwrite protected data store"));
 +        }
 +    }
 +    catch (...)
 +    {
 +        LOG_UNHANDLED_EXCEPTION(STRINGIZE("renaming '" << tmp_filename << "' to '"
 +                                          << mProtectedDataFilename << "'"));
 +        // it's good practice to clean up any secure information on error
 +        // (even though this file isn't really secure.  Perhaps in the future
 +        // it may be, however).
 +        LLFile::remove(tmp_filename);
 +
 +        //crash in LLSecAPIBasicHandler::_writeProtectedData()
 +        // Decided throwing an exception here was overkill until we figure out why this happens
 +        //LLTHROW(LLProtectedDataException("Error writing Protected Data Store"));
 +    }
 +}
 +
 +// instantiate a certificate from a pem string
 +LLPointer<LLCertificate> LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert)
 +{
 +    LLPointer<LLCertificate> result = new LLBasicCertificate(pem_cert);
 +    return result;
 +}
 +
 +
 +
 +// instiate a certificate from an openssl X509 structure
 +LLPointer<LLCertificate> LLSecAPIBasicHandler::getCertificate(X509* openssl_cert)
 +{
 +    LLPointer<LLCertificate> result = new LLBasicCertificate(openssl_cert);
 +    return result;
 +}
 +
 +// instantiate a chain from an X509_STORE_CTX
 +LLPointer<LLCertificateChain> LLSecAPIBasicHandler::getCertificateChain(X509_STORE_CTX* chain)
 +{
 +    LLPointer<LLCertificateChain> result = new LLBasicCertificateChain(chain);
 +    return result;
 +}
 +
 +// instantiate a cert store given it's id.  if a persisted version
 +// exists, it'll be loaded.  If not, one will be created (but not
 +// persisted)
 +LLPointer<LLCertificateStore> LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id)
 +{
 +    return mStore;
 +}
 +
 +// retrieve protected data
 +LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type,
 +                                            const std::string& data_id)
 +{
 +
 +    if (mProtectedDataMap.has(data_type) &&
 +        mProtectedDataMap[data_type].isMap() &&
 +        mProtectedDataMap[data_type].has(data_id))
 +    {
 +        return mProtectedDataMap[data_type][data_id];
 +    }
 +
 +    return LLSD();
 +}
 +
 +void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type,
 +                                               const std::string& data_id)
 +{
 +    if (mProtectedDataMap.has(data_type) &&
 +        mProtectedDataMap[data_type].isMap() &&
 +        mProtectedDataMap[data_type].has(data_id))
 +        {
 +            mProtectedDataMap[data_type].erase(data_id);
 +        }
 +}
 +
 +
 +//
 +// persist data in a protected store
 +//
 +void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type,
 +                                            const std::string& data_id,
 +                                            const LLSD& data)
 +{
 +    if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) {
 +        mProtectedDataMap[data_type] = LLSD::emptyMap();
 +    }
 +
 +    mProtectedDataMap[data_type][data_id] = data;
 +}
 +
 +// persist data in a protected store's map
 +void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type,
 +                                             const std::string& data_id,
 +                                             const std::string& map_elem,
 +                                             const LLSD& data)
 +{
 +    if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) {
 +        mProtectedDataMap[data_type] = LLSD::emptyMap();
 +    }
 +
 +    if (!mProtectedDataMap[data_type].has(data_id) || !mProtectedDataMap[data_type][data_id].isMap()) {
 +        mProtectedDataMap[data_type][data_id] = LLSD::emptyMap();
 +    }
 +
 +    mProtectedDataMap[data_type][data_id][map_elem] = data;
 +}
 +
 +// remove data from protected store's map
 +void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type,
 +                                                  const std::string& data_id,
 +                                                  const std::string& map_elem)
 +{
 +    if (mProtectedDataMap.has(data_type) &&
 +        mProtectedDataMap[data_type].isMap() &&
 +        mProtectedDataMap[data_type].has(data_id) &&
 +        mProtectedDataMap[data_type][data_id].isMap() &&
 +        mProtectedDataMap[data_type][data_id].has(map_elem))
 +    {
 +        mProtectedDataMap[data_type][data_id].erase(map_elem);
 +    }
 +}
 +
 +void LLSecAPIBasicHandler::syncProtectedMap()
 +{
 +    // TODO - consider unifing these functions
 +    _writeProtectedData();
 +}
 +//
 +// Create a credential object from an identifier and authenticator.  credentials are
 +// per grid.
 +LLPointer<LLCredential> LLSecAPIBasicHandler::createCredential(const std::string& grid,
 +                                                               const LLSD& identifier,
 +                                                               const LLSD& authenticator)
 +{
 +    LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);
 +    result->setCredentialData(identifier, authenticator);
 +    return result;
 +}
 +
 +// Load a credential from default credential store, given the grid
 +LLPointer<LLCredential> LLSecAPIBasicHandler::loadCredential(const std::string& grid)
 +{
 +    LLSD credential = getProtectedData(DEFAULT_CREDENTIAL_STORAGE, grid);
 +    LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);
 +    if(credential.isMap() &&
 +       credential.has("identifier"))
 +    {
 +
 +        LLSD identifier = credential["identifier"];
 +        LLSD authenticator;
 +        if (credential.has("authenticator"))
 +        {
 +            authenticator = credential["authenticator"];
 +        }
 +        result->setCredentialData(identifier, authenticator);
 +    }
 +    else
 +    {
 +        // credential was not in protected storage, so pull the credential
 +        // from the legacy store.
 +        std::string first_name = gSavedSettings.getString("FirstName");
 +        std::string last_name = gSavedSettings.getString("LastName");
 +
 +        if ((first_name != "") &&
 +            (last_name != ""))
 +        {
 +            LLSD identifier = LLSD::emptyMap();
 +            LLSD authenticator;
 +            identifier["type"] = "agent";
 +            identifier["first_name"] = first_name;
 +            identifier["last_name"] = last_name;
 +
 +            std::string legacy_password = _legacyLoadPassword();
 +            if (legacy_password.length() > 0)
 +            {
 +                authenticator = LLSD::emptyMap();
 +                authenticator["type"] = "hash";
 +                authenticator["algorithm"] = "md5";
 +                authenticator["secret"] = legacy_password;
 +            }
 +            result->setCredentialData(identifier, authenticator);
 +        }
 +    }
 +    return result;
 +}
 +
 +// Save the credential to the credential store.  Save the authenticator also if requested.
 +// That feature is used to implement the 'remember password' functionality.
 +void LLSecAPIBasicHandler::saveCredential(LLPointer<LLCredential> cred, bool save_authenticator)
 +{
 +    LLSD credential = LLSD::emptyMap();
 +    credential["identifier"] = cred->getIdentifier();
 +    if (save_authenticator)
 +    {
 +        credential["authenticator"] = cred->getAuthenticator();
 +    }
 +    LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL;
 +    setProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid(), credential);
 +    //*TODO: If we're saving Agni credentials, should we write the
 +    // credentials to the legacy password.dat/etc?
 +    _writeProtectedData();
 +}
 +
 +// Remove a credential from the credential store.
 +void LLSecAPIBasicHandler::deleteCredential(LLPointer<LLCredential> cred)
 +{
 +    LLSD undefVal;
 +    deleteProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid());
 +    cred->setCredentialData(undefVal, undefVal);
 +    _writeProtectedData();
 +}
 +
 +// has map of credentials declared as specific storage
 +bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const std::string& grid)
 +{
 +    if (storage == DEFAULT_CREDENTIAL_STORAGE)
 +    {
 +        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
 +    }
 +
 +    LLSD credential = getProtectedData(storage, grid);
 +
 +    return credential.isMap();
 +}
 +
 +// returns true if map is empty or does not exist
 +bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid)
 +{
 +    if (storage == DEFAULT_CREDENTIAL_STORAGE)
 +    {
 +        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
 +    }
 +
 +    LLSD credential = getProtectedData(storage, grid);
 +
 +    return !credential.isMap() || credential.size() == 0;
 +}
 +
 +// Load map of credentials from specified credential store, given the grid
 +void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map)
 +{
 +    if (storage == DEFAULT_CREDENTIAL_STORAGE)
 +    {
 +        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
 +    }
 +
 +    LLSD credential = getProtectedData(storage, grid);
 +    if (credential.isMap())
 +    {
 +        LLSD::map_const_iterator crd_it = credential.beginMap();
 +        for (; crd_it != credential.endMap(); crd_it++)
 +        {
 +            LLSD::String name = crd_it->first;
 +            const LLSD &link_map = crd_it->second;
 +            LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);
 +            if (link_map.has("identifier"))
 +            {
 +                LLSD identifier = link_map["identifier"];
 +                LLSD authenticator;
 +                if (link_map.has("authenticator"))
 +                {
 +                    authenticator = link_map["authenticator"];
 +                }
 +                result->setCredentialData(identifier, authenticator);
 +            }
 +            credential_map[name] = result;
 +        }
 +    }
 +}
 +
 +LLPointer<LLCredential> LLSecAPIBasicHandler::loadFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey)
 +{
 +    if (storage == DEFAULT_CREDENTIAL_STORAGE)
 +    {
 +        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
 +    }
 +
 +    LLPointer<LLSecAPIBasicCredential> result = new LLSecAPIBasicCredential(grid);
 +
 +    LLSD credential = getProtectedData(storage, grid);
 +    if (credential.isMap() && credential.has(userkey) && credential[userkey].has("identifier"))
 +    {
 +        LLSD identifier = credential[userkey]["identifier"];
 +        LLSD authenticator;
 +        if (credential[userkey].has("authenticator"))
 +        {
 +            authenticator = credential[userkey]["authenticator"];
 +        }
 +        result->setCredentialData(identifier, authenticator);
 +    }
 +
 +    return result;
 +}
 +
 +// add item to map of credentials from specific storage
 +void LLSecAPIBasicHandler::addToCredentialMap(const std::string& storage, LLPointer<LLCredential> cred, bool save_authenticator)
 +{
 +    if (storage == DEFAULT_CREDENTIAL_STORAGE)
 +    {
 +        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
 +    }
 +
 +    std::string user_id = cred->userID();
 +    LLSD credential = LLSD::emptyMap();
 +    credential["identifier"] = cred->getIdentifier();
 +    if (save_authenticator)
 +    {
 +        credential["authenticator"] = cred->getAuthenticator();
 +    }
 +    LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL;
 +    addToProtectedMap(storage, cred->getGrid(), user_id, credential);
 +
 +    _writeProtectedData();
 +}
 +
 +// remove item from map of credentials from specific storage
 +void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, LLPointer<LLCredential> cred)
 +{
 +    if (storage == DEFAULT_CREDENTIAL_STORAGE)
 +    {
 +        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
 +    }
 +
 +    LLSD undefVal;
 +    removeFromProtectedMap(storage, cred->getGrid(), cred->userID());
 +    cred->setCredentialData(undefVal, undefVal);
 +    _writeProtectedData();
 +}
 +
 +// remove item from map of credentials from specific storage
 +void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey)
 +{
 +    if (storage == DEFAULT_CREDENTIAL_STORAGE)
 +    {
 +        LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL;
 +    }
 +
 +    LLSD undefVal;
 +    LLPointer<LLCredential> cred = loadFromCredentialMap(storage, grid, userkey);
 +    removeFromProtectedMap(storage, grid, userkey);
 +    cred->setCredentialData(undefVal, undefVal);
 +    _writeProtectedData();
 +}
 +
 +// remove item from map of credentials from specific storage
 +void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const std::string& grid)
 +{
 +    deleteProtectedData(storage, grid);
 +    _writeProtectedData();
 +}
 +
 +// load the legacy hash for agni, and decrypt it given the
 +// mac address
 +std::string LLSecAPIBasicHandler::_legacyLoadPassword()
 +{
 +    const S32 HASHED_LENGTH = 32;
 +    std::vector<U8> buffer(HASHED_LENGTH);
 +    llifstream password_file(mLegacyPasswordPath.c_str(), llifstream::binary);
 +
 +    if(password_file.fail())
 +    {
 +        return std::string("");
 +    }
 +
 +    password_file.read((char*)&buffer[0], buffer.size());
 +    if(password_file.gcount() != buffer.size())
 +    {
 +        return std::string("");
 +    }
 +
 +    // Decipher with MAC address
 +    unsigned char unique_id[MAC_ADDRESS_BYTES];
 +    LLMachineID::getUniqueID(unique_id, sizeof(unique_id));
 +    LLXORCipher cipher(unique_id, sizeof(unique_id));
 +    cipher.decrypt(&buffer[0], buffer.size());
 +
 +    return std::string((const char*)&buffer[0], buffer.size());
 +}
 +
 +
 +// return an identifier for the user
 +std::string LLSecAPIBasicCredential::userID() const
 +{
 +    if (!mIdentifier.isMap())
 +    {
 +        return mGrid + "(null)";
 +    }
 +    else if ((std::string)mIdentifier["type"] == "agent")
 +    {
 +        std::string id = (std::string)mIdentifier["first_name"] + "_" + (std::string)mIdentifier["last_name"];
 +        LLStringUtil::toLower(id);
 +        return id;
 +    }
 +    else if ((std::string)mIdentifier["type"] == "account")
 +    {
 +        std::string id = (std::string)mIdentifier["account_name"];
 +        LLStringUtil::toLower(id);
 +        return id;
 +    }
 +
 +    return "unknown";
 +}
 +
 +// return a printable user identifier
 +std::string LLSecAPIBasicCredential::asString() const
 +{
 +    if (!mIdentifier.isMap())
 +    {
 +        return mGrid + ":(null)";
 +    }
 +    else if ((std::string)mIdentifier["type"] == "agent")
 +    {
 +        return mGrid + ":" + (std::string)mIdentifier["first_name"] + " " + (std::string)mIdentifier["last_name"];
 +    }
 +    else if ((std::string)mIdentifier["type"] == "account")
 +    {
 +        return mGrid + ":" + (std::string)mIdentifier["account_name"];
 +    }
 +
 +    return mGrid + ":(unknown type)";
 +}
 +
 +
 +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());
 +    }
 +
 +}
  | 
