Add hashing options to key extraction support.
[shibboleth/cpp-xmltooling.git] / xmltooling / security / impl / SecurityHelper.cpp
index 5c39bf8..ebcc37c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- *  Copyright 2001-2008 Internet2
+ *  Copyright 2001-2009 Internet2
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@
 #include "logging.h"
 #include "security/OpenSSLCryptoX509CRL.h"
 #include "security/SecurityHelper.h"
+#include "security/X509Credential.h"
 #include "util/NDC.h"
 
 #include <fstream>
@@ -51,6 +52,55 @@ static int passwd_callback(char* buf, int len, int verify, void* passwd)
     return 0;
 }
 
+const char* SecurityHelper::guessEncodingFormat(const char* pathname)
+{
+    const char* format=NULL;
+    BIO* in=BIO_new(BIO_s_file_internal());
+    if (in && BIO_read_filename(in, pathname)>0) {
+        const int READSIZE = 1;
+        char buf[READSIZE];
+        int mark;
+
+        // Examine the first byte.
+        try {
+            if ((mark = BIO_tell(in)) < 0)
+                throw XMLSecurityException("Error loading file: BIO_tell() can't get the file position.");
+            if (BIO_read(in, buf, READSIZE) <= 0)
+                throw XMLSecurityException("Error loading file: BIO_read() can't read from the stream.");
+            if (BIO_seek(in, mark) < 0)
+                throw XMLSecurityException("Error loading file: 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.
+            PKCS12* p12;
+            if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
+                format = "DER";
+            }
+            else {
+                format = "PKCS12";
+                PKCS12_free(p12);
+            }
+        }
+    }
+    if (in)
+        BIO_free(in);
+    if (format)
+        return format;
+    throw XMLSecurityException("Unable to determine encoding for file ($1).", params(1,pathname));
+}
+
 XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* format, const char* password)
 {
 #ifdef _DEBUG
@@ -66,7 +116,7 @@ XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char*
     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) {
+        if (!format || !*format) {
             const int READSIZE = 1;
             char buf[READSIZE];
             int mark;
@@ -176,7 +226,7 @@ vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromFile(
     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) {
+        if (!format || !*format) {
             const int READSIZE = 1;
             char buf[READSIZE];
             int mark;
@@ -281,7 +331,7 @@ vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromFile(
     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) {
+        if (!format || !*format) {
             const int READSIZE = 1;
             char buf[READSIZE];
             int mark;
@@ -344,13 +394,13 @@ vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromFile(
 XSECCryptoKey* SecurityHelper::loadKeyFromURL(SOAPTransport& transport, const char* backing, const char* format, const char* password)
 {
     // Fetch the data.
-    istringstream dummy;
-    transport.send(dummy);
+    transport.send();
     istream& msg = transport.receive();
 
     // Dump to output file.
     ofstream out(backing, fstream::trunc|fstream::binary);
     out << msg.rdbuf();
+    out.close();
 
     return loadKeyFromFile(backing, format, password);
 }
@@ -359,14 +409,13 @@ 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);
+    transport.send();
     istream& msg = transport.receive();
 
     // Dump to output file.
     ofstream out(backing, fstream::trunc|fstream::binary);
     out << msg.rdbuf();
+    out.close();
 
     return loadCertificatesFromFile(certs, backing, format, password);
 }
@@ -376,61 +425,209 @@ vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromURL(
     )
 {
     // Fetch the data.
-    istringstream dummy;
-    transport.send(dummy);
+    transport.send();
     istream& msg = transport.receive();
 
     // Dump to output file.
     ofstream out(backing, fstream::trunc|fstream::binary);
     out << msg.rdbuf();
+    out.close();
 
     return loadCRLsFromFile(crls, backing, format);
 }
 
-bool SecurityHelper::matches(const XSECCryptoKey* key1, const XSECCryptoKey* key2)
+bool SecurityHelper::matches(const XSECCryptoKey& key1, const XSECCryptoKey& key2)
 {
-    if (key1->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL ||
-        key2->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
+    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)
+    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);
+        const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA&>(key1).getOpenSSLRSA();
+        const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA&>(key2).getOpenSSLRSA();
+        return (rsa1 && rsa2 && 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)
+    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);
+        const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA&>(key1).getOpenSSLRSA();
+        const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA&>(key2).getOpenSSLRSA();
+        return (rsa1 && rsa2 && 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)
+    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);
+        const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA&>(key1).getOpenSSLDSA();
+        const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA&>(key2).getOpenSSLDSA();
+        return (dsa1 && dsa2 && 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)
+    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);
+        const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA&>(key1).getOpenSSLDSA();
+        const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA&>(key2).getOpenSSLDSA();
+        return (dsa1 && dsa2 && BN_cmp(dsa1->priv_key,dsa2->priv_key) == 0);
     }
 
     Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("unsupported key type for comparison");
     return false;
 }
