Multi-line svn commit, see body.
authorScott Cantor <cantor.2@osu.edu>
Tue, 28 Oct 2008 17:50:00 +0000 (17:50 +0000)
committerScott Cantor <cantor.2@osu.edu>
Tue, 28 Oct 2008 17:50:00 +0000 (17:50 +0000)
Initial set of helper functions to handle key/cert loading.
Additional unit tests and data.

.cproject
xmltooling/Makefile.am
xmltooling/security/SecurityHelper.h [new file with mode: 0644]
xmltooling/security/impl/SecurityHelper.cpp [new file with mode: 0644]
xmltoolingtest/Makefile.am
xmltoolingtest/SecurityHelperTest.h [new file with mode: 0644]
xmltoolingtest/data/cert.der [new file with mode: 0644]
xmltoolingtest/data/key.der [new file with mode: 0644]
xmltoolingtest/data/test.pfx [new file with mode: 0644]

index 2473079..b78749e 100644 (file)
--- a/.cproject
+++ b/.cproject
@@ -61,6 +61,7 @@
 <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>\r
 <storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>\r
 \r
 <storageModule moduleId="org.eclipse.cdt.core.externalSettings"/>\r
 <storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/>\r
 \r
+\r
 <storageModule moduleId="org.eclipse.cdt.core.pathentry">\r
 <pathentry include="C:/log4shib-1.0/include" kind="inc" path="" system="true"/>\r
 <pathentry include="C:/xerces-c_2_8_0-x86-windows-vc_8_0/include" kind="inc" path="" system="true"/>\r
 <storageModule moduleId="org.eclipse.cdt.core.pathentry">\r
 <pathentry include="C:/log4shib-1.0/include" kind="inc" path="" system="true"/>\r
 <pathentry include="C:/xerces-c_2_8_0-x86-windows-vc_8_0/include" kind="inc" path="" system="true"/>\r
@@ -73,6 +74,8 @@
 <pathentry include="C:/Program Files/Microsoft Visual Studio 8/VC/include" kind="inc" path="" system="true"/>\r
 <pathentry kind="mac" name="_MSC_VER" path="" value=""/>\r
 <pathentry kind="mac" name="WIN32" path="" value=""/>\r
 <pathentry include="C:/Program Files/Microsoft Visual Studio 8/VC/include" kind="inc" path="" system="true"/>\r
 <pathentry kind="mac" name="_MSC_VER" path="" value=""/>\r
 <pathentry kind="mac" name="WIN32" path="" value=""/>\r
+<pathentry kind="mac" name="XMLTOOL_API" path="" value=""/>\r
+<pathentry kind="mac" name="XMLTOOL_DLLLOCAL" path="" value=""/>\r
 <pathentry kind="out" path="xmltooling/Debug"/>\r
 <pathentry kind="out" path="xmltoolingtest/Debug"/>\r
 <pathentry kind="out" path="debug"/>\r
 <pathentry kind="out" path="xmltooling/Debug"/>\r
 <pathentry kind="out" path="xmltoolingtest/Debug"/>\r
 <pathentry kind="out" path="debug"/>\r
index a580e53..87a5bae 100644 (file)
@@ -77,6 +77,7 @@ secinclude_HEADERS = \
        security/KeyInfoCredentialContext.h \
        security/KeyInfoResolver.h \
        security/OpenSSLCredential.h \
        security/KeyInfoCredentialContext.h \
        security/KeyInfoResolver.h \
        security/OpenSSLCredential.h \
+       security/SecurityHelper.h \
        security/SignatureTrustEngine.h \
        security/TrustEngine.h \
        security/X509Credential.h \
        security/SignatureTrustEngine.h \
        security/TrustEngine.h \
        security/X509Credential.h \
