/** * @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 #include #include #include #include #include #include #include #include #include #include #include #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 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(), static_cast(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 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(); } i2d_X509_bio(der_bio, mCert); int length = BIO_get_mem_data(der_bio, &der_bio_data); std::vector 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 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(_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 LLBasicCertificateVector::erase(iterator _iter) { if (_iter != end()) { BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); LLPointer 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 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 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 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 auto 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. auto 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. auto subdomain_pos = new_hostname.find_last_of('.'); auto 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 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((double)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 parent, LLPointer 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 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((double)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 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(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(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(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 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 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, (int)protected_data_stream.gcount()); decrypted_data.append((const char *)decrypted_buffer, (int)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, (int)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 LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert) { LLPointer result = new LLBasicCertificate(pem_cert); return result; } // instiate a certificate from an openssl X509 structure LLPointer LLSecAPIBasicHandler::getCertificate(X509* openssl_cert) { LLPointer result = new LLBasicCertificate(openssl_cert); return result; } // instantiate a chain from an X509_STORE_CTX LLPointer LLSecAPIBasicHandler::getCertificateChain(X509_STORE_CTX* chain) { LLPointer 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 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 LLSecAPIBasicHandler::createCredential(const std::string& grid, const LLSD& identifier, const LLSD& authenticator) { LLPointer result = new LLSecAPIBasicCredential(grid); result->setCredentialData(identifier, authenticator); return result; } // Load a credential from default credential store, given the grid LLPointer LLSecAPIBasicHandler::loadCredential(const std::string& grid) { LLSD credential = getProtectedData(DEFAULT_CREDENTIAL_STORAGE, grid); LLPointer 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 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 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 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 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 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 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 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 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 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], static_cast(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()); } }