+
+string SecurityHelper::getDEREncoding(const XSECCryptoKey& key, bool hash, bool nowrap)
+{
+    string ret;
+
+    if (key.getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
+        Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("encoding of non-OpenSSL keys not supported");
+        return ret;
+    }
+
+    if (key.getKeyType() == XSECCryptoKey::KEY_RSA_PUBLIC || key.getKeyType() == XSECCryptoKey::KEY_RSA_PAIR) {
+        const RSA* rsa = static_cast<const OpenSSLCryptoKeyRSA&>(key).getOpenSSLRSA();
+        if (!rsa) {
+            Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("key was not populated");
+            return ret;
+        }
+        BIO* chain = BIO_new(BIO_s_mem());
+        BIO* b = BIO_new(BIO_f_base64());
+        if (nowrap)
+            BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
+        chain = BIO_push(b, chain);
+        if (hash) {
+            b = BIO_new(BIO_f_md());
+            BIO_set_md(b, EVP_sha1());
+            chain = BIO_push(b, chain);
+        }
+        i2d_RSA_PUBKEY_bio(chain, const_cast<RSA*>(rsa));
+        BIO_flush(chain);
+        if (hash) {
+            char digest[20];
+            int len = BIO_gets(chain, digest, sizeof(digest));
+            if (len != sizeof(digest)) {
+                BIO_free_all(chain);
+                return ret;
+            }
+            b = BIO_pop(chain);
+            BIO_free(chain);
+            chain = b;
+            BIO_reset(chain);
+            BIO_write(chain, digest, len);
+            BIO_flush(chain);
+        }
+        BUF_MEM* bptr=NULL;
+        BIO_get_mem_ptr(chain, &bptr);
+        if (bptr && bptr->length > 0)
+            ret.append(bptr->data, bptr->length);
+        BIO_free_all(chain);
+    }
+    else if (key.getKeyType() == XSECCryptoKey::KEY_DSA_PUBLIC || key.getKeyType() == XSECCryptoKey::KEY_DSA_PAIR) {
+        const DSA* dsa = static_cast<const OpenSSLCryptoKeyDSA&>(key).getOpenSSLDSA();
+        if (!dsa) {
+            Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("key was not populated");
+            return ret;
+        }
+        BIO* chain = BIO_new(BIO_s_mem());
+        BIO* b = BIO_new(BIO_f_base64());
+        if (nowrap)
+            BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
+        chain = BIO_push(b, chain);
+        if (hash) {
+            b = BIO_new(BIO_f_md());
+            BIO_set_md(b, EVP_sha1());
+            chain = BIO_push(b, chain);
+        }
+        i2d_DSA_PUBKEY_bio(chain, const_cast<DSA*>(dsa));
+        BIO_flush(chain);
+        if (hash) {
+            char digest[20];
+            int len = BIO_gets(chain, digest, sizeof(digest));
+            if (len != sizeof(digest)) {
+                BIO_free_all(chain);
+                return ret;
+            }
+            b = BIO_pop(chain);
+            BIO_free(chain);
+            chain = b;
+            BIO_reset(chain);
+            BIO_write(chain, digest, len);
+            BIO_flush(chain);
+        }
+        BUF_MEM* bptr=NULL;
+        BIO_get_mem_ptr(chain, &bptr);
+        if (bptr && bptr->length > 0)
+            ret.append(bptr->data, bptr->length);
+        BIO_free_all(chain);
+    }
+    else {
+        Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("encoding of non-RSA/DSA public keys not supported");
+    }
+    return ret;
+}
+
+string SecurityHelper::getDEREncoding(const XSECCryptoX509& cert, bool hash, bool nowrap)
+{
+    string ret;
+
+    if (cert.getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
+        Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("encoding of non-OpenSSL keys not supported");
+        return ret;
+    }
+
+    const X509* x = static_cast<const OpenSSLCryptoX509&>(cert).getOpenSSLX509();
+    EVP_PKEY* key = X509_get_pubkey(const_cast<X509*>(x));
+
+    BIO* chain = BIO_new(BIO_s_mem());
+    BIO* b = BIO_new(BIO_f_base64());
+    if (nowrap)
+        BIO_set_flags(b, BIO_FLAGS_BASE64_NO_NL);
+    chain = BIO_push(b, chain);
+    if (hash) {
+        b = BIO_new(BIO_f_md());
+        BIO_set_md(b, EVP_sha1());
+        chain = BIO_push(b, chain);
+    }
+    i2d_PUBKEY_bio(chain, key);
+    EVP_PKEY_free(key);
+    BIO_flush(chain);
+    if (hash) {
+        char digest[20];
+        int len = BIO_gets(chain, digest, sizeof(digest));
+        if (len != sizeof(digest)) {
+            BIO_free_all(chain);
+            return ret;
+        }
+        b = BIO_pop(chain);
+        BIO_free(chain);
+        chain = b;
+        BIO_reset(chain);
+        BIO_write(chain, digest, len);
+        BIO_flush(chain);
+    }
+    BUF_MEM* bptr=NULL;
+    BIO_get_mem_ptr(chain, &bptr);
+    if (bptr && bptr->length > 0)
+        ret.append(bptr->data, bptr->length);
+    BIO_free_all(chain);
+    return ret;
+}
+
+string SecurityHelper::getDEREncoding(const Credential& cred, bool hash, bool nowrap)
+{
+    const X509Credential* x509 = dynamic_cast<const X509Credential*>(&cred);
+    if (x509 && !x509->getEntityCertificateChain().empty())
+        return getDEREncoding(*(x509->getEntityCertificateChain().front()), hash, nowrap);
+    else if (cred.getPublicKey())
+        return getDEREncoding(*(cred.getPublicKey()), hash, nowrap);
+    return "";
+}