@@ -141,6 +142,7 @@ xmlsec_sources = \
        security/impl/InlineKeyResolver.cpp \
        security/impl/KeyInfoResolver.cpp \
        security/impl/OpenSSLCryptoX509CRL.cpp \
        security/impl/InlineKeyResolver.cpp \
        security/impl/KeyInfoResolver.cpp \
        security/impl/OpenSSLCryptoX509CRL.cpp \
+       security/impl/SecurityHelper.cpp \
        security/impl/StaticPKIXTrustEngine.cpp \
        security/impl/TrustEngine.cpp \
        security/impl/XSECCryptoX509CRL.cpp \
        security/impl/StaticPKIXTrustEngine.cpp \
        security/impl/TrustEngine.cpp \
        security/impl/XSECCryptoX509CRL.cpp \
diff --git a/xmltooling/security/SecurityHelper.h b/xmltooling/security/SecurityHelper.h
new file mode 100644 (file)
index 0000000..4c0bbf9
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ *  Copyright 2001-2008 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * @file xmltooling/security/SecurityHelper.h
+ *
+ * A helper class for working with keys, certificates, etc.
+ */
+
+#if !defined(__xmltooling_sechelper_h__) && !defined(XMLTOOLING_NO_XMLSEC)
+#define __xmltooling_sechelper_h__
+
+#include <xmltooling/security/XSECCryptoX509CRL.h>
+#include <xmltooling/soap/SOAPTransport.h>
+
+#include <vector>
+#include <xsec/enc/XSECCryptoKey.hpp>
+#include <xsec/enc/XSECCryptoX509.hpp>
+
+namespace xmltooling {
+    /**
+     * A helper class for working with keys, certificates, etc.
+     */
+    class XMLTOOL_API SecurityHelper
+    {
+    public:
+        /**
+         * Loads a private key from a local file.
+         *
+         * @param pathname  path to file containing key
+         * @param format    optional constant identifying key encoding format
+         * @param password  optional password to decrypt key
+         * @return  a populated key object
+         */
+        static XSECCryptoKey* loadKeyFromFile(const char* pathname, const char* format=NULL, const char* password=NULL);
+
+        /**
+         * Loads certificate(s) from a local file.
+         *
+         * @param certs     array to populate with certificate(s)
+         * @param pathname  path to file containing certificate(s)
+         * @param format    optional constant identifying certificate encoding format
+         * @return  size of the resulting array
+         */
+        static std::vector<XSECCryptoX509*>::size_type loadCertificatesFromFile(
+            std::vector<XSECCryptoX509*>& certs, const char* pathname, const char* format=NULL, const char* password=NULL
+            );
+
+        /**
+         * Loads CRL(s) from a local file.
+         *
+         * @param crls      array to populate with CRL(s)
+         * @param pathname  path to file containing CRL(s)
+         * @param format    optional constant identifying CRL encoding format
+         * @return  size of the resulting array
+         */
+        static std::vector<XSECCryptoX509CRL*>::size_type loadCRLsFromFile(
+            std::vector<XSECCryptoX509CRL*>& crls, const char* pathname, const char* format=NULL
+            );
+
+        /**
+         * Loads a private key from a URL.
+         *
+         * @param transport object to use to acquire key
+         * @param backing   backing file for key (written to or read from if download fails)
+         * @param format    optional constant identifying key encoding format
+         * @param password  optional password to decrypt key
+         * @return  a populated key object
+         */
+        static XSECCryptoKey* loadKeyFromURL(SOAPTransport& transport, const char* backing, const char* format=NULL, const char* password=NULL);
+
+        /**
+         * Loads certificate(s) from a URL.
+         *
+         * @param certs     array to populate with certificate(s)
+         * @param transport object to use to acquire certificate(s)
+         * @param backing   backing file for certificate(s) (written to or read from if download fails)
+         * @param format    optional constant identifying certificate encoding format
+         * @return  size of the resulting array
+         */
+        static std::vector<XSECCryptoX509*>::size_type loadCertificatesFromURL(
+            std::vector<XSECCryptoX509*>& certs, SOAPTransport& transport, const char* backing, const char* format=NULL, const char* password=NULL
+            );
+
+        /**
+         * Loads CRL(s) from a URL.
+         *
+         * @param crls      array to populate with CRL(s)
+         * @param transport object to use to acquire CRL(s)
+         * @param backing   backing file for CRL(s) (written to or read from if download fails)
+         * @param format    optional constant identifying CRL encoding format
+         * @return  size of the resulting array
+         */
+        static std::vector<XSECCryptoX509CRL*>::size_type loadCRLsFromURL(
+            std::vector<XSECCryptoX509CRL*>& crls, SOAPTransport& transport, const char* backing, const char* format=NULL
+            );
+
+        /**
+         * Compares two keys for equality.
+         *
+         * @param key1 first key to compare
+         * @param key2 second key to compare
+         * @return  true iff the keys match
+         */
+        static bool matches(const XSECCryptoKey* key1, const XSECCryptoKey* key2);
+    };
+};
+
+#endif /* __xmltooling_sechelper_h__ */
diff --git a/xmltooling/security/impl/SecurityHelper.cpp b/xmltooling/security/impl/SecurityHelper.cpp
new file mode 100644 (file)
index 0000000..5c39bf8
--- /dev/null
@@ -0,0 +1,436 @@
+/*
+ *  Copyright 2001-2008 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * SecurityHelper.cpp
+ *
+ * A helper class for working with keys, certificates, etc.
+ */
+
+#include "internal.h"
+#include "logging.h"
+#include "security/OpenSSLCryptoX509CRL.h"
+#include "security/SecurityHelper.h"
+#include "util/NDC.h"
+
+#include <fstream>
+#include <openssl/pem.h>
+#include <openssl/pkcs12.h>
+#include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
+#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
+#include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
+
+using namespace xmltooling::logging;
+using namespace xmltooling;
+using namespace std;
+
+// OpenSSL password callback...
+static int passwd_callback(char* buf, int len, int verify, void* passwd)
+{
+    if(!verify)
+    {
+        if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
+        {
+            strcpy(buf,reinterpret_cast<char*>(passwd));
+            return strlen(buf);
+        }
+    }
+    return 0;
+}
+
+XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* format, const char* password)
+{
+#ifdef _DEBUG
+    NDC ndc("loadKeyFromFile");
+#endif
+    Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
+    log.info("loading private key from file (%s)", pathname);
+
+    // Native objects.
+    PKCS12* p12=NULL;
+    EVP_PKEY* pkey=NULL;
+
+    BIO* in=BIO_new(BIO_s_file_internal());
+    if (in && BIO_read_filename(in, pathname)>0) {
+        // If the format isn't set, try and guess it.
+        if (!format) {
+            const int READSIZE = 1;
+            char buf[READSIZE];
+            int mark;
+
+            // Examine the first byte.
+            try {
+                if ((mark = BIO_tell(in)) < 0)
+                    throw XMLSecurityException("Error loading key: BIO_tell() can't get the file position.");
+                if (BIO_read(in, buf, READSIZE) <= 0)
+                    throw XMLSecurityException("Error loading key: BIO_read() can't read from the stream.");
+                if (BIO_seek(in, mark) < 0)
+                    throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
+            }
+            catch (exception&) {
+                log_openssl();
+                BIO_free(in);
+                throw;
+            }
+
+            // Check the first byte of the file.  If it's some kind of DER-encoded structure
+            // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
+            if (buf[0] != 48) {
+                format = "PEM";
+            }
+            else {
+                // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
+                // If it fails, must be another kind of DER-encoded structure.
+                if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
+                    format = "DER";
+                    if (BIO_seek(in, mark) < 0) {
+                        log_openssl();
+                        BIO_free(in);
+                        throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
+                    }
+                }
+                else {
+                    format = "PKCS12";
+                }
+            }
+            log.debug("key encoding format for (%s) dynamically resolved as (%s)", pathname, format);
+        }
+
+        // The format should be known, so parse accordingly.
+        if (!strcmp(format, "PEM")) {
+            pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(password));
+        }
+        else if (!strcmp(format, "DER")) {
+            pkey=d2i_PrivateKey_bio(in, NULL);
+        }
+        else if (!strcmp(format, "PKCS12")) {
+            if (!p12)
+                p12 = d2i_PKCS12_bio(in, NULL);
+            if (p12) {
+                X509* x=NULL;
+                PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, NULL);
+                PKCS12_free(p12);
+                X509_free(x);
+            }
+        }
+        else {
+            log.error("unknown key encoding format (%s)", format);
+        }
+    }
+    if (in)
+        BIO_free(in);
+
+    // Now map it to an XSEC wrapper.
+    if (pkey) {
+        XSECCryptoKey* ret=NULL;
+        switch (pkey->type) {
+            case EVP_PKEY_RSA:
+                ret=new OpenSSLCryptoKeyRSA(pkey);
+                break;
+
+            case EVP_PKEY_DSA:
+                ret=new OpenSSLCryptoKeyDSA(pkey);
+                break;
+
+            default:
+                log.error("unsupported private key type");
+        }
+        EVP_PKEY_free(pkey);
+        if (ret)
+            return ret;
+    }
+
+    log_openssl();
+    throw XMLSecurityException("Unable to load private key from file ($1).", params(1, pathname));
+}
+
+vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromFile(
+    vector<XSECCryptoX509*>& certs, const char* pathname, const char* format, const char* password
+    )
+{
+#ifdef _DEBUG
+    NDC ndc("loadCertificatesFromFile");
+#endif
+    Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
+    log.info("loading certificate(s) from file (%s)", pathname);
+
+    vector<XSECCryptoX509*>::size_type count = certs.size();
+
+    // Native objects.
+    X509* x=NULL;
+    PKCS12* p12=NULL;
+
+    BIO* in=BIO_new(BIO_s_file_internal());
+    if (in && BIO_read_filename(in, pathname)>0) {
+        // If the format isn't set, try and guess it.
+        if (!format) {
+            const int READSIZE = 1;
+            char buf[READSIZE];
+            int mark;
+
+            // Examine the first byte.
+            try {
+                if ((mark = BIO_tell(in)) < 0)
+                    throw XMLSecurityException("Error loading certificate: BIO_tell() can't get the file position.");
+                if (BIO_read(in, buf, READSIZE) <= 0)
+                    throw XMLSecurityException("Error loading certificate: BIO_read() can't read from the stream.");
+                if (BIO_seek(in, mark) < 0)
+                    throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
+            }
+            catch (exception&) {
+                log_openssl();
+                BIO_free(in);
+                throw;
+            }
+
+            // Check the first byte of the file.  If it's some kind of DER-encoded structure
+            // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
+            if (buf[0] != 48) {
+                format = "PEM";
+            }
+            else {
+                // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
+                // If it fails, must be another kind of DER-encoded structure.
+                if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
+                    format = "DER";
+                    if (BIO_seek(in, mark) < 0) {
+                        log_openssl();
+                        BIO_free(in);
+                        throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
+                    }
+                }
+                else {
+                    format = "PKCS12";
+                }
+            }
+        }
+
+        // The format should be known, so parse accordingly.
+        if (!strcmp(format, "PEM")) {
+            while (x=PEM_read_bio_X509(in, NULL, NULL, NULL)) {
+                certs.push_back(new OpenSSLCryptoX509(x));
+                X509_free(x);
+            }
+        }
+        else if (!strcmp(format, "DER")) {
+            x=d2i_X509_bio(in, NULL);
+            if (x) {
+                certs.push_back(new OpenSSLCryptoX509(x));
+                X509_free(x);
+            }
+        }
+        else if (!strcmp(format, "PKCS12")) {
+            if (!p12)
+                p12 = d2i_PKCS12_bio(in, NULL);
+            if (p12) {
+                EVP_PKEY* pkey=NULL;
+                STACK_OF(X509)* CAstack = sk_X509_new_null();
+                PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, &CAstack);
+                PKCS12_free(p12);
+                EVP_PKEY_free(pkey);
+                if (x) {
+                    certs.push_back(new OpenSSLCryptoX509(x));
+                    X509_free(x);
+                }
+                x = sk_X509_pop(CAstack);
+                while (x) {
+                    certs.push_back(new OpenSSLCryptoX509(x));
+                    X509_free(x);
+                    x = sk_X509_pop(CAstack);
+                }
+                sk_X509_free(CAstack);
+            }
+        }
+    }
+    if (in)
+        BIO_free(in);
+
+    if (certs.size() == count) {
+        log_openssl();
+        throw XMLSecurityException("Unable to load certificate(s) from file ($1).", params(1, pathname));
+    }
+
+    return certs.size();
+}
+
+vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromFile(
+    vector<XSECCryptoX509CRL*>& crls, const char* pathname, const char* format
+    )
+{
+#ifdef _DEBUG
+    NDC ndc("loadCRLsFromFile");
+#endif
+    Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
+    log.info("loading CRL(s) from file (%s)", pathname);
+
+    vector<XSECCryptoX509CRL*>::size_type count = crls.size();
+
+    BIO* in=BIO_new(BIO_s_file_internal());
+    if (in && BIO_read_filename(in, pathname)>0) {
+        // If the format isn't set, try and guess it.
+        if (!format) {
+            const int READSIZE = 1;
+            char buf[READSIZE];
+            int mark;
+
+            // Examine the first byte.
+            try {
+                if ((mark = BIO_tell(in)) < 0)
+                    throw XMLSecurityException("Error loading CRL: BIO_tell() can't get the file position.");
+                if (BIO_read(in, buf, READSIZE) <= 0)
+                    throw XMLSecurityException("Error loading CRL: BIO_read() can't read from the stream.");
+                if (BIO_seek(in, mark) < 0)
+                    throw XMLSecurityException("Error loading CRL: BIO_seek() can't reset the file position.");
+            }
+            catch (exception&) {
+                log_openssl();
+                BIO_free(in);
+                throw;
+            }
+
+            // Check the first byte of the file.  If it's some kind of DER-encoded structure
+            // it will begin with ASCII 048. Otherwise, assume it's PEM.
+            if (buf[0] != 48) {
+                format = "PEM";
+            }
+            else {
+                format = "DER";
+            }
+            log.debug("CRL encoding format for (%s) dynamically resolved as (%s)", pathname, format);
+        }
+
+        X509_CRL* crl=NULL;
+        if (!strcmp(format, "PEM")) {
+            while (crl=PEM_read_bio_X509_CRL(in, NULL, NULL, NULL)) {
+                crls.push_back(new OpenSSLCryptoX509CRL(crl));
+                X509_CRL_free(crl);
+            }
+        }
+        else if (!strcmp(format, "DER")) {
+            crl=d2i_X509_CRL_bio(in, NULL);
+            if (crl) {
+                crls.push_back(new OpenSSLCryptoX509CRL(crl));
+                X509_CRL_free(crl);
+            }
+        }
+        else {
+            log.error("unknown CRL encoding format (%s)", format);
+        }
+    }
+    if (in)
+        BIO_free(in);
+
+    if (crls.size() == count) {
+        log_openssl();
+        throw XMLSecurityException("Unable to load CRL(s) from file ($1).", params(1, pathname));
+    }
+
+    return crls.size();
+}
+
+XSECCryptoKey* SecurityHelper::loadKeyFromURL(SOAPTransport& transport, const char* backing, const char* format, const char* password)
+{
+    // Fetch the data.
+    istringstream dummy;
+    transport.send(dummy);
+    istream& msg = transport.receive();
+
+    // Dump to output file.
+    ofstream out(backing, fstream::trunc|fstream::binary);
+    out << msg.rdbuf();
+
+    return loadKeyFromFile(backing, format, password);
+}
+
+vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromURL(
+    vector<XSECCryptoX509*>& certs, SOAPTransport& transport, const char* backing, const char* format, const char* password
+    )
+{
+    // Fetch the data.
+    istringstream dummy;
+    transport.send(dummy);
+    istream& msg = transport.receive();
+
+    // Dump to output file.
+    ofstream out(backing, fstream::trunc|fstream::binary);
+    out << msg.rdbuf();
+
+    return loadCertificatesFromFile(certs, backing, format, password);
+}
+
+vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromURL(
+    vector<XSECCryptoX509CRL*>& crls, SOAPTransport& transport, const char* backing, const char* format
+    )
+{
+    // Fetch the data.
+    istringstream dummy;
+    transport.send(dummy);
+    istream& msg = transport.receive();
+
+    // Dump to output file.
+    ofstream out(backing, fstream::trunc|fstream::binary);
+    out << msg.rdbuf();
+
+    return loadCRLsFromFile(crls, backing, format);
+}
+
+bool SecurityHelper::matches(const XSECCryptoKey* key1, const XSECCryptoKey* key2)
+{
+    if (key1->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL ||
+        key2->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
+        Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("comparison of non-OpenSSL keys not supported");
+        return false;
+    }
+
+    // If one key is public or both, just compare the public key half.
+    if (key1->getKeyType()==XSECCryptoKey::KEY_RSA_PUBLIC || key1->getKeyType()==XSECCryptoKey::KEY_RSA_PAIR) {
+        if (key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PUBLIC && key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
+            return false;
+        const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA*>(key1)->getOpenSSLRSA();
+        const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA*>(key2)->getOpenSSLRSA();
+        return (BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->e,rsa2->e) == 0);
+    }
+
+    // For a private key, compare the private half.
+    if (key1->getKeyType()==XSECCryptoKey::KEY_RSA_PRIVATE) {
+        if (key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PRIVATE && key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
+            return false;
+        const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA*>(key1)->getOpenSSLRSA();
+        const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA*>(key2)->getOpenSSLRSA();
+        return (BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->d,rsa2->d) == 0);
+    }
+
+    // If one key is public or both, just compare the public key half.
+    if (key1->getKeyType()==XSECCryptoKey::KEY_DSA_PUBLIC || key1->getKeyType()==XSECCryptoKey::KEY_DSA_PAIR) {
+        if (key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PUBLIC && key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
+            return false;
+        const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA*>(key1)->getOpenSSLDSA();
+        const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA*>(key2)->getOpenSSLDSA();
+        return (BN_cmp(dsa1->pub_key,dsa2->pub_key) == 0);
+    }
+
+    // For a private key, compare the private half.
+    if (key1->getKeyType()==XSECCryptoKey::KEY_DSA_PRIVATE) {
+        if (key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PRIVATE && key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
+            return false;
+        const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA*>(key1)->getOpenSSLDSA();
+        const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA*>(key2)->getOpenSSLDSA();
+        return (BN_cmp(dsa1->priv_key,dsa2->priv_key) == 0);
+    }
+
+    Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("unsupported key type for comparison");
+    return false;
+}
index 053f6ab..33e4392 100644 (file)
@@ -23,6 +23,7 @@ xmlsec_sources = \
     FilesystemCredentialResolverTest.h \
     InlineKeyResolverTest.h \
     MemoryStorageServiceTest.h \
     FilesystemCredentialResolverTest.h \
     InlineKeyResolverTest.h \
     MemoryStorageServiceTest.h \
+    SecurityHelperTest.h \
     SignatureTest.h
 else
 xmlsec_sources =
     SignatureTest.h
 else
 xmlsec_sources =
diff --git a/xmltoolingtest/SecurityHelperTest.h b/xmltoolingtest/SecurityHelperTest.h
new file mode 100644 (file)
index 0000000..aa80fbc
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ *  Copyright 2001-2007 Internet2
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "XMLObjectBaseTestCase.h"
+
+#include <xmltooling/security/SecurityHelper.h>
+
+class SecurityHelperTest : public CxxTest::TestSuite {
+    vector<XSECCryptoX509*> certs;
+public:
+    void setUp() {
+    }
+
+    void tearDown() {
+        for_each(certs.begin(), certs.end(), xmltooling::cleanup<XSECCryptoX509>());
+    }
+
+    void testKeysFromFiles() {
+        string pathname = data_path + "key.pem";
+        auto_ptr<XSECCryptoKey> key1(SecurityHelper::loadKeyFromFile(pathname.c_str()));
+        pathname = data_path + "key.der";
+        auto_ptr<XSECCryptoKey> key2(SecurityHelper::loadKeyFromFile(pathname.c_str()));
+        pathname = data_path + "test.pfx";
+        auto_ptr<XSECCryptoKey> key3(SecurityHelper::loadKeyFromFile(pathname.c_str(), NULL, "password"));
+
+        TSM_ASSERT("PEM/DER keys did not match", SecurityHelper::matches(key1.get(), key2.get()));
+        TSM_ASSERT("DER/PKCS12 keys did not match", SecurityHelper::matches(key2.get(), key3.get()));
+
+        pathname = data_path + "key2.pem";
+        auto_ptr<XSECCryptoKey> key4(SecurityHelper::loadKeyFromFile(pathname.c_str()));
+        TSM_ASSERT("Different keys matched", !SecurityHelper::matches(key3.get(), key4.get()));
+    }
+
+    void testCertificatesFromFiles() {
+        string pathname = data_path + "cert.pem";
+        SecurityHelper::loadCertificatesFromFile(certs, pathname.c_str());
+        pathname = data_path + "cert.der";
+        SecurityHelper::loadCertificatesFromFile(certs, pathname.c_str());
+        pathname = data_path + "test.pfx";
+        SecurityHelper::loadCertificatesFromFile(certs, pathname.c_str(), NULL, "password");
+
+        TSM_ASSERT_EQUALS("Wrong certificate count", certs.size(), 3);
+
+        auto_ptr<XSECCryptoKey> key1(certs[0]->clonePublicKey());
+        auto_ptr<XSECCryptoKey> key2(certs[0]->clonePublicKey());
+        auto_ptr<XSECCryptoKey> key3(certs[0]->clonePublicKey());
+
+        TSM_ASSERT("PEM/DER keys did not match", SecurityHelper::matches(key1.get(), key2.get()));
+        TSM_ASSERT("DER/PKCS12 keys did not match", SecurityHelper::matches(key2.get(), key3.get()));
+
+        for_each(certs.begin(), certs.end(), xmltooling::cleanup<XSECCryptoX509>());
+    }
+};
diff --git a/xmltoolingtest/data/cert.der b/xmltoolingtest/data/cert.der
new file mode 100644 (file)
index 0000000..2776767
Binary files /dev/null and b/xmltoolingtest/data/cert.der differ
diff --git a/xmltoolingtest/data/key.der b/xmltoolingtest/data/key.der
new file mode 100644 (file)
index 0000000..454fdc7
Binary files /dev/null and b/xmltoolingtest/data/key.der differ
diff --git a/xmltoolingtest/data/test.pfx b/xmltoolingtest/data/test.pfx
new file mode 100644 (file)
index 0000000..4bbd936
Binary files /dev/null and b/xmltoolingtest/data/test.pfx differ