diff options
author | Roxanne Skelly <roxie@lindenlab.com> | 2009-07-08 00:45:17 +0000 |
---|---|---|
committer | Roxanne Skelly <roxie@lindenlab.com> | 2009-07-08 00:45:17 +0000 |
commit | 9e89819d55a3b6ee7fc56f3efb36f273e4e05c83 (patch) | |
tree | 1585010af9cafd82202c22ef9cb0db4967c74394 /indra/newview | |
parent | fe71dd340ab396b93bde45df438041af5d85fd47 (diff) |
DEV-34822 - merge with 1.23
certificate notification code
-r 118191
ignore-dead-branch
Diffstat (limited to 'indra/newview')
-rw-r--r-- | indra/newview/CMakeLists.txt | 25 | ||||
-rw-r--r-- | indra/newview/app_settings/settings.xml | 11 | ||||
-rw-r--r-- | indra/newview/llfloaterland.cpp | 1 | ||||
-rw-r--r-- | indra/newview/lllogininstance.cpp | 9 | ||||
-rw-r--r-- | indra/newview/llsecapi.cpp | 5 | ||||
-rw-r--r-- | indra/newview/llsecapi.h | 311 | ||||
-rw-r--r-- | indra/newview/llsechandler_basic.cpp | 913 | ||||
-rw-r--r-- | indra/newview/llsechandler_basic.h | 149 | ||||
-rw-r--r-- | indra/newview/llstartup.cpp | 163 | ||||
-rw-r--r-- | indra/newview/llviewernetwork.cpp | 16 | ||||
-rw-r--r-- | indra/newview/llviewernetwork.h | 4 | ||||
-rw-r--r-- | indra/newview/llxmlrpclistener.cpp | 18 | ||||
-rw-r--r-- | indra/newview/llxmlrpctransaction.cpp | 103 | ||||
-rw-r--r-- | indra/newview/llxmlrpctransaction.h | 3 | ||||
-rw-r--r-- | indra/newview/skins/default/xui/en/notifications.xml | 42 | ||||
-rw-r--r-- | indra/newview/tests/llsecapi_test.cpp | 188 | ||||
-rw-r--r-- | indra/newview/tests/llsechandler_basic_test.cpp | 625 | ||||
-rw-r--r-- | indra/newview/tests/llviewernetwork_test.cpp | 224 |
18 files changed, 2451 insertions, 359 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 4be6fb940e..35f0a5036d 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1803,6 +1803,31 @@ if (LL_TESTS) "${CMAKE_SOURCE_DIR}/llmessage/tests/test_llsdmessage_peer.py" ) + set(test_libs + ${LLMESSAGE_LIBRARIES} + ${WINDOWS_LIBRARIES} + ${LLVFS_LIBRARIES} + ${LLMATH_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${GOOGLEMOCK_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIBRARIES} + ) + LL_ADD_INTEGRATION_TEST(llsechandler_basic + llsechandler_basic.cpp + "${test_libs}" + ) + + LL_ADD_INTEGRATION_TEST(llsecapi + llsecapi.cpp + "${test_libs}" + ) + + LL_ADD_INTEGRATION_TEST(llviewernetwork + llviewernetwork.cpp + "${test_libs}" + ) + #ADD_VIEWER_BUILD_TEST(llmemoryview viewer) #ADD_VIEWER_BUILD_TEST(llagentaccess viewer) #ADD_VIEWER_BUILD_TEST(llworldmap viewer) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 75ee389750..1641ab0ce1 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1275,6 +1275,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>CertStore</key> + <map> + <key>Comment</key> + <string>Specifies the Certificate Store for certificate trust verification</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>default</string> + </map> <key>ChatBarStealsFocus</key> <map> <key>Comment</key> diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 598a13de15..9b6e24f251 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -42,7 +42,6 @@ #include "llnotificationsutil.h" #include "llparcel.h" #include "message.h" -#include "lluserauth.h" #include "llagent.h" #include "llbutton.h" diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index bb45cc93ea..e4b8becdd7 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -246,6 +246,15 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) LLSD data(LLSD::emptyMap()); data["message"] = message_response; data["reply_pump"] = TOS_REPLY_PUMP; + if(response.has("error_code")) + { + data["error_code"] = response["error_code"]; + } + if(response.has("certificate")) + { + data["certificate"] = response["certificate"]; + } + LLFloaterReg::showInstance("message_critical", data); LLEventPumps::instance().obtain(TOS_REPLY_PUMP) .listen(TOS_LISTENER_NAME, diff --git a/indra/newview/llsecapi.cpp b/indra/newview/llsecapi.cpp index cdf4a3fe01..70c247c2de 100644 --- a/indra/newview/llsecapi.cpp +++ b/indra/newview/llsecapi.cpp @@ -34,6 +34,7 @@ #include "llviewerprecompiledheaders.h" #include "llsecapi.h" #include "llsechandler_basic.h" +#include <openssl/evp.h> #include <map> @@ -42,6 +43,9 @@ LLPointer<LLSecAPIHandler> gSecAPIHandler; void initializeSecHandler() { + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); gHandlerMap[BASIC_SECHANDLER] = new LLSecAPIBasicHandler(); // Currently, we only have the Basic handler, so we can point the main sechandler @@ -76,6 +80,7 @@ std::ostream& operator <<(std::ostream& s, const LLCredential& cred) } + LLSD LLCredential::getLoginParams() { LLSD result = LLSD::emptyMap(); diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h index d456ca95b1..6fd12c044a 100644 --- a/indra/newview/llsecapi.h +++ b/indra/newview/llsecapi.h @@ -36,11 +36,16 @@ #include <openssl/x509.h> #include <ostream> +#ifdef LL_WINDOWS +#pragma warning(disable:4250) +#endif // LL_WINDOWS + // All error handling is via exceptions. #define CERT_SUBJECT_NAME "subject_name" #define CERT_ISSUER_NAME "issuer_name" +#define CERT_NAME_CN "commonName" #define CERT_SUBJECT_NAME_STRING "subject_name_string" #define CERT_ISSUER_NAME_STRING "issuer_name_string" @@ -51,23 +56,62 @@ #define CERT_VALID_TO "valid_to" #define CERT_SHA1_DIGEST "sha1_digest" #define CERT_MD5_DIGEST "md5_digest" +#define CERT_HOSTNAME "hostname" +#define CERT_BASIC_CONSTRAINTS "basicConstraints" +#define CERT_BASIC_CONSTRAINTS_CA "CA" +#define CERT_BASIC_CONSTRAINTS_PATHLEN "pathLen" + +#define CERT_KEY_USAGE "keyUsage" +#define CERT_KU_DIGITAL_SIGNATURE "digitalSignature" +#define CERT_KU_NON_REPUDIATION "nonRepudiation" +#define CERT_KU_KEY_ENCIPHERMENT "keyEncipherment" +#define CERT_KU_DATA_ENCIPHERMENT "dataEncipherment" +#define CERT_KU_KEY_AGREEMENT "keyAgreement" +#define CERT_KU_CERT_SIGN "certSigning" +#define CERT_KU_CRL_SIGN "crlSigning" +#define CERT_KU_ENCIPHER_ONLY "encipherOnly" +#define CERT_KU_DECIPHER_ONLY "decipherOnly" #define BASIC_SECHANDLER "BASIC_SECHANDLER" +#define CERT_VALIDATION_DATE "validation_date" + +#define CERT_EXTENDED_KEY_USAGE "extendedKeyUsage" +#define CERT_EKU_SERVER_AUTH SN_server_auth + +// validate the current time lies within +// the validation period of the cert +#define VALIDATION_POLICY_TIME 1 + +// validate that the CA, or some cert in the chain +// lies within the certificate store +#define VALIDATION_POLICY_TRUSTED 2 + +// validate that the subject name of +// the cert contains the passed in hostname +// or validates against the hostname +#define VALIDATION_POLICY_HOSTNAME 4 + + +// validate that the cert contains the SSL EKU +#define VALIDATION_POLICY_SSL_KU 8 + +// validate that the cert contains the SSL EKU +#define VALIDATION_POLICY_CA_KU 16 + +#define VALIDATION_POLICY_CA_BASIC_CONSTRAINTS 32 + +// validate that the cert is correct for SSL +#define VALIDATION_POLICY_SSL (VALIDATION_POLICY_TIME | \ + VALIDATION_POLICY_HOSTNAME | \ + VALIDATION_POLICY_TRUSTED | \ + VALIDATION_POLICY_SSL_KU | \ + VALIDATION_POLICY_CA_BASIC_CONSTRAINTS | \ + VALIDATION_POLICY_CA_KU) + + -// All error handling is via exceptions. -class LLCertException -{ -public: - LLCertException(const char* msg) - { - llerrs << "Certificate Error: " << msg << llendl; - mMsg = std::string(msg); - } -protected: - std::string mMsg; -}; class LLProtectedDataException { @@ -96,53 +140,88 @@ public: // return a PEM encoded certificate. The encoding // includes the -----BEGIN CERTIFICATE----- and end certificate elements - virtual std::string getPem()=0; + virtual std::string getPem() const=0; // return a DER encoded certificate - virtual std::vector<U8> getBinary()=0; + virtual std::vector<U8> getBinary() const=0; // return an LLSD object containing information about the certificate // such as its name, signature, expiry time, serial number - virtual LLSD getLLSD()=0; + virtual LLSD getLLSD() const=0; // return an openSSL X509 struct for the certificate - virtual X509* getOpenSSLX509()=0; + virtual X509* getOpenSSLX509() const=0; }; +// class LLCertificateVector +// base class for a list of certificates. -// class LLCertificateChain -// Class representing a chain of certificates in order, with the -// 0th element being the CA -class LLCertificateChain : public LLRefCount + +class LLCertificateVector : public LLRefCount { - LOG_CLASS(LLCertificateChain); - static const int VT_SSL = 0; - static const int VT_AGENT_DOMAIN = 1; - static const int VT_GRID_DOMAIN = 1; public: - LLCertificateChain() {} - virtual ~LLCertificateChain() {} + LLCertificateVector() {}; + virtual ~LLCertificateVector() {}; + + // base iterator implementation class, providing + // the functionality needed for the iterator class. + class iterator_impl : public LLRefCount + { + public: + iterator_impl() {}; + virtual ~iterator_impl() {}; + virtual void seek(bool incr)=0; + virtual LLPointer<iterator_impl> clone() const=0; + virtual bool equals(const LLPointer<iterator_impl>& _iter) const=0; + virtual LLPointer<LLCertificate> get()=0; + }; + + // iterator class + class iterator + { + public: + iterator(LLPointer<iterator_impl> impl) : mImpl(impl) {} + iterator() : mImpl(NULL) {} + iterator(const iterator& _iter) {mImpl = _iter.mImpl->clone(); } + ~iterator() {} + iterator& operator++() { if(mImpl.notNull()) mImpl->seek(true); return *this;} + iterator& operator--() { if(mImpl.notNull()) mImpl->seek(false); return *this;} + + iterator operator++(int) { iterator result = *this; if(mImpl.notNull()) mImpl->seek(true); return result;} + iterator operator--(int) { iterator result = *this; if(mImpl.notNull()) mImpl->seek(false); return result;} + LLPointer<LLCertificate> operator*() { return mImpl->get(); } + + LLPointer<iterator_impl> mImpl; + protected: + friend bool operator==(const LLCertificateVector::iterator& _lhs, const LLCertificateVector::iterator& _rhs); + bool equals(const iterator& _iter) const { return mImpl->equals(_iter.mImpl); } + }; - virtual X509_STORE getOpenSSLX509Store()=0; // return an openssl X509_STORE - // for this store + // numeric indexer + virtual LLPointer<LLCertificate> operator[](int)=0; - virtual void appendCert(const LLCertificate& cert)=0; // append a cert to the end - //of the chain + // Iteration + virtual iterator begin()=0; - virtual LLPointer<LLCertificate>& operator [](int index)=0; // retrieve a certificate - // from the chain by index - // -1 == end of chain + virtual iterator end()=0; - virtual int len() const =0; // return number of certificates in the chain + // find a cert given params + virtual iterator find(const LLSD& params) =0; - // validate a certificate chain given the params. - // validation type indicates whether it's simply an SSL cert, or - // something more specific - virtual bool validate(int validation_type, - const LLSD& validation_params) const =0; + // return the number of certs in the store + virtual int size() const = 0; + + // append the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void add(LLPointer<LLCertificate> cert)=0; + + // insert the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void insert(iterator location, LLPointer<LLCertificate> cert)=0; + + // remove a certificate from the store + virtual LLPointer<LLCertificate> erase(iterator cert)=0; }; @@ -151,43 +230,55 @@ public: // certificates. The store can be persisted, and can be used to validate // a cert chain // -class LLCertificateStore : public LLRefCount +class LLCertificateStore : virtual public LLCertificateVector { + public: + LLCertificateStore() {} virtual ~LLCertificateStore() {} - virtual X509_STORE* getOpenSSLX509Store()=0; // return an openssl X509_STORE - // for this store - - // add a copy of a cert to the store - virtual void append(const LLCertificate& cert)=0; - - // add a copy of a cert to the store - virtual void insert(const int index, const LLCertificate& cert)=0; - - // remove a certificate from the store - virtual void remove(int index)=0; - - // return a certificate at the index - virtual LLPointer<LLCertificate> operator[](int index)=0; - - // return the number of certs in the store - virtual int len() const =0; - - // load the store from a persisted location - virtual void load(const std::string& store_id)=0; - // persist the store virtual void save()=0; // return the store id - virtual std::string storeId()=0; + virtual std::string storeId() const=0; +}; + +// class LLCertificateChain +// Class representing a chain of certificates in order, with the +// first element being the child cert. +class LLCertificateChain : virtual public LLCertificateVector +{ + +public: + LLCertificateChain() {} - // validate a cert chain - virtual bool validate(const LLCertificateChain& cert_chain) const=0; + virtual ~LLCertificateChain() {} + + // validate a certificate chain given the params. + // Will throw exceptions on error + + virtual void validate(int validation_policy, + LLPointer<LLCertificateStore> ca_store, + const LLSD& validation_params) =0; }; + + + +inline +bool operator==(const LLCertificateVector::iterator& _lhs, const LLCertificateVector::iterator& _rhs) +{ + return _lhs.equals(_rhs); +} +inline +bool operator!=(const LLCertificateVector::iterator& _lhs, const LLCertificateVector::iterator& _rhs) +{ + return !(_lhs == _rhs); +} + + // // LLCredential - interface for credentials providing the following functionality: // * persistance of credential information based on grid (for saving username/password) @@ -232,6 +323,98 @@ protected: std::ostream& operator <<(std::ostream& s, const LLCredential& cred); +// All error handling is via exceptions. + +class LLCertException +{ +public: + LLCertException(LLPointer<LLCertificate> cert, const char* msg) + { + + mCert = cert; + + LL_WARNS("SECAPI") << "Certificate Error: " << (std::string)msg << LL_ENDL; + mMsg = (std::string)msg; + } + LLPointer<LLCertificate> getCert() { return mCert; } + std::string getMessage() { return mMsg; } +protected: + LLPointer<LLCertificate> mCert; + std::string mMsg; +}; + +class LLInvalidCertificate : public LLCertException +{ +public: + LLInvalidCertificate(LLPointer<LLCertificate> cert) : LLCertException(cert, "CertInvalid") + { + } +protected: +}; + +class LLCertValidationTrustException : public LLCertException +{ +public: + LLCertValidationTrustException(LLPointer<LLCertificate> cert) : LLCertException(cert, "CertUntrusted") + { + } +protected: +}; + +class LLCertValidationHostnameException : public LLCertException +{ +public: + LLCertValidationHostnameException(std::string hostname, + LLPointer<LLCertificate> cert) : LLCertException(cert, "CertInvalidHostname") + { + mHostname = hostname; + } + + std::string getHostname() { return mHostname; } +protected: + std::string mHostname; +}; + +class LLCertValidationExpirationException : public LLCertException +{ +public: + LLCertValidationExpirationException(LLPointer<LLCertificate> cert, + LLDate current_time) : LLCertException(cert, "CertExpired") + { + mTime = current_time; + } + LLDate GetTime() { return mTime; } +protected: + LLDate mTime; +}; + +class LLCertKeyUsageValidationException : public LLCertException +{ +public: + LLCertKeyUsageValidationException(LLPointer<LLCertificate> cert) : LLCertException(cert, "CertKeyUsage") + { + } +protected: +}; + +class LLCertBasicConstraintsValidationException : public LLCertException +{ +public: + LLCertBasicConstraintsValidationException(LLPointer<LLCertificate> cert) : LLCertException(cert, "CertBasicConstraints") + { + } +protected: +}; + +class LLCertValidationInvalidSignatureException : public LLCertException +{ +public: + LLCertValidationInvalidSignatureException(LLPointer<LLCertificate> cert) : LLCertException(cert, "CertInvalidSignature") + { + } +protected: +}; + // LLSecAPIHandler Class // Interface handler class for the various security storage handlers. class LLSecAPIHandler : public LLRefCount diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp index 4180f578b9..097cc395d7 100644 --- a/indra/newview/llsechandler_basic.cpp +++ b/indra/newview/llsechandler_basic.cpp @@ -20,7 +20,7 @@ * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlife.com/developers/opensource/flossexception * - * By copying, modifying or distributing this software, you acknowledge +LLS * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * @@ -42,17 +42,26 @@ #include "llviewercontrol.h" #include <vector> #include <ios> +#include <openssl/ossl_typ.h> #include <openssl/x509.h> +#include <openssl/x509v3.h> #include <openssl/pem.h> #include <openssl/asn1.h> #include <openssl/rand.h> +#include <openssl/err.h> #include <iostream> #include <iomanip> #include <time.h> + + // 128 bits of salt data... #define STORE_SALT_SIZE 16 #define BUFFER_READ_SIZE 256 +std::string cert_string_from_asn1_string(ASN1_STRING* value); +LLSD _basic_constraints_ext(X509* cert); +LLSD _key_usage_ext(X509* cert); +LLSD _ext_key_usage_ext(X509* cert); LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert) @@ -61,14 +70,19 @@ LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert) // BIO_new_mem_buf returns a read only bio, but takes a void* which isn't const // so we need to cast it. BIO * pem_bio = BIO_new_mem_buf((void*)pem_cert.c_str(), pem_cert.length()); - + if(pem_bio == NULL) + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + throw LLInvalidCertificate(this); + } mCert = NULL; PEM_read_bio_X509(pem_bio, &mCert, 0, NULL); BIO_free(pem_bio); if (!mCert) { - throw LLCertException("Error parsing certificate"); + throw LLInvalidCertificate(this); } + _initLLSD(); } @@ -76,20 +90,23 @@ LLBasicCertificate::LLBasicCertificate(X509* pCert) { if (!pCert || !pCert->cert_info) { - throw LLCertException("Invalid certificate"); + throw LLInvalidCertificate(this); } mCert = X509_dup(pCert); + _initLLSD(); } LLBasicCertificate::~LLBasicCertificate() { - - X509_free(mCert); + if(mCert) + { + X509_free(mCert); + } } // // retrieve the pem using the openssl functionality -std::string LLBasicCertificate::getPem() +std::string LLBasicCertificate::getPem() const { char * pem_bio_chars = NULL; // a BIO is the equivalent of a 'std::stream', and @@ -98,7 +115,8 @@ std::string LLBasicCertificate::getPem() BIO *pem_bio = BIO_new(BIO_s_mem()); if (!pem_bio) { - throw LLCertException("couldn't allocate memory buffer"); + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + return std::string(); } PEM_write_bio_X509(pem_bio, mCert); int length = BIO_get_mem_data(pem_bio, &pem_bio_chars); @@ -109,14 +127,15 @@ std::string LLBasicCertificate::getPem() // get the DER encoding for the cert // DER is a binary encoding format for certs... -std::vector<U8> LLBasicCertificate::getBinary() +std::vector<U8> LLBasicCertificate::getBinary() const { U8 * der_bio_data = NULL; // get a memory bio BIO *der_bio = BIO_new(BIO_s_mem()); if (!der_bio) { - throw LLCertException("couldn't allocate memory buffer"); + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + return std::vector<U8>(); } i2d_X509_bio(der_bio, mCert); int length = BIO_get_mem_data(der_bio, &der_bio_data); @@ -128,32 +147,131 @@ std::vector<U8> LLBasicCertificate::getBinary() } -LLSD LLBasicCertificate::getLLSD() +LLSD LLBasicCertificate::getLLSD() const +{ + return mLLSDInfo; +} + +// Initialize the LLSD info for the certificate +LLSD& LLBasicCertificate::_initLLSD() { - LLSD result; // call the various helpers to build the LLSD - result[CERT_SUBJECT_NAME] = cert_name_from_X509_NAME(X509_get_subject_name(mCert)); - result[CERT_ISSUER_NAME] = cert_name_from_X509_NAME(X509_get_issuer_name(mCert)); - result[CERT_SUBJECT_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_subject_name(mCert)); - result[CERT_ISSUER_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_issuer_name(mCert)); + mLLSDInfo[CERT_SUBJECT_NAME] = cert_name_from_X509_NAME(X509_get_subject_name(mCert)); + mLLSDInfo[CERT_ISSUER_NAME] = cert_name_from_X509_NAME(X509_get_issuer_name(mCert)); + mLLSDInfo[CERT_SUBJECT_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_subject_name(mCert)); + mLLSDInfo[CERT_ISSUER_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_issuer_name(mCert)); ASN1_INTEGER *sn = X509_get_serialNumber(mCert); if (sn != NULL) { - result[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn); + mLLSDInfo[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn); } - result[CERT_VALID_TO] = cert_date_from_asn1_time(X509_get_notAfter(mCert)); - result[CERT_VALID_FROM] = cert_date_from_asn1_time(X509_get_notBefore(mCert)); - result[CERT_SHA1_DIGEST] = cert_get_digest("sha1", mCert); - result[CERT_MD5_DIGEST] = cert_get_digest("md5", mCert); + mLLSDInfo[CERT_VALID_TO] = cert_date_from_asn1_time(X509_get_notAfter(mCert)); + mLLSDInfo[CERT_VALID_FROM] = cert_date_from_asn1_time(X509_get_notBefore(mCert)); + mLLSDInfo[CERT_SHA1_DIGEST] = cert_get_digest("sha1", mCert); + mLLSDInfo[CERT_MD5_DIGEST] = cert_get_digest("md5", mCert); + // add the known extensions + mLLSDInfo[CERT_BASIC_CONSTRAINTS] = _basic_constraints_ext(mCert); + mLLSDInfo[CERT_KEY_USAGE] = _key_usage_ext(mCert); + mLLSDInfo[CERT_EXTENDED_KEY_USAGE] = _ext_key_usage_ext(mCert); + return mLLSDInfo; +} + +// Retrieve the basic constraints info +LLSD _basic_constraints_ext(X509* cert) +{ + LLSD result; + BASIC_CONSTRAINTS *bs = (BASIC_CONSTRAINTS *)X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL); + if(bs) + { + result = LLSD::emptyMap(); + // Determines whether the cert can be used as a CA + result[CERT_BASIC_CONSTRAINTS_CA] = (bool)bs->ca; + + if(bs->pathlen) + { + // the pathlen determines how deep a certificate chain can be from + // this CA + if((bs->pathlen->type == V_ASN1_NEG_INTEGER) + || !bs->ca) + { + result[CERT_BASIC_CONSTRAINTS_PATHLEN] = 0; + } + else + { + result[CERT_BASIC_CONSTRAINTS_PATHLEN] = (int)ASN1_INTEGER_get(bs->pathlen); + } + } + } + return result; +} +// retrieve the key usage, which specifies how the cert can be used. +// +LLSD _key_usage_ext(X509* cert) +{ + LLSD result; + ASN1_STRING *usage_str = (ASN1_STRING *)X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL); + if(usage_str) + { + result = LLSD::emptyArray(); + long usage = 0; + if(usage_str->length > 0) + { + usage = usage_str->data[0]; + if(usage_str->length > 1) + { + usage |= usage_str->data[1] << 8; + } + } + ASN1_STRING_free(usage_str); + if(usage) + { + if(usage & KU_DIGITAL_SIGNATURE) result.append(LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE)); + if(usage & KU_NON_REPUDIATION) result.append(LLSD((std::string)CERT_KU_NON_REPUDIATION)); + if(usage & KU_KEY_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT)); + if(usage & KU_DATA_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_DATA_ENCIPHERMENT)); + if(usage & KU_KEY_AGREEMENT) result.append(LLSD((std::string)CERT_KU_KEY_AGREEMENT)); + if(usage & KU_KEY_CERT_SIGN) result.append(LLSD((std::string)CERT_KU_CERT_SIGN)); + if(usage & KU_CRL_SIGN) result.append(LLSD((std::string)CERT_KU_CRL_SIGN)); + if(usage & KU_ENCIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_ENCIPHER_ONLY)); + if(usage & KU_DECIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_DECIPHER_ONLY)); + } + } + return result; +} - return result; +// retrieve the extended key usage for the cert +LLSD _ext_key_usage_ext(X509* cert) +{ + LLSD result; + EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)X509_get_ext_d2i(cert, NID_ext_key_usage, NULL, NULL); + if(eku) + { + result = LLSD::emptyArray(); + while(sk_ASN1_OBJECT_num(eku)) + { + ASN1_OBJECT *usage = sk_ASN1_OBJECT_pop(eku); + if(usage) + { + int nid = OBJ_obj2nid(usage); + if (nid) + { + std::string sn = OBJ_nid2sn(nid); + result.append(sn); + } + ASN1_OBJECT_free(usage); + } + } + } + return result; } -X509* LLBasicCertificate::getOpenSSLX509() +// retrieve an openssl x509 object, +// which must be freed by X509_free +X509* LLBasicCertificate::getOpenSSLX509() const { return X509_dup(mCert); } @@ -203,13 +321,46 @@ LLSD cert_name_from_X509_NAME(X509_NAME* name) std::string cert_string_from_asn1_integer(ASN1_INTEGER* value) { + std::string result; BIGNUM *bn = ASN1_INTEGER_to_BN(value, NULL); - char * ascii_bn = BN_bn2hex(bn); + if(bn) + { + char * ascii_bn = BN_bn2hex(bn); - BN_free(bn); + if(ascii_bn) + { + result = ascii_bn; + OPENSSL_free(ascii_bn); + } + BN_free(bn); + } + return result; +} + +// Generate a string from an ASN1 integer. ASN1 Integers are +// bignums, so they can be 'infinitely' long, therefore we +// cannot simply use a conversion to U64 or something. +// We retrieve as a readable string for UI - std::string result(ascii_bn); - OPENSSL_free(ascii_bn); +std::string cert_string_from_asn1_string(ASN1_STRING* value) +{ + char * string_bio_chars = NULL; + std::string result; + // get a memory bio + BIO *string_bio = BIO_new(BIO_s_mem()); + if(!string_bio) + { + // stream the name into the bio. The name will be in the 'short name' format + ASN1_STRING_print_ex(string_bio, value, ASN1_STRFLGS_RFC2253); + int length = BIO_get_mem_data(string_bio, &string_bio_chars); + result = std::string(string_bio_chars, length); + BIO_free(string_bio); + } + else + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + } + return result; } @@ -222,8 +373,9 @@ LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time) int i = asn1_time->length; if (i < 10) - throw LLCertException("invalid certificate time value"); - + { + return LLDate(); + } // convert the date from the ASN1 time (which is a string in ZULU time), to // a timeval. timestruct.tm_year = (asn1_time->data[0]-'0') * 10 + (asn1_time->data[1]-'0'); @@ -237,19 +389,23 @@ LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time) timestruct.tm_hour = (asn1_time->data[6]-'0') * 10 + (asn1_time->data[7]-'0'); timestruct.tm_min = (asn1_time->data[8]-'0') * 10 + (asn1_time->data[9]-'0'); timestruct.tm_sec = (asn1_time->data[10]-'0') * 10 + (asn1_time->data[11]-'0'); - - return LLDate((F64)mktime(×truct)); + +#if LL_WINDOWS + return LLDate((F64)_mkgmtime(×truct)); +#else // LL_WINDOWS + return LLDate((F64)timegm(×truct)); +#endif // LL_WINDOWS } + // Generate a string containing a digest. The digest time is 'ssh1' or // 'md5', and the resulting string is of the form "aa:12:5c:' and so on std::string cert_get_digest(const std::string& digest_type, X509 *cert) { - unsigned char digest_data[1024]; - unsigned int len = 1024; + unsigned char digest_data[BUFFER_READ_SIZE]; + unsigned int len = sizeof(digest_data); std::stringstream result; const EVP_MD* digest = NULL; - OpenSSL_add_all_digests(); // we could use EVP_get_digestbyname, but that requires initializer code which // would require us to complicate things by plumbing it into the system. if (digest_type == "md5") @@ -262,7 +418,7 @@ std::string cert_get_digest(const std::string& digest_type, X509 *cert) } else { - throw LLCertException("Invalid digest"); + return std::string(); } X509_digest(cert, digest, digest_data, &len); @@ -279,75 +435,588 @@ std::string cert_get_digest(const std::string& digest_type, X509 *cert) } +// class LLBasicCertificateVector +// This class represents a list of certificates, implemented by a vector of certificate pointers. +// it contains implementations of the virtual functions for iterators, search, add, remove, etc. +// + +// Find a certificate in the list. +// It will find a cert that has minimally the params listed, with the values being the same +LLBasicCertificateVector::iterator LLBasicCertificateVector::find(const LLSD& params) +{ + BOOL found = FALSE; + // loop through the entire vector comparing the values in the certs + // against those passed in via the params. + // params should be a map. Only the items specified in the map will be + // checked, but they must match exactly, even if they're maps or arrays. + + for(iterator cert = begin(); + cert != end(); + cert++) + { + + found= TRUE; + LLSD cert_info = (*cert)->getLLSD(); + for (LLSD::map_const_iterator param = params.beginMap(); + param != params.endMap(); + param++) + { + + if (!cert_info.has((std::string)param->first) || + (!valueCompareLLSD(cert_info[(std::string)param->first], param->second))) + { + found = FALSE; + break; + } + } + if (found) + { + return (cert); + } + } + return end(); +} + +// Insert a certificate into the store. If the certificate already +// exists in the store, nothing is done. +void LLBasicCertificateVector::insert(iterator _iter, + LLPointer<LLCertificate> cert) +{ + LLSD cert_info = cert->getLLSD(); + if (cert_info.isMap() && cert_info.has(CERT_SHA1_DIGEST)) + { + LLSD existing_cert_info = LLSD::emptyMap(); + existing_cert_info[CERT_MD5_DIGEST] = cert_info[CERT_MD5_DIGEST]; + if(find(existing_cert_info) == end()) + { + BasicIteratorImpl *basic_iter = dynamic_cast<BasicIteratorImpl*>(_iter.mImpl.get()); + mCerts.insert(basic_iter->mIter, cert); + } + } +} + +// remove a certificate from the store +LLPointer<LLCertificate> LLBasicCertificateVector::erase(iterator _iter) +{ + + if (_iter != end()) + { + BasicIteratorImpl *basic_iter = dynamic_cast<BasicIteratorImpl*>(_iter.mImpl.get()); + LLPointer<LLCertificate> result = (*_iter); + mCerts.erase(basic_iter->mIter); + return result; + } + return NULL; +} + + // // LLBasicCertificateStore -// +// This class represents a store of CA certificates. The basic implementation +// uses a pem file such as the legacy CA.pem stored in the existing +// SL implementation. LLBasicCertificateStore::LLBasicCertificateStore(const std::string& filename) { + mFilename = filename; + load_from_file(filename); } -LLBasicCertificateStore::LLBasicCertificateStore(const X509_STORE* store) + +void LLBasicCertificateStore::load_from_file(const std::string& filename) { + // scan the PEM file extracting each certificate + BIO* file_bio = BIO_new(BIO_s_file()); + if(file_bio) + { + if (BIO_read_filename(file_bio, filename.c_str()) > 0) + { + X509 *cert_x509 = NULL; + while((PEM_read_bio_X509(file_bio, &cert_x509, 0, NULL)) && + (cert_x509 != NULL)) + { + try + { + add(new LLBasicCertificate(cert_x509)); + } + catch (...) + { + LL_WARNS("SECAPI") << "Failure creating certificate from the certificate store file." << LL_ENDL; + } + X509_free(cert_x509); + cert_x509 = NULL; + } + BIO_free(file_bio); + } + } + else + { + LL_WARNS("SECAPI") << "Could not allocate a file BIO" << LL_ENDL; + } } + LLBasicCertificateStore::~LLBasicCertificateStore() { } - -X509_STORE* LLBasicCertificateStore::getOpenSSLX509Store() + +// persist the store +void LLBasicCertificateStore::save() { - return NULL; + llofstream file_store(mFilename, llofstream::binary); + if(!file_store.fail()) + { + for(iterator cert = begin(); + cert != end(); + cert++) + { + std::string pem = (*cert)->getPem(); + if(!pem.empty()) + { + file_store << (*cert)->getPem() << std::endl; + } + } + file_store.close(); + } + else + { + LL_WARNS("SECAPI") << "Could not open certificate store " << mFilename << "for save" << LL_ENDL; + } } - - // add a copy of a cert to the store -void LLBasicCertificateStore::append(const LLCertificate& cert) + +// return the store id +std::string LLBasicCertificateStore::storeId() const { + // this is the basic handler which uses the CA.pem store, + // so we ignore this. + return std::string(""); } - - // add a copy of a cert to the store -void LLBasicCertificateStore::insert(const int index, const LLCertificate& cert) + + +// +// LLBasicCertificateChain +// This class represents a chain of certs, each cert being signed by the next cert +// in the chain. Certs must be properly signed by the parent +LLBasicCertificateChain::LLBasicCertificateChain(const X509_STORE_CTX* store) { + + // we're passed in a context, which contains a cert, and a blob of untrusted + // certificates which compose the chain. + if((store == NULL) || (store->cert == NULL)) + { + LL_WARNS("SECAPI") << "An invalid store context was passed in when trying to create a certificate chain" << LL_ENDL; + return; + } + // grab the child cert + LLPointer<LLCertificate> current = new LLBasicCertificate(store->cert); + + add(current); + if(store->untrusted != NULL) + { + // if there are other certs in the chain, we build up a vector + // of untrusted certs so we can search for the parents of each + // consecutive cert. + LLBasicCertificateVector untrusted_certs; + for(int i = 0; i < sk_X509_num(store->untrusted); i++) + { + LLPointer<LLCertificate> cert = new LLBasicCertificate(sk_X509_value(store->untrusted, i)); + untrusted_certs.add(cert); + + } + while(untrusted_certs.size() > 0) + { + LLSD find_data = LLSD::emptyMap(); + LLSD cert_data = current->getLLSD(); + // we simply build the chain via subject/issuer name as the + // client should not have passed in multiple CA's with the same + // subject name. If they did, it'll come out in the wash during + // validation. + find_data[CERT_SUBJECT_NAME_STRING] = cert_data[CERT_ISSUER_NAME_STRING]; + LLBasicCertificateVector::iterator issuer = untrusted_certs.find(find_data); + if (issuer != untrusted_certs.end()) + { + current = untrusted_certs.erase(issuer); + add(current); + } + else + { + break; + } + } + } } - - // remove a certificate from the store -void LLBasicCertificateStore::remove(int index) + + +// subdomain wildcard specifiers can be divided into 3 parts +// the part before the first *, the part after the first * but before +// the second *, and the part after the second *. +// It then iterates over the second for each place in the string +// that it matches. ie if the subdomain was testfoofoobar, and +// the wildcard was test*foo*bar, it would match test, then +// recursively match foofoobar and foobar + +bool _cert_subdomain_wildcard_match(const std::string& subdomain, + const std::string& wildcard) { + // split wildcard into the portion before the *, and the portion after + + int wildcard_pos = wildcard.find_first_of('*'); + // check the case where there is no wildcard. + if(wildcard_pos == wildcard.npos) + { + return (subdomain == wildcard); + } + + // we need to match the first part of the subdomain string up to the wildcard + // position + if(subdomain.substr(0, wildcard_pos) != wildcard.substr(0, wildcard_pos)) + { + // the first portions of the strings didn't match + return FALSE; + } + + // as the portion of the wildcard string before the * matched, we need to check the + // portion afterwards. Grab that portion. + std::string new_wildcard_string = wildcard.substr( wildcard_pos+1, wildcard.npos); + if(new_wildcard_string.empty()) + { + // we had nothing after the *, so it's an automatic match + return TRUE; + } + + // grab the portion of the remaining wildcard string before the next '*'. We need to find this + // within the remaining subdomain string. and then recursively check. + std::string new_wildcard_match_string = new_wildcard_string.substr(0, new_wildcard_string.find_first_of('*')); + + // grab the portion of the subdomain after the part that matched the initial wildcard portion + std::string new_subdomain = subdomain.substr(wildcard_pos, subdomain.npos); + + // iterate through the current subdomain, finding instances of the match string. + int sub_pos = new_subdomain.find_first_of(new_wildcard_match_string); + while(sub_pos != std::string::npos) + { + new_subdomain = new_subdomain.substr(sub_pos, std::string::npos); + if(_cert_subdomain_wildcard_match(new_subdomain, new_wildcard_string)) + { + return TRUE; + } + sub_pos = new_subdomain.find_first_of(new_wildcard_match_string, 1); + + + } + // didn't find any instances of the match string that worked in the subdomain, so fail. + return FALSE; } - - // return a certificate at the index -LLPointer<LLCertificate> LLBasicCertificateStore::operator[](int index) + + +// RFC2459 does not address wildcards as part of it's name matching +// specification, and there is no RFC specifying wildcard matching, +// RFC2818 does a few statements about wildcard matching, but is very +// general. Generally, wildcard matching is per implementation, although +// it's pretty similar. +// in our case, we use the '*' wildcard character only, within each +// subdomain. The hostname and the CN specification should have the +// same number of subdomains. +// We then iterate that algorithm over each subdomain. +bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& common_name) { - LLPointer<LLCertificate> result = NULL; - return result; + std::string new_hostname = hostname; + std::string new_cn = common_name; + int subdomain_pos = new_hostname.find_first_of('.'); + int subcn_pos = new_cn.find_first_of('.'); + + while((subcn_pos != std::string::npos) && (subdomain_pos != std::string::npos)) + { + // snip out the first subdomain and cn element + + if(!_cert_subdomain_wildcard_match(new_hostname.substr(0, subdomain_pos), + new_cn.substr(0, subcn_pos))) + { + return FALSE; + } + new_hostname = new_hostname.substr(subdomain_pos+1, std::string::npos); + new_cn = new_cn.substr(subcn_pos+1, std::string::npos); + subdomain_pos = new_hostname.find_first_of('.'); + subcn_pos = new_cn.find_first_of('.'); + } + return _cert_subdomain_wildcard_match(new_hostname, new_cn); + } - // return the number of certs in the store -int LLBasicCertificateStore::len() const + +// validate that the LLSD array in llsd_set contains the llsd_value +bool _LLSDArrayIncludesValue(const LLSD& llsd_set, LLSD llsd_value) { - return 0; + for(LLSD::array_const_iterator set_value = llsd_set.beginArray(); + set_value != llsd_set.endArray(); + set_value++) + { + if(valueCompareLLSD((*set_value), llsd_value)) + { + return TRUE; + } + } + return FALSE; } - - // load the store from a persisted location -void LLBasicCertificateStore::load(const std::string& store_id) + +void _validateCert(int validation_policy, + const LLPointer<LLCertificate> cert, + const LLSD& validation_params, + int depth) { -} + + LLSD current_cert_info = cert->getLLSD(); + // check basic properties exist in the cert + if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info.has(CERT_SUBJECT_NAME_STRING)) + { + throw LLCertException(cert, "Cert doesn't have a Subject Name"); + } + + if(!current_cert_info.has(CERT_ISSUER_NAME_STRING)) + { + throw LLCertException(cert, "Cert doesn't have an Issuer Name"); + } + + // check basic properties exist in the cert + if(!current_cert_info.has(CERT_VALID_FROM) || !current_cert_info.has(CERT_VALID_TO)) + { + throw LLCertException(cert, "Cert doesn't have an expiration period"); + } + if (!current_cert_info.has(CERT_SHA1_DIGEST)) + { + throw LLCertException(cert, "No SHA1 digest"); + } + + if (validation_policy & VALIDATION_POLICY_TIME) + { + + LLDate validation_date(time(NULL)); + if(validation_params.has(CERT_VALIDATION_DATE)) + { + validation_date = validation_params[CERT_VALIDATION_DATE]; + } - // persist the store -void LLBasicCertificateStore::save() -{ + if((validation_date < current_cert_info[CERT_VALID_FROM].asDate()) || + (validation_date > current_cert_info[CERT_VALID_TO].asDate())) + { + throw LLCertValidationExpirationException(cert, validation_date); + } + } + if (validation_policy & VALIDATION_POLICY_SSL_KU) + { + if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && + (!(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE))) || + !(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT))))) + { + throw LLCertKeyUsageValidationException(cert); + } + // only validate EKU if the cert has it + if(current_cert_info.has(CERT_EXTENDED_KEY_USAGE) && current_cert_info[CERT_EXTENDED_KEY_USAGE].isArray() && + (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE], + LLSD((std::string)CERT_EKU_SERVER_AUTH)))) + { + throw LLCertKeyUsageValidationException(cert); + } + } + if (validation_policy & VALIDATION_POLICY_CA_KU) + { + if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && + (!_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + (std::string)CERT_KU_CERT_SIGN))) + { + throw LLCertKeyUsageValidationException(cert); + } + } + + // validate basic constraints + if ((validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS) && + current_cert_info.has(CERT_BASIC_CONSTRAINTS) && + current_cert_info[CERT_BASIC_CONSTRAINTS].isMap()) + { + if(!current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_CA) || + !current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_CA]) + { + throw LLCertBasicConstraintsValidationException(cert); + } + if (current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_PATHLEN) && + ((current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger() != 0) && + (depth > current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger()))) + { + throw LLCertBasicConstraintsValidationException(cert); + } + } } - - // return the store id -std::string LLBasicCertificateStore::storeId() + +bool _verify_signature(LLPointer<LLCertificate> parent, + LLPointer<LLCertificate> child) { - return std::string(""); -} + bool verify_result = FALSE; + LLSD cert1 = parent->getLLSD(); + LLSD cert2 = child->getLLSD(); + X509 *signing_cert = parent->getOpenSSLX509(); + X509 *child_cert = child->getOpenSSLX509(); + if((signing_cert != NULL) && (child_cert != NULL)) + { + EVP_PKEY *pkey = X509_get_pubkey(signing_cert); + - // validate a cert chain -bool LLBasicCertificateStore::validate(const LLCertificateChain& cert_chain) const + if(pkey) + { + int verify_code = X509_verify(child_cert, pkey); + verify_result = ( verify_code > 0); + EVP_PKEY_free(pkey); + } + else + { + LL_WARNS("SECAPI") << "Could not validate the cert chain signature, as the public key of the signing cert could not be retrieved" << LL_ENDL; + } + + } + else + { + LL_WARNS("SECAPI") << "Signature verification failed as there are no certs in the chain" << LL_ENDL; + } + if(child_cert) + { + X509_free(child_cert); + } + if(signing_cert) + { + X509_free(signing_cert); + } + return verify_result; +} + +// validate the certificate chain against a store. +// There are many aspects of cert validatioin policy involved in +// trust validation. The policies in this validation algorithm include +// * Hostname matching for SSL certs +// * Expiration time matching +// * Signature validation +// * Chain trust (is the cert chain trusted against the store) +// * Basic constraints +// * key usage and extended key usage +// TODO: We should add 'authority key identifier' for chaining. +// This algorithm doesn't simply validate the chain by itself +// and verify the last cert is in the certificate store, or points +// to a cert in the store. It validates whether any cert in the chain +// is trusted in the store, even if it's not the last one. +void LLBasicCertificateChain::validate(int validation_policy, + LLPointer<LLCertificateStore> ca_store, + const LLSD& validation_params) { - return FALSE; + + if(size() < 1) + { + throw LLCertException(NULL, "No certs in chain"); + } + iterator current_cert = begin(); + LLSD current_cert_info = (*current_cert)->getLLSD(); + LLSD validation_date; + if (validation_params.has(CERT_VALIDATION_DATE)) + { + validation_date = validation_params[CERT_VALIDATION_DATE]; + } + + if (validation_policy & VALIDATION_POLICY_HOSTNAME) + { + if(!validation_params.has(CERT_HOSTNAME)) + { + throw LLCertException((*current_cert), "No hostname passed in for validation"); + } + if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info[CERT_SUBJECT_NAME].has(CERT_NAME_CN)) + { + throw LLInvalidCertificate((*current_cert)); + } + + LL_INFOS("SECAPI") << "Validating the hostname " << validation_params[CERT_HOSTNAME].asString() << + "against the cert CN " << current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString() << LL_ENDL; + if(!_cert_hostname_wildcard_match(validation_params[CERT_HOSTNAME].asString(), + current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString())) + { + throw LLCertValidationHostnameException(validation_params[CERT_HOSTNAME].asString(), + (*current_cert)); + } + } + + + int depth = 0; + LLPointer<LLCertificate> previous_cert; + // loop through the cert chain, validating the current cert against the next one. + while(current_cert != end()) + { + + int local_validation_policy = validation_policy; + if(current_cert == begin()) + { + // for the child cert, we don't validate CA stuff + local_validation_policy &= ~(VALIDATION_POLICY_CA_KU | + VALIDATION_POLICY_CA_BASIC_CONSTRAINTS); + } + else + { + // for non-child certs, we don't validate SSL Key usage + local_validation_policy &= ~VALIDATION_POLICY_SSL_KU; + if(!_verify_signature((*current_cert), + previous_cert)) + { + throw LLCertValidationInvalidSignatureException(previous_cert); + } + } + _validateCert(local_validation_policy, + (*current_cert), + validation_params, + depth); + + // look for a CA in the CA store that may belong to this chain. + LLSD cert_llsd = (*current_cert)->getLLSD(); + LLSD cert_search_params = LLSD::emptyMap(); + // is the cert itself in the store? + cert_search_params[CERT_SHA1_DIGEST] = cert_llsd[CERT_SHA1_DIGEST]; + LLCertificateStore::iterator found_store_cert = ca_store->find(cert_search_params); + if(found_store_cert != ca_store->end()) + { + return; + } + + // is the parent in the cert store? + + cert_search_params = LLSD::emptyMap(); + cert_search_params[CERT_SUBJECT_NAME_STRING] = cert_llsd[CERT_ISSUER_NAME_STRING]; + found_store_cert = ca_store->find(cert_search_params); + + if(found_store_cert != ca_store->end()) + { + LLSD foo = (*found_store_cert)->getLLSD(); + // validate the store cert against the depth + _validateCert(validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS, + (*found_store_cert), + LLSD(), + depth); + + // verify the signature of the CA + if(!_verify_signature((*found_store_cert), + (*current_cert))) + { + throw LLCertValidationInvalidSignatureException(*current_cert); + } + // successfully validated. + return; + } + previous_cert = (*current_cert); + current_cert++; + depth++; + } + if (validation_policy & VALIDATION_POLICY_TRUSTED) + { + LLPointer<LLCertificate> untrusted_ca_cert = (*this)[size()-1]; + // we reached the end without finding a trusted cert. + throw LLCertValidationTrustException((*this)[size()-1]); + + } } + // LLSecAPIBasicHandler Class // Interface handler class for the various security storage handlers. @@ -369,7 +1038,39 @@ LLSecAPIBasicHandler::LLSecAPIBasicHandler() mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat"); mProtectedDataMap = LLSD::emptyMap(); + + mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "bin_conf.dat"); _readProtectedData(); + + std::string store_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "CA.pem"); + // copy the CA file to a user writable location so we can manipulate it. + // for this provider, by using a user writable file, there is a risk that + // an attacking program can modify the file, but OS dependent providers + // will reduce that risk. + // by using a user file, modifications will be limited to one user if + // we read-only the main file + + + if (!LLFile::isfile(store_file)) + { + + std::string ca_file_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); + llifstream ca_file(ca_file_path.c_str(), llifstream::binary | llifstream::in); + llofstream copied_store_file(store_file.c_str(), llofstream::binary | llofstream::out); + + while(!ca_file.fail()) + { + char buffer[BUFFER_READ_SIZE]; + ca_file.read(buffer, sizeof(buffer)); + copied_store_file.write(buffer, ca_file.gcount()); + } + ca_file.close(); + copied_store_file.close(); + } + LL_INFOS("SECAPI") << "Loading certificate store from " << store_file << LL_ENDL; + mStore = new LLBasicCertificateStore(store_file); } LLSecAPIBasicHandler::~LLSecAPIBasicHandler() @@ -537,7 +1238,7 @@ LLPointer<LLCertificate> LLSecAPIBasicHandler::getCertificate(X509* openssl_cert // instantiate a chain from an X509_STORE_CTX LLPointer<LLCertificateChain> LLSecAPIBasicHandler::getCertificateChain(const X509_STORE_CTX* chain) { - LLPointer<LLCertificateChain> result = NULL; + LLPointer<LLCertificateChain> result = new LLBasicCertificateChain(chain); return result; } @@ -546,8 +1247,7 @@ LLPointer<LLCertificateChain> LLSecAPIBasicHandler::getCertificateChain(const X5 // persisted) LLPointer<LLCertificateStore> LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id) { - LLPointer<LLCertificateStore> result; - return result; + return mStore; } // retrieve protected data @@ -659,7 +1359,7 @@ void LLSecAPIBasicHandler::saveCredential(LLPointer<LLCredential> cred, bool sav { credential["authenticator"] = cred->getAuthenticator(); } - LL_INFOS("Credentials") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; + LL_INFOS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; setProtectedData("credential", cred->getGrid(), credential); //*TODO: If we're saving Agni credentials, should we write the // credentials to the legacy password.dat/etc? @@ -742,3 +1442,64 @@ std::string LLSecAPIBasicCredential::asString() const } +bool valueCompareLLSD(const LLSD& lhs, const LLSD& rhs) +{ + if (lhs.type() != rhs.type()) + { + return FALSE; + } + if (lhs.isMap()) + { + // iterate through the map, verifying the right hand side has all of the + // values that the left hand side has. + for (LLSD::map_const_iterator litt = lhs.beginMap(); + litt != lhs.endMap(); + litt++) + { + if (!rhs.has(litt->first)) + { + return FALSE; + } + } + + // Now validate that the left hand side has everything the + // right hand side has, and that the values are equal. + for (LLSD::map_const_iterator ritt = rhs.beginMap(); + ritt != rhs.endMap(); + ritt++) + { + if (!lhs.has(ritt->first)) + { + return FALSE; + } + if (!valueCompareLLSD(lhs[ritt->first], ritt->second)) + { + return FALSE; + } + } + return TRUE; + } + else if (lhs.isArray()) + { + LLSD::array_const_iterator ritt = rhs.beginArray(); + // iterate through the array, comparing + for (LLSD::array_const_iterator litt = lhs.beginArray(); + litt != lhs.endArray(); + litt++) + { + if (!valueCompareLLSD(*ritt, *litt)) + { + return FALSE; + } + ritt++; + } + + return (ritt == rhs.endArray()); + } + else + { + // simple type, compare as string + return (lhs.asString() == rhs.asString()); + } + +} diff --git a/indra/newview/llsechandler_basic.h b/indra/newview/llsechandler_basic.h index 5d81b6e190..e041322260 100644 --- a/indra/newview/llsechandler_basic.h +++ b/indra/newview/llsechandler_basic.h @@ -57,59 +57,147 @@ public: virtual ~LLBasicCertificate(); - virtual std::string getPem(); - virtual std::vector<U8> getBinary(); - virtual LLSD getLLSD(); + virtual std::string getPem() const; + virtual std::vector<U8> getBinary() const; + virtual LLSD getLLSD() const; - virtual X509* getOpenSSLX509(); + virtual X509* getOpenSSLX509() const; + + // set llsd elements for testing + void setLLSD(const std::string name, const LLSD& value) { mLLSDInfo[name] = value; } protected: + // certificates are stored as X509 objects, as validation and // other functionality is via openssl X509* mCert; + + LLSD& _initLLSD(); + LLSD mLLSDInfo; }; -// class LLCertificateStore -// represents a store of certificates, typically a store of root CA -// certificates. The store can be persisted, and can be used to validate -// a cert chain -// -class LLBasicCertificateStore : public LLCertificateStore + +// class LLBasicCertificateVector +// Class representing a list of certificates +// This implementation uses a stl vector of certificates. +class LLBasicCertificateVector : virtual public LLCertificateVector { + public: - LLBasicCertificateStore(const std::string& filename); - LLBasicCertificateStore(const X509_STORE* store); - virtual ~LLBasicCertificateStore(); + LLBasicCertificateVector() {} - virtual X509_STORE* getOpenSSLX509Store(); // return an openssl X509_STORE - // for this store + virtual ~LLBasicCertificateVector() {} - // add a copy of a cert to the store - virtual void append(const LLCertificate& cert); + // Implementation of the basic iterator implementation. + // The implementation uses a vector iterator derived from + // the vector in the LLBasicCertificateVector class + class BasicIteratorImpl : public iterator_impl + { + public: + BasicIteratorImpl(std::vector<LLPointer<LLCertificate> >::iterator _iter) { mIter = _iter;} + virtual ~BasicIteratorImpl() {}; + // seek forward or back. Used by the operator++/operator-- implementations + virtual void seek(bool incr) + { + if(incr) + { + mIter++; + } + else + { + mIter--; + } + } + // create a copy of the iterator implementation class, used by the iterator copy constructor + virtual LLPointer<iterator_impl> clone() const + { + return new BasicIteratorImpl(mIter); + } + + virtual bool equals(const LLPointer<iterator_impl>& _iter) const + { + const BasicIteratorImpl *rhs_iter = dynamic_cast<const BasicIteratorImpl *>(_iter.get()); + return (mIter == rhs_iter->mIter); + } + virtual LLPointer<LLCertificate> get() + { + return *mIter; + } + protected: + friend class LLBasicCertificateVector; + std::vector<LLPointer<LLCertificate> >::iterator mIter; + }; - // add a copy of a cert to the store - virtual void insert(const int index, const LLCertificate& cert); + // numeric index of the vector + virtual LLPointer<LLCertificate> operator[](int _index) { return mCerts[_index];} - // remove a certificate from the store - virtual void remove(int index); + // Iteration + virtual iterator begin() { return iterator(new BasicIteratorImpl(mCerts.begin())); } + + virtual iterator end() { return iterator(new BasicIteratorImpl(mCerts.end())); } + + // find a cert given params + virtual iterator find(const LLSD& params); - // return a certificate at the index - virtual LLPointer<LLCertificate> operator[](int index); // return the number of certs in the store - virtual int len() const; + virtual int size() const { return mCerts.size(); } + + // insert the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void add(LLPointer<LLCertificate> cert) { insert(end(), cert); } - // load the store from a persisted location - virtual void load(const std::string& store_id); + // insert the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void insert(iterator _iter, LLPointer<LLCertificate> cert); + + // remove a certificate from the store + virtual LLPointer<LLCertificate> erase(iterator _iter); + +protected: + std::vector<LLPointer<LLCertificate> >mCerts; +}; + +// class LLCertificateStore +// represents a store of certificates, typically a store of root CA +// certificates. The store can be persisted, and can be used to validate +// a cert chain +// +class LLBasicCertificateStore : virtual public LLBasicCertificateVector, public LLCertificateStore +{ +public: + LLBasicCertificateStore(const std::string& filename); + void load_from_file(const std::string& filename); + + virtual ~LLBasicCertificateStore(); // persist the store virtual void save(); // return the store id - virtual std::string storeId(); + virtual std::string storeId() const; - // validate a cert chain - virtual bool validate(const LLCertificateChain& cert_chain) const; +protected: + std::vector<LLPointer<LLCertificate> >mCerts; + std::string mFilename; }; +// class LLCertificateChain +// Class representing a chain of certificates in order, with the +// first element being the child cert. +class LLBasicCertificateChain : virtual public LLBasicCertificateVector, public LLCertificateChain +{ + +public: + LLBasicCertificateChain(const X509_STORE_CTX * store); + + virtual ~LLBasicCertificateChain() {} + + // validate a certificate chain against a certificate store, using the + // given validation policy. + virtual void validate(int validation_policy, + LLPointer<LLCertificateStore> ca_store, + const LLSD& validation_params); +}; + + + // LLSecAPIBasicCredential class class LLSecAPIBasicCredential : public LLCredential { @@ -182,10 +270,13 @@ protected: std::string mProtectedDataFilename; LLSD mProtectedDataMap; + LLPointer<LLBasicCertificateStore> mStore; std::string mLegacyPasswordPath; }; +bool valueCompareLLSD(const LLSD& lhs, const LLSD& rhs); + #endif // LLSECHANDLER_BASIC diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index dd991c8eff..6f7a4e2f6a 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -261,6 +261,9 @@ bool callback_choose_gender(const LLSD& notification, const LLSD& response); void init_start_screen(S32 location_id); void release_start_screen(); void reset_login(); +LLSD transform_cert_args(LLPointer<LLCertificate> cert); +void general_cert_done(const LLSD& notification, const LLSD& response); +void trust_cert_done(const LLSD& notification, const LLSD& response); void apply_udp_blacklist(const std::string& csv); bool process_login_success_response(); void transition_back_to_login_panel(const std::string& emsg); @@ -1053,10 +1056,11 @@ bool idle_startup() { LL_INFOS("LLStartup") << "Login failed, LLLoginInstance::getResponse(): " << LLLoginInstance::getInstance()->getResponse() << LL_ENDL; + LLSD response = LLLoginInstance::getInstance()->getResponse(); // Still have error conditions that may need some // sort of handling. - std::string reason_response = LLLoginInstance::getInstance()->getResponse("reason"); - std::string message_response = LLLoginInstance::getInstance()->getResponse("message"); + std::string reason_response = response["reason"]; + std::string message_response = response["message"]; if(!message_response.empty()) { @@ -1090,18 +1094,65 @@ bool idle_startup() LLLoginInstance::getInstance()->disconnect(); LLAppViewer::instance()->forceQuit(); } - else + else { - // Don't pop up a notification in the TOS case because - // LLFloaterTOS::onCancel() already scolded the user. - if (reason_response != "tos") + if (reason_response != "tos") { - LLSD args; - args["ERROR_MESSAGE"] = emsg.str(); - LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; - LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); + // Don't pop up a notification in the TOS case because + // LLFloaterTOS::onCancel() already scolded the user. + std::string error_code; + if(response.has("errorcode")) + { + error_code = response["errorcode"].asString(); + } + if ((reason_response == "CURLError") && + (error_code == "SSL_CACERT" || error_code == "SSL_PEER_CERTIFICATE") && + response.has("certificate")) + { + // This was a certificate error, so grab the certificate + // and throw up the appropriate dialog. + LLPointer<LLCertificate> certificate = gSecAPIHandler->getCertificate(response["certificate"]); + if(certificate) + { + LLSD args = transform_cert_args(certificate); + + if(error_code == "SSL_CACERT") + { + // if we are handling an untrusted CA, throw up the dialog + // with the 'trust this CA' button. + LLNotificationsUtil::add("TrustCertificateError", args, response, + trust_cert_done); + + show_connect_box = true; + } + else + { + // the certificate exception returns a unique string for each type of exception. + // we grab this string via the LLUserAuth object, and use that to grab the localized + // string. + args["REASON"] = LLTrans::getString(message_response); + + LLNotificationsUtil::add("GeneralCertificateError", args, response, + general_cert_done); + + reset_login(); + gSavedSettings.setBOOL("AutoLogin", FALSE); + show_connect_box = true; + + } + + } + } + else + { + // This wasn't a certificate error, so throw up the normal + // notificatioin message. + LLSD args; + args["ERROR_MESSAGE"] = emsg.str(); + LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; + LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); + } } - //setup map of datetime strings to codes and slt & local time offset from utc // *TODO: Does this need to be here? LLStringOps::setupDatetimeInfo (false); @@ -1126,6 +1177,7 @@ bool idle_startup() LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); transition_back_to_login_panel(emsg.str()); show_connect_box = true; + return FALSE; } } return FALSE; @@ -2370,7 +2422,9 @@ const std::string FEMALE_OUTFIT_FOLDER = "Female Shape & Outfit"; const S32 OPT_CLOSED_WINDOW = -1; const S32 OPT_MALE = 0; const S32 OPT_FEMALE = 1; - +const S32 OPT_TRUST_CERT = 0; +const S32 OPT_CANCEL_TRUST = 1; + bool callback_choose_gender(const LLSD& notification, const LLSD& response) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); @@ -2633,6 +2687,91 @@ bool login_alert_done(const LLSD& notification, const LLSD& response) return false; } +// parse the certificate information into args for the +// certificate notifications +LLSD transform_cert_args(LLPointer<LLCertificate> cert) +{ + LLSD args = LLSD::emptyMap(); + std::string value; + LLSD cert_info = cert->getLLSD(); + // convert all of the elements in the cert into + // args for the xml dialog, so we have flexability to + // display various parts of the cert by only modifying + // the cert alert dialog xml. + for(LLSD::map_iterator iter = cert_info.beginMap(); + iter != cert_info.endMap(); + iter++) + { + // key usage and extended key usage + // are actually arrays, and we want to format them as comma separated + // strings, so special case those. + LLSDSerialize::toXML(cert_info[iter->first], std::cout); + if((iter->first== std::string(CERT_KEY_USAGE)) | + (iter->first == std::string(CERT_EXTENDED_KEY_USAGE))) + { + value = ""; + LLSD usage = cert_info[iter->first]; + for (LLSD::array_iterator usage_iter = usage.beginArray(); + usage_iter != usage.endArray(); + usage_iter++) + { + + if(usage_iter != usage.beginArray()) + { + value += ", "; + } + + value += (*usage_iter).asString(); + } + + } + else + { + value = iter->second.asString(); + } + + std::string name = iter->first; + std::transform(name.begin(), name.end(), name.begin(), + (int(*)(int))toupper); + args[name.c_str()] = value; + } + return args; +} + + +// when we handle a cert error, give focus back to the login panel +void general_cert_done(const LLSD& notification, const LLSD& response) +{ + LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + LLPanelLogin::giveFocus(); +} + +// check to see if the user wants to trust the cert. +// if they do, add it to the cert store and +void trust_cert_done(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + switch(option) + { + case OPT_TRUST_CERT: + { + LLPointer<LLCertificate> cert = gSecAPIHandler->getCertificate(notification["payload"]["certificate"]); + LLPointer<LLCertificateStore> store = gSecAPIHandler->getCertificateStore(gSavedSettings.getString("CertStore")); + store->add(cert); + store->save(); + LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); + break; + } + case OPT_CANCEL_TRUST: + reset_login(); + gSavedSettings.setBOOL("AutoLogin", FALSE); + LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + default: + LLPanelLogin::giveFocus(); + break; + } + +} void apply_udp_blacklist(const std::string& csv) { diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp index fcfaf1eef2..82dc459777 100644 --- a/indra/newview/llviewernetwork.cpp +++ b/indra/newview/llviewernetwork.cpp @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2006&license=viewergpl$ * - * Copyright (c) 2006-2007, Linden Research, Inc. + * Copyright (c) 2006-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -60,12 +60,6 @@ LLGridManager::LLGridManager() } -LLGridManager::LLGridManager(const std::string& grid_file) -{ - // initialize with an explicity grid file for testing. - initialize(grid_file); -} - // // LLGridManager - class for managing the list of known grids, and the current // selection @@ -240,12 +234,8 @@ void LLGridManager::initialize(const std::string& grid_file) // load a grid from the command line. // if the actual grid name is specified from the command line, // set it as the 'selected' grid. - LLSD cmd_line_grid = gSavedSettings.getString("CmdLineGridChoice"); - if (gSavedSettings.controlExists("CmdLineGridChoice")) - { - mGridName = gSavedSettings.getString("CmdLineGridChoice"); - LL_INFOS("GridManager") << "Grid Name: " << mGridName << LL_ENDL; - } + mGridName = gSavedSettings.getString("CmdLineGridChoice"); + LL_INFOS("GridManager") << "Grid Name: " << mGridName << LL_ENDL; // If a command line login URI was passed in, so we should add the command // line grid to the list of grids diff --git a/indra/newview/llviewernetwork.h b/indra/newview/llviewernetwork.h index 7b3ce9c499..0642845d54 100644 --- a/indra/newview/llviewernetwork.h +++ b/indra/newview/llviewernetwork.h @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2006&license=viewergpl$ * - * Copyright (c) 2006-2007, Linden Research, Inc. + * Copyright (c) 2006-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -43,6 +43,7 @@ extern const char* DEFAULT_LOGIN_PAGE; #define GRID_LOGIN_PAGE_VALUE "login_page" #define GRID_IS_SYSTEM_GRID_VALUE "system_grid" #define GRID_IS_FAVORITE_VALUE "favorite" +#define GRID_IS_VISIBLE_VALUE "visible" #define GRID_LOGIN_CREDENTIAL_PAGE_TYPE_VALUE "credential_type" #define GRID_LOGIN_CREDENTIAL_PAGE_TYPE_AGENT "agent" #define GRID_LOGIN_CREDENTIAL_PAGE_TYPE_ACCOUNT "account" @@ -78,7 +79,6 @@ public: // when the grid manager is instantiated, the default grids are automatically // loaded, and the grids favorites list is loaded from the xml file. - LLGridManager(const std::string& grid_file); LLGridManager(); ~LLGridManager(); diff --git a/indra/newview/llxmlrpclistener.cpp b/indra/newview/llxmlrpclistener.cpp index 15417614af..8237132ac5 100644 --- a/indra/newview/llxmlrpclistener.cpp +++ b/indra/newview/llxmlrpclistener.cpp @@ -28,6 +28,7 @@ #include "llerror.h" #include "stringize.h" #include "llxmlrpctransaction.h" +#include "llsecapi.h" #if LL_WINDOWS #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally @@ -356,7 +357,22 @@ public: << data["errorcode"].asString() << " (" << data["error"].asString() << ")" << LL_ENDL; - // In addition to CURLE_OK, LLUserAuth distinguishes different error + + switch (curlcode) + { + case CURLE_SSL_PEER_CERTIFICATE: + case CURLE_SSL_CACERT: + { + LLPointer<LLCertificate> error_cert(mTransaction->getErrorCert()); + if(error_cert) + { + data["certificate"] = error_cert->getPem(); + } + break; + } + default: + break; + } // values of 'curlcode': // CURLE_COULDNT_RESOLVE_HOST, // CURLE_SSL_PEER_CERTIFICATE, diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp index 70859e8ea5..da61840761 100644 --- a/indra/newview/llxmlrpctransaction.cpp +++ b/indra/newview/llxmlrpctransaction.cpp @@ -31,6 +31,9 @@ */ #include "llviewerprecompiledheaders.h" +#include <openssl/x509_vfy.h> +#include <openssl/ssl.h> +#include "llsecapi.h" #include "llxmlrpctransaction.h" #include "llxmlrpclistener.h" @@ -176,6 +179,8 @@ public: std::string mResponseText; XMLRPC_REQUEST mResponse; + std::string mCertStore; + LLPointer<LLCertificate> mErrorCert; Impl(const std::string& uri, XMLRPC_REQUEST request, bool useGzip); Impl(const std::string& uri, @@ -190,7 +195,8 @@ public: private: void init(XMLRPC_REQUEST request, bool useGzip); - + static int _sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param); + static CURLcode _sslCtxFunction(CURL * curl, void *sslctx, void *param); static size_t curlDownloadCallback( char* data, size_t size, size_t nmemb, void* user_data); }; @@ -228,8 +234,74 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri, XMLRPC_RequestFree(request, 1); } +// _sslCertVerifyCallback +// callback called when a cert verification is requested. +// calls SECAPI to validate the context +int LLXMLRPCTransaction::Impl::_sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param) +{ + LLXMLRPCTransaction::Impl *transaction = (LLXMLRPCTransaction::Impl *)param; + LLPointer<LLCertificateStore> store = gSecAPIHandler->getCertificateStore(transaction->mCertStore); + LLPointer<LLCertificateChain> chain = gSecAPIHandler->getCertificateChain(ctx); + LLSD validation_params = LLSD::emptyMap(); + LLURI uri(transaction->mURI); + validation_params[CERT_HOSTNAME] = uri.hostName(); + try + { + chain->validate(VALIDATION_POLICY_SSL, store, validation_params); + } + catch (LLCertValidationTrustException& cert_exception) + { + // this exception is is handled differently than the general cert + // exceptions, as we allow the user to actually add the certificate + // for trust. + // therefore we pass back a different error code + // NOTE: We're currently 'wired' to pass around CURL error codes. This is + // somewhat clumsy, as we may run into errors that do not map directly to curl + // error codes. Should be refactored with login refactoring, perhaps. + transaction->mCurlCode = CURLE_SSL_CACERT; + // set the status directly. set curl status generates error messages and we want + // to use the fixed ones from the exceptions + transaction->setStatus(StatusCURLError, cert_exception.getMessage(), std::string()); + // We should probably have a more generic way of passing information + // back to the error handlers. + transaction->mErrorCert = cert_exception.getCert(); + return 0; + } + catch (LLCertException& cert_exception) + { + transaction->mCurlCode = CURLE_SSL_PEER_CERTIFICATE; + // set the status directly. set curl status generates error messages and we want + // to use the fixed ones from the exceptions + transaction->setStatus(StatusCURLError, cert_exception.getMessage(), std::string()); + transaction->mErrorCert = cert_exception.getCert(); + return 0; + } + catch (...) + { + // any other odd error, we just handle as a connect error. + transaction->mCurlCode = CURLE_SSL_CONNECT_ERROR; + transaction->setCurlStatus(CURLE_SSL_CONNECT_ERROR); + return 0; + } + return 1; +} +// _sslCtxFunction +// Callback function called when an SSL Context is created via CURL +// used to configure the context for custom cert validate(<, <#const & xs#>, <#T * #>, <#long #>)tion +// based on SECAPI +CURLcode LLXMLRPCTransaction::Impl::_sslCtxFunction(CURL * curl, void *sslctx, void *param) +{ + SSL_CTX * ctx = (SSL_CTX *) sslctx; + // disable any default verification for server certs + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + // set the verification callback. + SSL_CTX_set_cert_verify_callback(ctx, _sslCertVerifyCallback, param); + // the calls are void + return CURLE_OK; + +} void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) { @@ -237,6 +309,7 @@ void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) { mCurlRequest = new LLCurlEasyRequest(); } + mErrorCert = NULL; if (gSavedSettings.getBOOL("BrowserProxyEnabled")) { @@ -252,11 +325,13 @@ void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) // mCurlRequest->setopt(CURLOPT_VERBOSE, 1); // usefull for debugging mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1); mCurlRequest->setWriteCallback(&curlDownloadCallback, (void*)this); - BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert"); + BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert"); + mCertStore = gSavedSettings.getString("CertStore"); mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, vefifySSLCert); mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, vefifySSLCert ? 2 : 0); // Be a little impatient about establishing connections. mCurlRequest->setopt(CURLOPT_CONNECTTIMEOUT, 40L); + mCurlRequest->setSSLCtxCallback(_sslCtxFunction, (void *)this); /* Setting the DNS cache timeout to -1 disables it completely. This might help with bug #503 */ @@ -342,11 +417,19 @@ bool LLXMLRPCTransaction::Impl::process() { if (result != CURLE_OK) { - setCurlStatus(result); - llwarns << "LLXMLRPCTransaction CURL error " - << mCurlCode << ": " << mCurlRequest->getErrorString() << llendl; - llwarns << "LLXMLRPCTransaction request URI: " - << mURI << llendl; + if ((result != CURLE_SSL_PEER_CERTIFICATE) && + (result != CURLE_SSL_CACERT)) + { + // if we have a curl error that's not already been handled + // (a non cert error), then generate the error message as + // appropriate + setCurlStatus(result); + + llwarns << "LLXMLRPCTransaction CURL error " + << mCurlCode << ": " << mCurlRequest->getErrorString() << llendl; + llwarns << "LLXMLRPCTransaction request URI: " + << mURI << llendl; + } return true; } @@ -424,7 +507,6 @@ void LLXMLRPCTransaction::Impl::setStatus(EStatus status, case StatusComplete: mStatusMessage = "(done)"; break; - default: // Usually this means that there's a problem with the login server, // not with the client. Direct user to status page. @@ -540,6 +622,11 @@ std::string LLXMLRPCTransaction::statusMessage() return impl.mStatusMessage; } +LLPointer<LLCertificate> LLXMLRPCTransaction::getErrorCert() +{ + return impl.mErrorCert; +} + std::string LLXMLRPCTransaction::statusURI() { return impl.mStatusURI; diff --git a/indra/newview/llxmlrpctransaction.h b/indra/newview/llxmlrpctransaction.h index c835423d67..8beb2e2623 100644 --- a/indra/newview/llxmlrpctransaction.h +++ b/indra/newview/llxmlrpctransaction.h @@ -38,6 +38,7 @@ typedef struct _xmlrpc_request* XMLRPC_REQUEST; typedef struct _xmlrpc_value* XMLRPC_VALUE; // foward decl of types from xmlrpc.h (this usage is type safe) +class LLCertificate; class LLXMLRPCValue // a c++ wrapper around XMLRPC_VALUE @@ -115,6 +116,8 @@ public: EStatus status(int* curlCode); // return status, and extended CURL code, if code isn't null + + LLPointer<LLCertificate> getErrorCert(); std::string statusMessage(); // return a message string, suitable for showing the user std::string statusURI(); diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 6d5f0bedb0..c4cbcb1dc8 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -2371,6 +2371,48 @@ Please choose the male or female avatar. You can change your mind later. notext="Female" yestext="Male"/> </notification> + <notification icon="alertmodal.tga" + name="GeneralCertificateError" + type="alertmodal"> +Could not connect to the server. +[REASON] + +SubjectName: [SUBJECT_NAME_STRING] +IssuerName: [ISSUER_NAME_STRING] +Valid From: [VALID_FROM] +Valid To: [VALID_TO] +MD5 Fingerprint: [SHA1_DIGEST] +SHA1 Fingerprint: [MD5_DIGEST] +Key Usage: [KEYUSAGE] +Extended Key Usage: [EXTENDEDKEYUSAGE] +Subject Key Identifier: [SUBJECTKEYIDENTIFIER] + <usetemplate + name="okbutton" + yestext="OK"/> + </notification> + + <notification icon="alertmodal.tga" + name="TrustCertificateError" + type="alertmodal"> +The certification authority for this server is not known. + +Certificate Information: +SubjectName: [SUBJECT_NAME_STRING] +IssuerName: [ISSUER_NAME_STRING] +Valid From: [VALID_FROM] +Valid To: [VALID_TO] +MD5 Fingerprint: [SHA1_DIGEST] +SHA1 Fingerprint: [MD5_DIGEST] +Key Usage: [KEYUSAGE] +Extended Key Usage: [EXTENDEDKEYUSAGE] +Subject Key Identifier: [SUBJECTKEYIDENTIFIER] + +Would you like to trust this authority? + <usetemplate + name="okcancelbuttons" + notext="Cancel" + yestext="Trust"/> + </notification> <notification icon="alertmodal.tga" diff --git a/indra/newview/tests/llsecapi_test.cpp b/indra/newview/tests/llsecapi_test.cpp new file mode 100644 index 0000000000..22bc47b6d3 --- /dev/null +++ b/indra/newview/tests/llsecapi_test.cpp @@ -0,0 +1,188 @@ +/** + * @file llsecapi_test.cpp + * @author Roxie + * @date 2009-02-10 + * @brief Test the sec api functionality + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden LregisterSecAPIab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#include "../llviewerprecompiledheaders.h" +#include "../llviewernetwork.h" +#include "../test/lltut.h" +#include "../llsecapi.h" +#include "../../llxml/llcontrol.h" + + +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker<LLControlGroup, std::string>(name) {} +LLControlGroup::~LLControlGroup() {} +BOOL LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + BOOL persist) {return TRUE;} +void LLControlGroup::setString(const std::string& name, const std::string& val){} +std::string LLControlGroup::getString(const std::string& name) +{ + return ""; +} + + +LLControlGroup gSavedSettings("test"); +class LLSecAPIBasicHandler : public LLSecAPIHandler +{ +protected: + LLPointer<LLCertificateChain> mCertChain; + LLPointer<LLCertificate> mCert; + LLPointer<LLCertificateStore> mCertStore; + LLSD mLLSD; + +public: + LLSecAPIBasicHandler() {} + + virtual ~LLSecAPIBasicHandler() {} + + // instantiate a certificate from a pem string + virtual LLPointer<LLCertificate> getCertificate(const std::string& pem_cert) + { + return mCert; + } + + + // instiate a certificate from an openssl X509 structure + virtual LLPointer<LLCertificate> getCertificate(X509* openssl_cert) + { + return mCert; + } + + + // instantiate a chain from an X509_STORE_CTX + virtual LLPointer<LLCertificateChain> getCertificateChain(const X509_STORE_CTX* chain) + { + return mCertChain; + } + + // 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) + virtual LLPointer<LLCertificateStore> getCertificateStore(const std::string& store_id) + { + return mCertStore; + } + + // persist data in a protected store + virtual void setProtectedData(const std::string& data_type, + const std::string& data_id, + const LLSD& data) {} + + // retrieve protected data + virtual LLSD getProtectedData(const std::string& data_type, + const std::string& data_id) + { + return mLLSD; + } + + virtual void deleteProtectedData(const std::string& data_type, + const std::string& data_id) + { + } + + virtual LLPointer<LLCredential> createCredential(const std::string& grid, + const LLSD& identifier, + const LLSD& authenticator) + { + LLPointer<LLCredential> cred = NULL; + return cred; + } + + virtual LLPointer<LLCredential> loadCredential(const std::string& grid) + { + LLPointer<LLCredential> cred = NULL; + return cred; + } + + virtual void saveCredential(LLPointer<LLCredential> cred, bool save_authenticator) {} + + virtual void deleteCredential(LLPointer<LLCredential> cred) {} +}; + +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- +namespace tut +{ + // Test wrapper declaration : wrapping nothing for the moment + struct secapiTest + { + + secapiTest() + { + } + ~secapiTest() + { + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group<secapiTest> secapiTestFactory; + typedef secapiTestFactory::object secapiTestObject; + tut::secapiTestFactory tut_test("llsecapi"); + + // --------------------------------------------------------------------------------------- + // Test functions + // --------------------------------------------------------------------------------------- + // registration + template<> template<> + void secapiTestObject::test<1>() + { + // retrieve an unknown handler + + ensure("'Unknown' handler should be NULL", !(BOOL)getSecHandler("unknown")); + LLPointer<LLSecAPIHandler> test1_handler = new LLSecAPIBasicHandler(); + registerSecHandler("sectest1", test1_handler); + ensure("'Unknown' handler should be NULL", !(BOOL)getSecHandler("unknown")); + LLPointer<LLSecAPIHandler> retrieved_test1_handler = getSecHandler("sectest1"); + ensure("Retrieved sectest1 handler should be the same", + retrieved_test1_handler == test1_handler); + + // insert a second handler + LLPointer<LLSecAPIHandler> test2_handler = new LLSecAPIBasicHandler(); + registerSecHandler("sectest2", test2_handler); + ensure("'Unknown' handler should be NULL", !(BOOL)getSecHandler("unknown")); + retrieved_test1_handler = getSecHandler("sectest1"); + ensure("Retrieved sectest1 handler should be the same", + retrieved_test1_handler == test1_handler); + + LLPointer<LLSecAPIHandler> retrieved_test2_handler = getSecHandler("sectest2"); + ensure("Retrieved sectest1 handler should be the same", + retrieved_test2_handler == test2_handler); + + } +}
\ No newline at end of file diff --git a/indra/newview/tests/llsechandler_basic_test.cpp b/indra/newview/tests/llsechandler_basic_test.cpp index a5554d55a5..f52ebc198d 100644 --- a/indra/newview/tests/llsechandler_basic_test.cpp +++ b/indra/newview/tests/llsechandler_basic_test.cpp @@ -44,9 +44,60 @@ #include <ios> #include <llsdserialize.h> #include <openssl/pem.h> +#include <openssl/err.h> +#include <openssl/evp.h> #include "llxorcipher.h" -LLControlGroup gSavedSettings; +#define ensure_throws(str, exc_type, cert, func, ...) \ +try \ +{ \ +func(__VA_ARGS__); \ +fail("throws, " str); \ +} \ +catch(exc_type& except) \ +{ \ +ensure("Exception cert is incorrect for " str, except.getCert() == cert); \ +} + +extern bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& wildcard_string); + +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +std::string gFirstName; +std::string gLastName; +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker<LLControlGroup, std::string>(name) {} +LLControlGroup::~LLControlGroup() {} +BOOL LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + BOOL persist) {return TRUE;} +void LLControlGroup::setString(const std::string& name, const std::string& val){} +std::string LLControlGroup::getString(const std::string& name) +{ + + if (name == "FirstName") + return gFirstName; + else if (name == "LastName") + return gLastName; + return ""; +} + +LLSD LLCredential::getLoginParams() +{ + LLSD result = LLSD::emptyMap(); + + // legacy credential + result["passwd"] = "$1$testpasssd"; + result["first"] = "myfirst"; + result["last"] ="mylast"; + return result; +} + + + +LLControlGroup gSavedSettings("test"); unsigned char gMACAddress[MAC_ADDRESS_BYTES] = {77,21,46,31,89,2}; // ------------------------------------------------------------------------------------------- @@ -57,44 +108,91 @@ namespace tut // Test wrapper declaration : wrapping nothing for the moment struct sechandler_basic_test { - std::string mPemTestCert; + std::string mPemTestCert, mPemRootCert, mPemIntermediateCert, mPemChildCert; std::string mDerFormat; - X509 *mX509TestCert; - LLBasicCertificate* mTestCert; + X509 *mX509TestCert, *mX509RootCert, *mX509IntermediateCert, *mX509ChildCert; sechandler_basic_test() { + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + ERR_load_crypto_strings(); + gFirstName = ""; + gLastName = ""; LLFile::remove("test_password.dat"); LLFile::remove("sechandler_settings.tmp"); mPemTestCert = "-----BEGIN CERTIFICATE-----\n" -"MIIEuDCCA6CgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBtDELMAkGA1UEBhMCQlIx\n" -"EzARBgNVBAoTCklDUC1CcmFzaWwxPTA7BgNVBAsTNEluc3RpdHV0byBOYWNpb25h\n" -"bCBkZSBUZWNub2xvZ2lhIGRhIEluZm9ybWFjYW8gLSBJVEkxETAPBgNVBAcTCEJy\n" -"YXNpbGlhMQswCQYDVQQIEwJERjExMC8GA1UEAxMoQXV0b3JpZGFkZSBDZXJ0aWZp\n" -"Y2Fkb3JhIFJhaXogQnJhc2lsZWlyYTAeFw0wMTExMzAxMjU4MDBaFw0xMTExMzAy\n" -"MzU5MDBaMIG0MQswCQYDVQQGEwJCUjETMBEGA1UEChMKSUNQLUJyYXNpbDE9MDsG\n" -"A1UECxM0SW5zdGl0dXRvIE5hY2lvbmFsIGRlIFRlY25vbG9naWEgZGEgSW5mb3Jt\n" -"YWNhbyAtIElUSTERMA8GA1UEBxMIQnJhc2lsaWExCzAJBgNVBAgTAkRGMTEwLwYD\n" -"VQQDEyhBdXRvcmlkYWRlIENlcnRpZmljYWRvcmEgUmFpeiBCcmFzaWxlaXJhMIIB\n" -"IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPMudwX/hvm+Uh2b/lQAcHVA\n" -"isamaLkWdkwP9/S/tOKIgRrL6Oy+ZIGlOUdd6uYtk9Ma/3pUpgcfNAj0vYm5gsyj\n" -"Qo9emsc+x6m4VWwk9iqMZSCK5EQkAq/Ut4n7KuLE1+gdftwdIgxfUsPt4CyNrY50\n" -"QV57KM2UT8x5rrmzEjr7TICGpSUAl2gVqe6xaii+bmYR1QrmWaBSAG59LrkrjrYt\n" -"bRhFboUDe1DK+6T8s5L6k8c8okpbHpa9veMztDVC9sPJ60MWXh6anVKo1UcLcbUR\n" -"yEeNvZneVRKAAU6ouwdjDvwlsaKydFKwed0ToQ47bmUKgcm+wV3eTRk36UOnTwID\n" -"AQABo4HSMIHPME4GA1UdIARHMEUwQwYFYEwBAQAwOjA4BggrBgEFBQcCARYsaHR0\n" -"cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0RQQ2FjcmFpei5wZGYwPQYDVR0f\n" -"BDYwNDAyoDCgLoYsaHR0cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0xDUmFj\n" -"cmFpei5jcmwwHQYDVR0OBBYEFIr68VeEERM1kEL6V0lUaQ2kxPA3MA8GA1UdEwEB\n" -"/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAZA5c1\n" -"U/hgIh6OcgLAfiJgFWpvmDZWqlV30/bHFpj8iBobJSm5uDpt7TirYh1Uxe3fQaGl\n" -"YjJe+9zd+izPRbBqXPVQA34EXcwk4qpWuf1hHriWfdrx8AcqSqr6CuQFwSr75Fos\n" -"SzlwDADa70mT7wZjAmQhnZx2xJ6wfWlT9VQfS//JYeIc7Fue2JNLd00UOSMMaiK/\n" -"t79enKNHEA2fupH3vEigf5Eh4bVAN5VohrTm6MY53x7XQZZr1ME7a55lFEnSeT0u\n" -"mlOAjR2mAbvSM5X5oSZNrmetdzyTj2flCM8CC7MLab0kkdngRIlUBGHF1/S5nmPb\n" -"K+9A46sd33oqK8n8\n" -"-----END CERTIFICATE-----\n" -""; + "MIIEuDCCA6CgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBtDELMAkGA1UEBhMCQlIx\n" + "EzARBgNVBAoTCklDUC1CcmFzaWwxPTA7BgNVBAsTNEluc3RpdHV0byBOYWNpb25h\n" + "bCBkZSBUZWNub2xvZ2lhIGRhIEluZm9ybWFjYW8gLSBJVEkxETAPBgNVBAcTCEJy\n" + "YXNpbGlhMQswCQYDVQQIEwJERjExMC8GA1UEAxMoQXV0b3JpZGFkZSBDZXJ0aWZp\n" + "Y2Fkb3JhIFJhaXogQnJhc2lsZWlyYTAeFw0wMTExMzAxMjU4MDBaFw0xMTExMzAy\n" + "MzU5MDBaMIG0MQswCQYDVQQGEwJCUjETMBEGA1UEChMKSUNQLUJyYXNpbDE9MDsG\n" + "A1UECxM0SW5zdGl0dXRvIE5hY2lvbmFsIGRlIFRlY25vbG9naWEgZGEgSW5mb3Jt\n" + "YWNhbyAtIElUSTERMA8GA1UEBxMIQnJhc2lsaWExCzAJBgNVBAgTAkRGMTEwLwYD\n" + "VQQDEyhBdXRvcmlkYWRlIENlcnRpZmljYWRvcmEgUmFpeiBCcmFzaWxlaXJhMIIB\n" + "IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwPMudwX/hvm+Uh2b/lQAcHVA\n" + "isamaLkWdkwP9/S/tOKIgRrL6Oy+ZIGlOUdd6uYtk9Ma/3pUpgcfNAj0vYm5gsyj\n" + "Qo9emsc+x6m4VWwk9iqMZSCK5EQkAq/Ut4n7KuLE1+gdftwdIgxfUsPt4CyNrY50\n" + "QV57KM2UT8x5rrmzEjr7TICGpSUAl2gVqe6xaii+bmYR1QrmWaBSAG59LrkrjrYt\n" + "bRhFboUDe1DK+6T8s5L6k8c8okpbHpa9veMztDVC9sPJ60MWXh6anVKo1UcLcbUR\n" + "yEeNvZneVRKAAU6ouwdjDvwlsaKydFKwed0ToQ47bmUKgcm+wV3eTRk36UOnTwID\n" + "AQABo4HSMIHPME4GA1UdIARHMEUwQwYFYEwBAQAwOjA4BggrBgEFBQcCARYsaHR0\n" + "cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0RQQ2FjcmFpei5wZGYwPQYDVR0f\n" + "BDYwNDAyoDCgLoYsaHR0cDovL2FjcmFpei5pY3BicmFzaWwuZ292LmJyL0xDUmFj\n" + "cmFpei5jcmwwHQYDVR0OBBYEFIr68VeEERM1kEL6V0lUaQ2kxPA3MA8GA1UdEwEB\n" + "/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQAZA5c1\n" + "U/hgIh6OcgLAfiJgFWpvmDZWqlV30/bHFpj8iBobJSm5uDpt7TirYh1Uxe3fQaGl\n" + "YjJe+9zd+izPRbBqXPVQA34EXcwk4qpWuf1hHriWfdrx8AcqSqr6CuQFwSr75Fos\n" + "SzlwDADa70mT7wZjAmQhnZx2xJ6wfWlT9VQfS//JYeIc7Fue2JNLd00UOSMMaiK/\n" + "t79enKNHEA2fupH3vEigf5Eh4bVAN5VohrTm6MY53x7XQZZr1ME7a55lFEnSeT0u\n" + "mlOAjR2mAbvSM5X5oSZNrmetdzyTj2flCM8CC7MLab0kkdngRIlUBGHF1/S5nmPb\n" + "K+9A46sd33oqK8n8\n" + "-----END CERTIFICATE-----\n"; + + mPemRootCert = "-----BEGIN CERTIFICATE-----\n" + "MIIB0TCCATqgAwIBAgIJANaTqrzEvHaRMA0GCSqGSIb3DQEBBAUAMBsxGTAXBgNV\n" + "BAMTEFJveGllcyB0ZXN0IHJvb3QwHhcNMDkwNDE1MjEwNzQ3WhcNMTAwNDE1MjEw\n" + "NzQ3WjAbMRkwFwYDVQQDExBSb3hpZXMgdGVzdCByb290MIGfMA0GCSqGSIb3DQEB\n" + "AQUAA4GNADCBiQKBgQCpo5nDW6RNz9IHUVZd7Tw2XAQiBniDF4xH0N1w7sUYTiFq\n" + "21mABsnOPJD3ra+MtOsXPHcaljm661JjTD8L40v5sfEbqDUPcOw76ClrPqnuAeyT\n" + "38qk8DHku/mT8YdprevGZdVcUXQg3vosVzOL93HOOHK+u61mEEoM9W5xoNVEdQID\n" + "AQABox0wGzAMBgNVHRMEBTADAQH/MAsGA1UdDwQEAwIBBjANBgkqhkiG9w0BAQQF\n" + "AAOBgQAzn0aW/+zWPmcTbvxonyiYYUr9b4SOB/quhAkT8KT4ir1dcZAXRR59+kEn\n" + "HSTu1FAodV0gvESqyobftF5hZ1XMxdJqGu//xP+YCwlv244G/0pp7KLI8ihNO2+N\n" + "lPBUJgbo++ZkhiE1jotZi9Ay0Oedh3s/AfbMZPyfpJ23ll6+BA==\n" + "-----END CERTIFICATE-----\n"; + + + + mPemIntermediateCert = "-----BEGIN CERTIFICATE-----\n" + "MIIBzzCCATigAwIBAgIBATANBgkqhkiG9w0BAQQFADAbMRkwFwYDVQQDExBSb3hp\n" + "ZXMgdGVzdCByb290MB4XDTA5MDQxNTIxMzE1NloXDTEwMDQxNTIxMzE1NlowITEf\n" + "MB0GA1UEAxMWUm94aWVzIGludGVybWVkaWF0ZSBDQTCBnzANBgkqhkiG9w0BAQEF\n" + "AAOBjQAwgYkCgYEA15MM0W1R37rx/24Q2Qkb5bSiQZxTUcQAhJ2pA8mwUucXuCVt\n" + "6ayI2TuN32nkjmsCgUkiT/bdXWp0OJo7/MXRIFeUNMCRxrpeFnxuigYEqbIXAdN6\n" + "qu/vdG2X4PRv/v9Ijrju4cBEiKIldIgOurWEIfXEsVSFP2XmFQHesF04qDcCAwEA\n" + "AaMdMBswDAYDVR0TBAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEEBQAD\n" + "gYEAYljikYgak3W1jSo0vYthNHUy3lBVAKzDhpM96lY5OuXFslpCRX42zNL8X3kN\n" + "U/4IaJUVtZqx8WsUXl1eXHzBCaXCftapV4Ir6cENLIsXCdXs8paFYzN5nPJA5GYU\n" + "zWgkSEl1MEhNIc+bJW34vwi29EjrAShAhsIZ84Mt/lvD3Pc=\n" + "-----END CERTIFICATE-----\n"; + + mPemChildCert = "-----BEGIN CERTIFICATE-----\n" + "MIIB5DCCAU0CBEnm9eUwDQYJKoZIhvcNAQEEBQAwITEfMB0GA1UEAxMWUm94aWVz\n" + "IGludGVybWVkaWF0ZSBDQTAeFw0wOTA0MTYwMDAzNDlaFw0xMDA0MTYwMDAzNDla\n" + "MCAxHjAcBgNVBAMTFWVuaWFjNjMubGluZGVubGFiLmNvbTCBnzANBgkqhkiG9w0B\n" + "AQEFAAOBjQAwgYkCgYEAp9I5rofEzbjNht+9QejfnsIlEPqSxskoWKCG255TesWR\n" + "RTmw9wafHQQkJk/VIsaU4RMBYHkknGbHX2dGvMHmKZoWUPSQ/8FZz09o0Qx3TNUZ\n" + "l7KlGOD2d1c7ZxXDPqlLC6QW8DrE1/8zfwJ5cbYBXc8e7OKdSZeRrnwHyw4Q8r8C\n" + "AwEAAaMvMC0wEwYDVR0lBAwwCgYIKwYBBQUHAwEwCQYDVR0TBAIwADALBgNVHQ8E\n" + "BAMCBaAwDQYJKoZIhvcNAQEEBQADgYEAIG0M5tqYlXyMiGKPZfXy/R3M3ZZOapDk\n" + "W0dsXJYXAc35ftwtn0VYu9CNnZCcli17/d+AKhkK8a/oGPazqudjFF6WLJLTXaY9\n" + "NmhkJcOPADXkbyQPUPXzLe4YRrkEQeGhzMb4rKDQ1TKAcXfs0Y068pTpsixNSxja\n" + "NhAUUcve5Is=\n" + "-----END CERTIFICATE-----\n"; + mDerFormat = "MIIEuDCCA6CgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBtDELMAkGA1UEBhMCQlIxEzARBgNVBAoT" "CklDUC1CcmFzaWwxPTA7BgNVBAsTNEluc3RpdHV0byBOYWNpb25hbCBkZSBUZWNub2xvZ2lhIGRh" "IEluZm9ybWFjYW8gLSBJVEkxETAPBgNVBAcTCEJyYXNpbGlhMQswCQYDVQQIEwJERjExMC8GA1UE" @@ -118,24 +216,33 @@ namespace tut "1ME7a55lFEnSeT0umlOAjR2mAbvSM5X5oSZNrmetdzyTj2flCM8CC7MLab0kkdngRIlUBGHF1/S5" "nmPbK+9A46sd33oqK8n8"; - mTestCert = new LLBasicCertificate(mPemTestCert); - - gSavedSettings.cleanup(); - gSavedSettings.declareString("FirstName", "", "", FALSE); - gSavedSettings.declareString("LastName", "", "", FALSE); mX509TestCert = NULL; - BIO * validation_bio = BIO_new_mem_buf((void*)mPemTestCert.c_str(), mPemTestCert.length()); + mX509RootCert = NULL; + mX509IntermediateCert = NULL; + mX509ChildCert = NULL; + BIO * validation_bio = BIO_new_mem_buf((void*)mPemTestCert.c_str(), mPemTestCert.length()); PEM_read_bio_X509(validation_bio, &mX509TestCert, 0, NULL); BIO_free(validation_bio); - + validation_bio = BIO_new_mem_buf((void*)mPemRootCert.c_str(), mPemRootCert.length()); + PEM_read_bio_X509(validation_bio, &mX509RootCert, 0, NULL); + BIO_free(validation_bio); + validation_bio = BIO_new_mem_buf((void*)mPemIntermediateCert.c_str(), mPemIntermediateCert.length()); + PEM_read_bio_X509(validation_bio, &mX509IntermediateCert, 0, NULL); + BIO_free(validation_bio); + validation_bio = BIO_new_mem_buf((void*)mPemChildCert.c_str(), mPemChildCert.length()); + PEM_read_bio_X509(validation_bio, &mX509ChildCert, 0, NULL); + BIO_free(validation_bio); } ~sechandler_basic_test() { LLFile::remove("test_password.dat"); LLFile::remove("sechandler_settings.tmp"); - delete mTestCert; + LLFile::remove("mycertstore.pem"); X509_free(mX509TestCert); + X509_free(mX509RootCert); + X509_free(mX509IntermediateCert); + X509_free(mX509ChildCert); } }; @@ -152,18 +259,18 @@ namespace tut void sechandler_basic_test_object::test<1>() { - char buffer[4096]; - + LLPointer<LLCertificate> test_cert = new LLBasicCertificate(mPemTestCert); + ensure_equals("Resultant pem is correct", - mPemTestCert, mTestCert->getPem()); - std::vector<U8> binary_cert = mTestCert->getBinary(); + mPemTestCert, test_cert->getPem()); + std::vector<U8> binary_cert = test_cert->getBinary(); apr_base64_encode(buffer, (const char *)&binary_cert[0], binary_cert.size()); ensure_equals("Der Format is correct", memcmp(buffer, mDerFormat.c_str(), mDerFormat.length()), 0); - LLSD llsd_cert = mTestCert->getLLSD(); + LLSD llsd_cert = test_cert->getLLSD(); std::ostringstream llsd_value; llsd_value << LLSDOStreamer<LLSDNotationFormatter>(llsd_cert) << std::endl; std::string llsd_cert_str = llsd_value.str(); @@ -194,10 +301,15 @@ namespace tut ensure_equals("serial number", (std::string)llsd_cert["serial_number"], "04"); // sha1 digest is giving a weird value, and I've no idea why...feh //ensure_equals("sha1 digest", (std::string)llsd_cert["sha1_digest"], "8e:fd:ca:bc:93:e6:1e:92:5d:4d:1d:ed:18:1a:43:20:a4:67:a1:39"); - ensure_equals("valid from", (std::string)llsd_cert["valid_from"], "2001-11-30T20:58:00Z"); - ensure_equals("valid to", (std::string)llsd_cert["valid_to"], "2011-12-01T07:59:00Z"); + ensure_equals("valid from", (std::string)llsd_cert["valid_from"], "2001-11-30T12:58:00Z"); + ensure_equals("valid to", (std::string)llsd_cert["valid_to"], "2011-11-30T23:59:00Z"); + LLSD expectedKeyUsage = LLSD::emptyArray(); + expectedKeyUsage.append(LLSD((std::string)"certSigning")); + expectedKeyUsage.append(LLSD((std::string)"crlSigning")); + ensure("key usage", valueCompareLLSD(llsd_cert["keyUsage"], expectedKeyUsage)); + ensure("basic constraints", (bool)llsd_cert["basicConstraints"]["CA"]); - ensure("x509 is equal", !X509_cmp(mX509TestCert, mTestCert->getOpenSSLX509())); + ensure("x509 is equal", !X509_cmp(mX509TestCert, test_cert->getOpenSSLX509())); } @@ -319,7 +431,6 @@ namespace tut void sechandler_basic_test_object::test<3>() { LLPointer<LLSecAPIBasicHandler> handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); - LLSD my_id = LLSD::emptyMap(); LLSD my_authenticator = LLSD::emptyMap(); @@ -349,7 +460,7 @@ namespace tut // test loading of a credential, that hasn't been saved, without // any legacy saved credential data - LLPointer<LLCredential> my_new_cred = handler->loadCredential("my_grid"); + LLPointer<LLCredential> my_new_cred = handler->loadCredential("my_grid2"); ensure("unknown credential load test", my_new_cred->getIdentifier().isMap()); ensure("unknown credential load test", !my_new_cred->getIdentifier().has("type")); ensure("unknown credential load test", my_new_cred->getAuthenticator().isMap()); @@ -379,10 +490,8 @@ namespace tut // test loading of an unknown credential with legacy saved username, but without // saved password - - gSavedSettings.setString("FirstName", "myfirstname"); - gSavedSettings.setString("LastName", "mylastname"); - + gFirstName = "myfirstname"; + gLastName = "mylastname"; my_new_cred = handler->loadCredential("my_legacy_grid"); ensure_equals("legacy credential with no password: type", (const std::string)my_new_cred->getIdentifier()["type"], "agent"); @@ -438,19 +547,413 @@ namespace tut ensure("no authenticator values were saved", my_new_cred->getAuthenticator().isUndefined()); } + // test cert vector + template<> template<> + void sechandler_basic_test_object::test<4>() + { + + // validate create from empty vector + LLPointer<LLBasicCertificateVector> test_vector = new LLBasicCertificateVector(); + ensure_equals("when loading with nothing, we should result in no certs in vector", test_vector->size(), 0); + + test_vector->add(new LLBasicCertificate(mPemTestCert)); + ensure_equals("one element in vector", test_vector->size(), 1); + test_vector->add(new LLBasicCertificate(mPemChildCert)); + ensure_equals("two elements in vector after add", test_vector->size(), 2); + + test_vector->add(new LLBasicCertificate(mPemChildCert)); + ensure_equals("two elements in vector after re-add", test_vector->size(), 2); + // validate order + X509* test_cert = (*test_vector)[0]->getOpenSSLX509(); + ensure("first cert added remains first cert", !X509_cmp(test_cert, mX509TestCert)); + X509_free(test_cert); + + test_cert = (*test_vector)[1]->getOpenSSLX509(); + ensure("adding a duplicate cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // + // validate iterator + // + LLBasicCertificateVector::iterator current_cert = test_vector->begin(); + LLBasicCertificateVector::iterator copy_current_cert = current_cert; + // operator++(int) + ensure("validate iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[0]); + ensure("validate 2nd iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[1]); + ensure("validate end iterator++", current_cert == test_vector->end()); + + // copy + ensure("validate copy iterator element in vector is expected cert", *copy_current_cert == (*test_vector)[0]); + + // operator--(int) + current_cert--; + ensure("validate iterator-- element in vector is expected cert", *current_cert-- == (*test_vector)[1]); + ensure("validate iterator-- element in vector is expected cert", *current_cert == (*test_vector)[0]); + + ensure("begin iterator is equal", current_cert == test_vector->begin()); + + // operator++ + ensure("validate ++iterator element in vector is expected cert", *++current_cert == (*test_vector)[1]); + ensure("end of cert vector after ++iterator", ++current_cert == test_vector->end()); + // operator-- + ensure("validate --iterator element in vector is expected cert", *--current_cert == (*test_vector)[1]); + ensure("validate 2nd --iterator element in vector is expected cert", *--current_cert == (*test_vector)[0]); + + // validate remove + // validate create from empty vector + test_vector = new LLBasicCertificateVector(); + test_vector->add(new LLBasicCertificate(mPemTestCert)); + test_vector->add(new LLBasicCertificate(mPemChildCert)); + test_vector->erase(test_vector->begin()); + ensure_equals("one element in store after remove", test_vector->size(), 1); + test_cert = (*test_vector)[0]->getOpenSSLX509(); + ensure("validate cert was removed", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // validate insert + test_vector->insert(test_vector->begin(), new LLBasicCertificate(mPemChildCert)); + test_cert = (*test_vector)[0]->getOpenSSLX509(); + + ensure("validate cert was inserted", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + //validate find + LLSD find_info = LLSD::emptyMap(); + test_vector->insert(test_vector->begin(), new LLBasicCertificate(mPemRootCert)); + find_info["issuer_name"] = LLSD::emptyMap(); + find_info["issuer_name"]["commonName"] = "Roxies intermediate CA"; + find_info["md5_digest"] = "97:24:c7:4c:d4:ba:2d:0e:9c:a1:18:8e:3a:c6:1f:c3"; + current_cert = test_vector->find(find_info); + ensure("found", current_cert != test_vector->end()); + ensure("found cert", (*current_cert).get() == (*test_vector)[1].get()); + find_info["sha1_digest"] = "bad value"; + current_cert =test_vector->find(find_info); + ensure("didn't find cert", current_cert == test_vector->end()); + } + // test cert store template<> template<> - void sechandler_basic_test_object::test<4>() + void sechandler_basic_test_object::test<5>() { + // validate load with nothing + LLFile::remove("mycertstore.pem"); + LLPointer<LLBasicCertificateStore> test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); + + // validate load with empty file + test_store->save(); + test_store = NULL; + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); + test_store=NULL; + // instantiate a cert store from a file - llofstream certstorefile("mycertstore.pem", std::ios::out | std::ios::binary); - - certstorefile << mPemTestCert; + llofstream certstorefile("mycertstore.pem", std::ios::out); + certstorefile << mPemChildCert << std::endl << mPemTestCert << std::endl; certstorefile.close(); - // LLBasicCertificateStore test_store("mycertstore.pem"); - // X509* test_cert = test_store[0]->getOpenSSLX509(); + // validate loaded certs + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("two elements in store", test_store->size(), 2); + + // operator[] + X509* test_cert = (*test_store)[0]->getOpenSSLX509(); + + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_store)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); + X509_free(test_cert); + + + // validate save + LLFile::remove("mycertstore.pem"); + test_store->save(); + test_store = NULL; + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("two elements in store after save", test_store->size(), 2); + LLCertificateStore::iterator current_cert = test_store->begin(); + test_cert = (*current_cert)->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + current_cert++; + X509_free(test_cert); + test_cert = (*current_cert)->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); + X509_free(test_cert); + current_cert++; + ensure("end of cert store", current_cert == test_store->end()); + + } + + // cert name wildcard matching + template<> template<> + void sechandler_basic_test_object::test<6>() + { + ensure("simple name match", + _cert_hostname_wildcard_match("foo", "foo")); + + ensure("simple name match, with end period", + _cert_hostname_wildcard_match("foo.", "foo.")); + + ensure("simple name match, with begin period", + _cert_hostname_wildcard_match(".foo", ".foo")); + + ensure("simple name match, with subdomain", + _cert_hostname_wildcard_match("foo.bar", "foo.bar")); + + ensure("stutter name match", + _cert_hostname_wildcard_match("foobbbbfoo", "foo*bbbfoo")); + + ensure("simple name match, with beginning wildcard", + _cert_hostname_wildcard_match("foobar", "*bar")); + + ensure("simple name match, with ending wildcard", + _cert_hostname_wildcard_match("foobar", "foo*")); + + ensure("simple name match, with beginning null wildcard", + _cert_hostname_wildcard_match("foobar", "*foobar")); + + ensure("simple name match, with ending null wildcard", + _cert_hostname_wildcard_match("foobar", "foobar*")); + + ensure("simple name match, with embedded wildcard", + _cert_hostname_wildcard_match("foobar", "f*r")); + + ensure("simple name match, with embedded null wildcard", + _cert_hostname_wildcard_match("foobar", "foo*bar")); + + ensure("simple name match, with dual embedded wildcard", + _cert_hostname_wildcard_match("foobar", "f*o*ar")); + + ensure("simple name mismatch", + !_cert_hostname_wildcard_match("bar", "foo")); + + ensure("simple name mismatch, with end period", + !_cert_hostname_wildcard_match("foobar.", "foo.")); + + ensure("simple name mismatch, with begin period", + !_cert_hostname_wildcard_match(".foobar", ".foo")); + + ensure("simple name mismatch, with subdomain", + !_cert_hostname_wildcard_match("foobar.bar", "foo.bar")); + + ensure("simple name mismatch, with beginning wildcard", + !_cert_hostname_wildcard_match("foobara", "*bar")); + + ensure("simple name mismatch, with ending wildcard", + !_cert_hostname_wildcard_match("oobar", "foo*")); + + ensure("simple name mismatch, with embedded wildcard", + !_cert_hostname_wildcard_match("oobar", "f*r")); + + ensure("simple name mismatch, with dual embedded wildcard", + !_cert_hostname_wildcard_match("foobar", "f*d*ar")); + + ensure("simple wildcard", + _cert_hostname_wildcard_match("foobar", "*")); + + ensure("long domain", + _cert_hostname_wildcard_match("foo.bar.com", "foo.bar.com")); + + ensure("long domain with multiple wildcards", + _cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com")); - // ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); + ensure("end periods", + _cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com.")); + + ensure("mismatch end period", + !_cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com")); + + ensure("mismatch end period2", + !_cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com.")); + } + + // test cert chain + template<> template<> + void sechandler_basic_test_object::test<7>() + { + // validate create from empty chain + LLPointer<LLBasicCertificateChain> test_chain = new LLBasicCertificateChain(NULL); + ensure_equals("when loading with nothing, we should result in no certs in chain", test_chain->size(), 0); + + // Single cert in the chain. + X509_STORE_CTX *test_store = X509_STORE_CTX_new(); + test_store->cert = mX509ChildCert; + test_store->untrusted = NULL; + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 1); + X509* test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // cert + CA + + test_store = X509_STORE_CTX_new(); + test_store->cert = mX509ChildCert; + test_store->untrusted = sk_X509_new_null(); + sk_X509_push(test_store->untrusted, mX509IntermediateCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 2); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_chain)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + X509_free(test_cert); + + // cert + nonrelated + + test_store = X509_STORE_CTX_new(); + test_store->cert = mX509ChildCert; + test_store->untrusted = sk_X509_new_null(); + sk_X509_push(test_store->untrusted, mX509TestCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 1); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // cert + CA + nonrelated + test_store = X509_STORE_CTX_new(); + test_store->cert = mX509ChildCert; + test_store->untrusted = sk_X509_new_null(); + sk_X509_push(test_store->untrusted, mX509IntermediateCert); + sk_X509_push(test_store->untrusted, mX509TestCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 2); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_chain)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + X509_free(test_cert); + + // cert + intermediate + CA + test_store = X509_STORE_CTX_new(); + test_store->cert = mX509ChildCert; + test_store->untrusted = sk_X509_new_null(); + sk_X509_push(test_store->untrusted, mX509IntermediateCert); + sk_X509_push(test_store->untrusted, mX509RootCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("three elements in store", test_chain->size(), 3); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_chain)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + X509_free(test_cert); + + test_cert = (*test_chain)[2]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509RootCert)); + X509_free(test_cert); + } + // test cert validation + template<> template<> + void sechandler_basic_test_object::test<8>() + { + // start with a trusted store with our known root cert + LLFile::remove("mycertstore.pem"); + LLPointer<LLBasicCertificateStore> test_store = new LLBasicCertificateStore("mycertstore.pem"); + test_store->add(new LLBasicCertificate(mX509RootCert)); + LLSD validation_params; + + // validate basic trust for a chain containing only the intermediate cert. (1 deep) + LLPointer<LLBasicCertificateChain> test_chain = new LLBasicCertificateChain(NULL); + + test_chain->add(new LLBasicCertificate(mX509IntermediateCert)); + + test_chain->validate(0, test_store, validation_params); + + // add the root certificate to the chain and revalidate + test_chain->add(new LLBasicCertificate(mX509RootCert)); + test_chain->validate(0, test_store, validation_params); + + // add the child cert at the head of the chain, and revalidate (3 deep chain) + test_chain->insert(test_chain->begin(), new LLBasicCertificate(mX509ChildCert)); + test_chain->validate(0, test_store, validation_params); + + // basic failure cases + test_chain = new LLBasicCertificateChain(NULL); + //validate with only the child cert + test_chain->add(new LLBasicCertificate(mX509ChildCert)); + ensure_throws("no CA, with only a child cert", + LLCertValidationTrustException, + (*test_chain)[0], + test_chain->validate, + VALIDATION_POLICY_TRUSTED, + test_store, + validation_params); + + + // validate without the trust flag. + test_chain->validate(0, test_store, validation_params); + + // clear out the store + test_store = new LLBasicCertificateStore("mycertstore.pem"); + // append the intermediate cert + test_chain->add(new LLBasicCertificate(mX509IntermediateCert)); + ensure_throws("no CA, with child and intermediate certs", + LLCertValidationTrustException, + (*test_chain)[1], + test_chain->validate, + VALIDATION_POLICY_TRUSTED, + test_store, + validation_params); + // validate without the trust flag + test_chain->validate(0, test_store, validation_params); + + // Test time validity + LLSD child_info = (*test_chain)[0]->getLLSD(); + validation_params = LLSD::emptyMap(); + validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() + 1.0); + test_chain->validate(VALIDATION_POLICY_TIME, test_store, validation_params); + + validation_params = LLSD::emptyMap(); + validation_params[CERT_VALIDATION_DATE] = child_info[CERT_VALID_FROM].asDate(); + + validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() - 1.0); + + // test not yet valid + ensure_throws("Child cert not yet valid", + LLCertValidationExpirationException, + (*test_chain)[0], + test_chain->validate, + VALIDATION_POLICY_TIME, + test_store, + validation_params); + validation_params = LLSD::emptyMap(); + validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_TO].asDate().secondsSinceEpoch() + 1.0); + + // test cert expired + ensure_throws("Child cert expired", + LLCertValidationExpirationException, + (*test_chain)[0], + test_chain->validate, + VALIDATION_POLICY_TIME, + test_store, + validation_params); + + // test SSL KU + // validate basic trust for a chain containing child and intermediate. + test_chain = new LLBasicCertificateChain(NULL); + test_chain->add(new LLBasicCertificate(mX509ChildCert)); + test_chain->add(new LLBasicCertificate(mX509IntermediateCert)); + test_chain->validate(VALIDATION_POLICY_SSL_KU, test_store, validation_params); + + test_chain = new LLBasicCertificateChain(NULL); + test_chain->add(new LLBasicCertificate(mX509TestCert)); + + ensure_throws("Cert doesn't have ku", + LLCertKeyUsageValidationException, + (*test_chain)[0], + test_chain->validate, + VALIDATION_POLICY_SSL_KU, + test_store, + validation_params); } }; + diff --git a/indra/newview/tests/llviewernetwork_test.cpp b/indra/newview/tests/llviewernetwork_test.cpp index c7a6b2ad15..f341482d6f 100644 --- a/indra/newview/tests/llviewernetwork_test.cpp +++ b/indra/newview/tests/llviewernetwork_test.cpp @@ -37,7 +37,51 @@ #include "../../llxml/llcontrol.h" #include "llfile.h" -LLControlGroup gSavedSettings; +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker<LLControlGroup, std::string>(name) {} +LLControlGroup::~LLControlGroup() {} +BOOL LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + BOOL persist) {return TRUE;} +void LLControlGroup::setString(const std::string& name, const std::string& val){} + +std::string gCmdLineLoginURI; +std::string gCmdLineGridChoice; +std::string gCmdLineHelperURI; +std::string gLoginPage; +std::string gCurrentGrid; +std::string LLControlGroup::getString(const std::string& name) +{ + if (name == "CmdLineGridChoice") + return gCmdLineGridChoice; + else if (name == "CmdLineHelperURI") + return gCmdLineHelperURI; + else if (name == "LoginPage") + return gLoginPage; + else if (name == "CurrentGrid") + return gCurrentGrid; + return ""; +} + +LLSD LLControlGroup::getLLSD(const std::string& name) +{ + if (name == "CmdLineLoginURI") + { + if(!gCmdLineLoginURI.empty()) + { + return LLSD(gCmdLineLoginURI); + } + } + return LLSD(); +} + + +LLControlGroup gSavedSettings("test"); + const char *gSampleGridFile = "<llsd><map>" "<key>grid1</key><map>" " <key>favorite</key><integer>1</integer>" @@ -68,13 +112,12 @@ namespace tut { viewerNetworkTest() { - gSavedSettings.cleanup(); - gSavedSettings.cleanup(); - gSavedSettings.declareString("CmdLineGridChoice", "", "", FALSE); - gSavedSettings.declareString("CmdLineHelperURI", "", "", FALSE); - gSavedSettings.declareString("LoginPage", "", "", FALSE); - gSavedSettings.declareString("CurrentGrid", "", "", FALSE); - gSavedSettings.declareLLSD("CmdLineLoginURI", LLSD(), "", FALSE); + LLFile::remove("grid_test.xml"); + gCmdLineLoginURI.clear(); + gCmdLineGridChoice.clear(); + gCmdLineHelperURI.clear(); + gLoginPage.clear(); + gCurrentGrid.clear(); } ~viewerNetworkTest() { @@ -95,20 +138,25 @@ namespace tut void viewerNetworkTestObject::test<1>() { - LLGridManager manager("grid_test.xml"); + LLGridManager *manager = LLGridManager::getInstance(); + // grid file doesn't exist + manager->initialize("grid_test.xml"); // validate that some of the defaults are available. - std::map<std::string, std::string> known_grids = manager.getKnownGrids(); + std::map<std::string, std::string> known_grids = manager->getKnownGrids(); #ifndef LL_RELEASE_FOR_DOWNLOAD ensure_equals("Known grids is a string-string map of size 18", known_grids.size(), 18); + ensure_equals("Agni has the right name and label", + known_grids[std::string("util.agni.lindenlab.com")], std::string("Agni")); #else // LL_RELEASE_FOR_DOWNLOAD - ensure_equals("Known grids is a string-string map of size 18", known_grids.size(), 2); + ensure_equals("Known grids is a string-string map of size 2", known_grids.size(), 2); + ensure_equals("Agni has the right name and label", + known_grids[std::string("util.agni.lindenlab.com")], std::string("Secondlife.com")); #endif // LL_RELEASE_FOR_DOWNLOAD - ensure_equals("Agni has the right name and label", - known_grids[std::string("util.agni.lindenlab.com")], std::string("Agni")); + ensure_equals("None exists", known_grids[""], "None"); - LLSD grid = manager.getGridInfo("util.agni.lindenlab.com"); + LLSD grid = LLGridManager::getInstance()->getGridInfo("util.agni.lindenlab.com"); ensure("Grid info for agni is a map", grid.isMap()); ensure_equals("name is correct for agni", grid[GRID_NAME_VALUE].asString(), std::string("util.agni.lindenlab.com")); @@ -130,8 +178,8 @@ namespace tut ensure_equals("Agni login page is correct", grid[GRID_LOGIN_PAGE_VALUE].asString(), std::string("http://secondlife.com/app/login/")); - ensure("Agni is not a favorite", - !grid.has(GRID_IS_FAVORITE_VALUE)); + ensure("Agni is a favorite", + grid.has(GRID_IS_FAVORITE_VALUE)); ensure("Agni is a system grid", grid.has(GRID_IS_SYSTEM_GRID_VALUE)); ensure("Grid file wasn't greated as it wasn't saved", @@ -146,20 +194,25 @@ namespace tut gridfile << gSampleGridFile; gridfile.close(); - LLGridManager manager("grid_test.xml"); - std::map<std::string, std::string> known_grids = manager.getKnownGrids(); + LLGridManager::getInstance()->initialize("grid_test.xml"); + std::map<std::string, std::string> known_grids = LLGridManager::getInstance()->getKnownGrids(); #ifndef LL_RELEASE_FOR_DOWNLOAD ensure_equals("adding a grid via a grid file increases known grid size", known_grids.size(), 19); + ensure_equals("Agni is still there after we've added a grid via a grid file", + known_grids["util.agni.lindenlab.com"], std::string("Agni")); + #else ensure_equals("adding a grid via a grid file increases known grid size", known_grids.size(), 3); -#endif ensure_equals("Agni is still there after we've added a grid via a grid file", - known_grids["util.agni.lindenlab.com"], std::string("Agni")); + known_grids["util.agni.lindenlab.com"], std::string("Secondlife.com")); + +#endif + // assure Agni doesn't get overwritten - LLSD grid = manager.getGridInfo("util.agni.lindenlab.com"); + LLSD grid = LLGridManager::getInstance()->getGridInfo("util.agni.lindenlab.com"); #ifndef LL_RELEASE_FOR_DOWNLOAD ensure_equals("Agni grid label was not modified by grid file", grid[GRID_LABEL_VALUE].asString(), std::string("Agni")); @@ -181,14 +234,14 @@ namespace tut ensure_equals("Agni login page the same after grid file", grid[GRID_LOGIN_PAGE_VALUE].asString(), std::string("http://secondlife.com/app/login/")); - ensure("Agni still not favorite after grid file", - !grid.has(GRID_IS_FAVORITE_VALUE)); + ensure("Agni still a favorite after grid file", + grid.has(GRID_IS_FAVORITE_VALUE)); ensure("Agni system grid still set after grid file", grid.has(GRID_IS_SYSTEM_GRID_VALUE)); ensure_equals("Grid file adds to name<->label map", known_grids["grid1"], std::string("mylabel")); - grid = manager.getGridInfo("grid1"); + grid = LLGridManager::getInstance()->getGridInfo("grid1"); ensure_equals("grid file grid name is set", grid[GRID_NAME_VALUE].asString(), std::string("grid1")); ensure_equals("grid file label is set", @@ -217,22 +270,16 @@ namespace tut template<> template<> void viewerNetworkTestObject::test<3>() { - LLSD loginURI = std::string("https://my.login.uri/cgi-bin/login.cgi"); - gSavedSettings.setLLSD("CmdLineLoginURI", loginURI); - LLGridManager manager("grid_test.xml"); + gCmdLineLoginURI = "https://my.login.uri/cgi-bin/login.cgi"; + LLGridManager::getInstance()->initialize("grid_test.xml"); // with single login uri specified. - std::map<std::string, std::string> known_grids = manager.getKnownGrids(); -#ifndef LL_RELEASE_FOR_DOWNLOAD + std::map<std::string, std::string> known_grids = LLGridManager::getInstance()->getKnownGrids(); ensure_equals("adding a command line grid increases known grid size", known_grids.size(), 19); -#else - ensure_equals("adding a command line grid increases known grid size", - known_grids.size(), 3); -#endif ensure_equals("Command line grid is added to the list of grids", known_grids["my.login.uri"], std::string("my.login.uri")); - LLSD grid = manager.getGridInfo("my.login.uri"); + LLSD grid = LLGridManager::getInstance()->getGridInfo("my.login.uri"); ensure_equals("Command line grid name is set", grid[GRID_NAME_VALUE].asString(), std::string("my.login.uri")); ensure_equals("Command line grid label is set", @@ -254,19 +301,14 @@ namespace tut !grid.has(GRID_IS_SYSTEM_GRID_VALUE)); // now try a command line with a custom grid identifier - gSavedSettings.setString("CmdLineGridChoice", "mycustomgridchoice"); - manager = LLGridManager("grid_test.xml"); - known_grids = manager.getKnownGrids(); -#ifndef LL_RELEASE_FOR_DOWNLOAD + gCmdLineGridChoice = "mycustomgridchoice"; + LLGridManager::getInstance()->initialize("grid_test.xml"); + known_grids = LLGridManager::getInstance()->getKnownGrids(); ensure_equals("adding a command line grid with custom name increases known grid size", known_grids.size(), 19); -#else - ensure_equals("adding a command line grid with custom name inceases known grid size", - known_grids.size(), 3); -#endif ensure_equals("Custom Command line grid is added to the list of grids", known_grids["mycustomgridchoice"], std::string("mycustomgridchoice")); - grid = manager.getGridInfo("mycustomgridchoice"); + grid = LLGridManager::getInstance()->getGridInfo("mycustomgridchoice"); ensure_equals("Custom Command line grid name is set", grid[GRID_NAME_VALUE].asString(), std::string("mycustomgridchoice")); ensure_equals("Custom Command line grid label is set", @@ -278,16 +320,16 @@ namespace tut std::string("https://my.login.uri/cgi-bin/login.cgi")); // add a helperuri - gSavedSettings.setString("CmdLineHelperURI", "myhelperuri"); - manager = LLGridManager("grid_test.xml"); - grid = manager.getGridInfo("mycustomgridchoice"); + gCmdLineHelperURI = "myhelperuri"; + LLGridManager::getInstance()->initialize("grid_test.xml"); + grid = LLGridManager::getInstance()->getGridInfo("mycustomgridchoice"); ensure_equals("Validate command line helper uri", grid[GRID_HELPER_URI_VALUE].asString(), std::string("myhelperuri")); // add a login page - gSavedSettings.setString("LoginPage", "myloginpage"); - manager = LLGridManager("grid_test.xml"); - grid = manager.getGridInfo("mycustomgridchoice"); + gLoginPage = "myloginpage"; + LLGridManager::getInstance()->initialize("grid_test.xml"); + grid = LLGridManager::getInstance()->getGridInfo("mycustomgridchoice"); ensure_equals("Validate command line helper uri", grid[GRID_LOGIN_PAGE_VALUE].asString(), std::string("myloginpage")); } @@ -301,36 +343,34 @@ namespace tut // adding a grid with simply a name will populate the values. grid[GRID_NAME_VALUE] = "myaddedgrid"; - loginURI.append(std::string("https://my.login.uri/cgi-bin/login.cgi")); - gSavedSettings.setLLSD("CmdLineLoginURI", loginURI); - LLGridManager manager("grid_test.xml"); - manager.addGrid(grid); - manager.setGridChoice("util.agni.lindenlab.com"); + LLGridManager::getInstance()->initialize("grid_test.xml"); + LLGridManager::getInstance()->addGrid(grid); + LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); #ifndef LL_RELEASE_FOR_DOWNLOAD - ensure_equals("getGridLabel", manager.getGridLabel(), std::string("Agni")); + ensure_equals("getGridLabel", LLGridManager::getInstance()->getGridLabel(), std::string("Agni")); #else // LL_RELEASE_FOR_DOWNLOAD - ensure_equals("getGridLabel", manager.getGridLabel(), std::string("Secondlife.com")); + ensure_equals("getGridLabel", LLGridManager::getInstance()->getGridLabel(), std::string("Secondlife.com")); #endif // LL_RELEASE_FOR_DOWNLOAD - ensure_equals("getGridName", manager.getGridName(), + ensure_equals("getGridName", LLGridManager::getInstance()->getGridName(), std::string("util.agni.lindenlab.com")); - ensure_equals("getHelperURI", manager.getHelperURI(), + ensure_equals("getHelperURI", LLGridManager::getInstance()->getHelperURI(), std::string("https://secondlife.com/helpers/")); - ensure_equals("getLoginPage", manager.getLoginPage(), + ensure_equals("getLoginPage", LLGridManager::getInstance()->getLoginPage(), std::string("http://secondlife.com/app/login/")); - ensure_equals("getLoginPage2", manager.getLoginPage("util.agni.lindenlab.com"), + ensure_equals("getLoginPage2", LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com"), std::string("http://secondlife.com/app/login/")); - ensure("Is Agni a production grid", manager.isInProductionGrid()); + ensure("Is Agni a production grid", LLGridManager::getInstance()->isInProductionGrid()); std::vector<std::string> uris; - manager.getLoginURIs(uris); + LLGridManager::getInstance()->getLoginURIs(uris); ensure_equals("getLoginURIs size", uris.size(), 1); ensure_equals("getLoginURIs", uris[0], std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); - manager.setGridChoice("myaddedgrid"); - ensure_equals("getGridLabel", manager.getGridLabel(), std::string("myaddedgrid")); - ensure("Is myaddedgrid a production grid", !manager.isInProductionGrid()); + LLGridManager::getInstance()->setGridChoice("myaddedgrid"); + ensure_equals("getGridLabel", LLGridManager::getInstance()->getGridLabel(), std::string("myaddedgrid")); + ensure("Is myaddedgrid a production grid", !LLGridManager::getInstance()->isInProductionGrid()); - manager.setFavorite(); - grid = manager.getGridInfo("myaddedgrid"); + LLGridManager::getInstance()->setFavorite(); + grid = LLGridManager::getInstance()->getGridInfo("myaddedgrid"); ensure("setting favorite", grid.has(GRID_IS_FAVORITE_VALUE)); } @@ -338,12 +378,12 @@ namespace tut template<> template<> void viewerNetworkTestObject::test<5>() { - LLGridManager manager("grid_test.xml"); + LLGridManager::getInstance()->initialize("grid_test.xml"); LLSD grid = LLSD::emptyMap(); // adding a grid with simply a name will populate the values. grid[GRID_NAME_VALUE] = "myaddedgrid"; - manager.addGrid(grid); - grid = manager.getGridInfo("myaddedgrid"); + LLGridManager::getInstance()->addGrid(grid); + grid = LLGridManager::getInstance()->getGridInfo("myaddedgrid"); ensure_equals("name based grid has name value", grid[GRID_NAME_VALUE].asString(), @@ -380,29 +420,29 @@ namespace tut { // try with initial grid list without a grid file, // without setting the grid to a saveable favorite. - LLGridManager manager("grid_test.xml"); + LLGridManager::getInstance()->initialize("grid_test.xml"); LLSD grid = LLSD::emptyMap(); grid[GRID_NAME_VALUE] = std::string("mynewgridname"); - manager.addGrid(grid); - manager.saveFavorites(); + LLGridManager::getInstance()->addGrid(grid); + LLGridManager::getInstance()->saveFavorites(); ensure("Grid file exists after saving", LLFile::isfile("grid_test.xml")); - manager = LLGridManager("grid_test.xml"); + LLGridManager::getInstance()->initialize("grid_test.xml"); // should not be there - std::map<std::string, std::string> known_grids = manager.getKnownGrids(); + std::map<std::string, std::string> known_grids = LLGridManager::getInstance()->getKnownGrids(); ensure("New grid wasn't added to persisted list without being marked a favorite", known_grids.find(std::string("mynewgridname")) == known_grids.end()); // mark a grid a favorite to make sure it's persisted - manager.addGrid(grid); - manager.setGridChoice("mynewgridname"); - manager.setFavorite(); - manager.saveFavorites(); + LLGridManager::getInstance()->addGrid(grid); + LLGridManager::getInstance()->setGridChoice("mynewgridname"); + LLGridManager::getInstance()->setFavorite(); + LLGridManager::getInstance()->saveFavorites(); ensure("Grid file exists after saving", LLFile::isfile("grid_test.xml")); - manager = LLGridManager("grid_test.xml"); + LLGridManager::getInstance()->initialize("grid_test.xml"); // should not be there - known_grids = manager.getKnownGrids(); + known_grids = LLGridManager::getInstance()->getKnownGrids(); ensure("New grid wasn't added to persisted list after being marked a favorite", known_grids.find(std::string("mynewgridname")) != known_grids.end()); @@ -417,28 +457,28 @@ namespace tut gridfile << gSampleGridFile; gridfile.close(); - LLGridManager manager("grid_test.xml"); + LLGridManager::getInstance()->initialize("grid_test.xml"); LLSD grid = LLSD::emptyMap(); grid[GRID_NAME_VALUE] = std::string("mynewgridname"); - manager.addGrid(grid); - manager.saveFavorites(); + LLGridManager::getInstance()->addGrid(grid); + LLGridManager::getInstance()->saveFavorites(); // validate we didn't lose existing favorites - manager = LLGridManager("grid_test.xml"); - std::map<std::string, std::string> known_grids = manager.getKnownGrids(); + LLGridManager::getInstance()->initialize("grid_test.xml"); + std::map<std::string, std::string> known_grids = LLGridManager::getInstance()->getKnownGrids(); ensure("New grid wasn't added to persisted list after being marked a favorite", known_grids.find(std::string("grid1")) != known_grids.end()); // add a grid - manager.addGrid(grid); - manager.setGridChoice("mynewgridname"); - manager.setFavorite(); - manager.saveFavorites(); - known_grids = manager.getKnownGrids(); + LLGridManager::getInstance()->addGrid(grid); + LLGridManager::getInstance()->setGridChoice("mynewgridname"); + LLGridManager::getInstance()->setFavorite(); + LLGridManager::getInstance()->saveFavorites(); + known_grids = LLGridManager::getInstance()->getKnownGrids(); ensure("New grid wasn't added to persisted list after being marked a favorite", known_grids.find(std::string("grid1")) != known_grids.end()); - known_grids = manager.getKnownGrids(); + known_grids = LLGridManager::getInstance()->getKnownGrids(); ensure("New grid wasn't added to persisted list after being marked a favorite", known_grids.find(std::string("mynewgridname")) != known_grids.end()); |