Add option to reject unauthenticated ciphers
[shibboleth/cpp-xmltooling.git] / xmltooling / encryption / impl / Decrypter.cpp
index b29ca13..ce9445f 100644 (file)
@@ -1,17 +1,21 @@
-/*
- *  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
+/**
+ * Licensed to the University Corporation for Advanced Internet
+ * Development, Inc. (UCAID) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership.
+ *
+ * UCAID licenses this file to you 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
+ * 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.
+ * 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.
  */
 
 /**
@@ -24,6 +28,7 @@
 #include "logging.h"
 #include "encryption/Decrypter.h"
 #include "encryption/EncryptedKeyResolver.h"
+#include "encryption/Encryption.h"
 #include "security/Credential.h"
 #include "security/CredentialCriteria.h"
 #include "security/CredentialResolver.h"
@@ -33,6 +38,7 @@
 #include <xsec/framework/XSECAlgorithmMapper.hpp>
 #include <xsec/framework/XSECAlgorithmHandler.hpp>
 #include <xsec/utils/XSECBinTXFMInputStream.hpp>
+#include <xsec/xenc/XENCCipher.hpp>
 #include <xsec/xenc/XENCEncryptedData.hpp>
 #include <xsec/xenc/XENCEncryptedKey.hpp>
 
@@ -42,30 +48,60 @@ using namespace xmltooling;
 using namespace xercesc;
 using namespace std;
 
+
+Decrypter::Decrypter(
+    const CredentialResolver* credResolver,
+    CredentialCriteria* criteria,
+    const EncryptedKeyResolver* EKResolver,
+    bool requireAuthenticatedCipher
+    ) : m_cipher(nullptr), m_credResolver(credResolver), m_criteria(criteria), m_EKResolver(EKResolver),
+        m_requireAuthenticatedCipher(requireAuthenticatedCipher)
+{
+}
+
 Decrypter::~Decrypter()
 {
     if (m_cipher)
         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseCipher(m_cipher);
 }
 
+void Decrypter::setEncryptedKeyResolver(const EncryptedKeyResolver* EKResolver)
+{
+    m_EKResolver=EKResolver;
+}
+
+void Decrypter::setKEKResolver(const CredentialResolver* resolver, CredentialCriteria* criteria)
+{
+    m_credResolver=resolver;
+    m_criteria=criteria;
+}
+
 DOMDocumentFragment* Decrypter::decryptData(const EncryptedData& encryptedData, XSECCryptoKey* key)
 {
-    if (encryptedData.getDOM()==NULL)
+    if (encryptedData.getDOM() == nullptr)
         throw DecryptionException("The object must be marshalled before decryption.");
 
+    XMLToolingInternalConfig& xmlconf = XMLToolingInternalConfig::getInternalConfig();
+    if (m_requireAuthenticatedCipher) {
+        const XMLCh* alg = encryptedData.getEncryptionMethod() ? encryptedData.getEncryptionMethod()->getAlgorithm() : nullptr;
+        if (!alg || !xmlconf.isXMLAlgorithmSupported(alg, XMLToolingConfig::ALGTYPE_AUTHNENCRYPT)) {
+            throw DecryptionException("Unauthenticated data encryption algorithm unsupported.");
+        }
+    }
+
     // We can reuse the cipher object if the document hasn't changed.
 
     if (m_cipher && m_cipher->getDocument()!=encryptedData.getDOM()->getOwnerDocument()) {
-        XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseCipher(m_cipher);
-        m_cipher=NULL;
+        xmlconf.m_xsecProvider->releaseCipher(m_cipher);
+        m_cipher=nullptr;
     }
     
     if (!m_cipher)
-        m_cipher=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newCipher(encryptedData.getDOM()->getOwnerDocument());
+        m_cipher = xmlconf.m_xsecProvider->newCipher(encryptedData.getDOM()->getOwnerDocument());
 
     try {
         m_cipher->setKey(key->clone());
-        DOMNode* ret=m_cipher->decryptElementDetached(encryptedData.getDOM());
+        DOMNode* ret = m_cipher->decryptElementDetached(encryptedData.getDOM());
         if (ret->getNodeType()!=DOMNode::DOCUMENT_FRAGMENT_NODE) {
             ret->release();
             throw DecryptionException("Decryption operation did not result in DocumentFragment.");
@@ -95,7 +131,7 @@ DOMDocumentFragment* Decrypter::decryptData(const EncryptedData& encryptedData,
         const EncryptionMethod* meth = encryptedData.getEncryptionMethod();
         if (meth)
             m_criteria->setXMLAlgorithm(meth->getAlgorithm());
-        m_credResolver->resolve(creds,m_criteria);
+        m_credResolver->resolve(creds, m_criteria);
     }
     else {
         CredentialCriteria criteria;
@@ -104,12 +140,12 @@ DOMDocumentFragment* Decrypter::decryptData(const EncryptedData& encryptedData,
         const EncryptionMethod* meth = encryptedData.getEncryptionMethod();
         if (meth)
             criteria.setXMLAlgorithm(meth->getAlgorithm());
-        m_credResolver->resolve(creds,&criteria);
+        m_credResolver->resolve(creds, &criteria);
     }
 
     // Loop over them and try each one.
     XSECCryptoKey* key;
-    for (vector<const Credential*>::const_iterator cred = creds.begin(); cred!=creds.end(); ++cred) {
+    for (vector<const Credential*>::const_iterator cred = creds.begin(); cred != creds.end(); ++cred) {
         try {
             key = (*cred)->getPrivateKey();
             if (!key)
@@ -123,12 +159,12 @@ DOMDocumentFragment* Decrypter::decryptData(const EncryptedData& encryptedData,
 
     // We need to find an encrypted decryption key somewhere. We'll need the underlying algorithm...
     const XMLCh* algorithm=
-        encryptedData.getEncryptionMethod() ? encryptedData.getEncryptionMethod()->getAlgorithm() : NULL;
+        encryptedData.getEncryptionMethod() ? encryptedData.getEncryptionMethod()->getAlgorithm() : nullptr;
     if (!algorithm)
         throw DecryptionException("No EncryptionMethod/@Algorithm set, key decryption cannot proceed.");
     
     // Check for external resolver.
-    const EncryptedKey* encKey=NULL;
+    const EncryptedKey* encKey=nullptr;
     if (m_EKResolver)
         encKey = m_EKResolver->resolveKey(encryptedData, recipient);
     else {
@@ -147,18 +183,26 @@ DOMDocumentFragment* Decrypter::decryptData(const EncryptedData& encryptedData,
 
 void Decrypter::decryptData(ostream& out, const EncryptedData& encryptedData, XSECCryptoKey* key)
 {
-    if (encryptedData.getDOM()==NULL)
+    if (encryptedData.getDOM() == nullptr)
         throw DecryptionException("The object must be marshalled before decryption.");
 
+    XMLToolingInternalConfig& xmlconf = XMLToolingInternalConfig::getInternalConfig();
+    if (m_requireAuthenticatedCipher) {
+        const XMLCh* alg = encryptedData.getEncryptionMethod() ? encryptedData.getEncryptionMethod()->getAlgorithm() : nullptr;
+        if (!alg || !xmlconf.isXMLAlgorithmSupported(alg, XMLToolingConfig::ALGTYPE_AUTHNENCRYPT)) {
+            throw DecryptionException("Unauthenticated data encryption algorithm unsupported.");
+        }
+    }
+
     // We can reuse the cipher object if the document hasn't changed.
 
-    if (m_cipher && m_cipher->getDocument()!=encryptedData.getDOM()->getOwnerDocument()) {
-        XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseCipher(m_cipher);
-        m_cipher=NULL;
+    if (m_cipher && m_cipher->getDocument() != encryptedData.getDOM()->getOwnerDocument()) {
+        xmlconf.m_xsecProvider->releaseCipher(m_cipher);
+        m_cipher = nullptr;
     }
     
     if (!m_cipher)
-        m_cipher=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newCipher(encryptedData.getDOM()->getOwnerDocument());
+        m_cipher = xmlconf.m_xsecProvider->newCipher(encryptedData.getDOM()->getOwnerDocument());
 
     try {
         m_cipher->setKey(key->clone());
@@ -206,7 +250,7 @@ void Decrypter::decryptData(ostream& out, const EncryptedData& encryptedData, co
 
     // Loop over them and try each one.
     XSECCryptoKey* key;
-    for (vector<const Credential*>::const_iterator cred = creds.begin(); cred!=creds.end(); ++cred) {
+    for (vector<const Credential*>::const_iterator cred = creds.begin(); cred != creds.end(); ++cred) {
         try {
             key = (*cred)->getPrivateKey();
             if (!key)
@@ -220,12 +264,12 @@ void Decrypter::decryptData(ostream& out, const EncryptedData& encryptedData, co
 
     // We need to find an encrypted decryption key somewhere. We'll need the underlying algorithm...
     const XMLCh* algorithm=
-        encryptedData.getEncryptionMethod() ? encryptedData.getEncryptionMethod()->getAlgorithm() : NULL;
+        encryptedData.getEncryptionMethod() ? encryptedData.getEncryptionMethod()->getAlgorithm() : nullptr;
     if (!algorithm)
         throw DecryptionException("No EncryptionMethod/@Algorithm set, key decryption cannot proceed.");
     
     // Check for external resolver.
-    const EncryptedKey* encKey=NULL;
+    const EncryptedKey* encKey=nullptr;
     if (m_EKResolver)
         encKey = m_EKResolver->resolveKey(encryptedData, recipient);
     else {
@@ -247,22 +291,32 @@ XSECCryptoKey* Decrypter::decryptKey(const EncryptedKey& encryptedKey, const XML
     if (!m_credResolver)
         throw DecryptionException("No CredentialResolver supplied to provide decryption keys.");
 
-    if (encryptedKey.getDOM()==NULL)
+    if (encryptedKey.getDOM()==nullptr)
         throw DecryptionException("The object must be marshalled before decryption.");
 
-    XSECAlgorithmHandler* handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(algorithm);
-    if (!handler)
-        throw DecryptionException("Unrecognized algorithm, no way to build object around decrypted key.");
+    XSECAlgorithmHandler* handler;
+    try {
+        handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(algorithm);
+        if (!handler)
+            throw DecryptionException("Unrecognized algorithm, no way to build object around decrypted key.");
+    }
+    catch(XSECException& e) {
+        auto_ptr_char temp(e.getMsg());
+        throw DecryptionException(string("XMLSecurity exception while decrypting key: ") + temp.get());
+    }
+    catch(XSECCryptoException& e) {
+        throw DecryptionException(string("XMLSecurity exception while decrypting key: ") + e.getMsg());
+    }
     
     // We can reuse the cipher object if the document hasn't changed.
 
     if (m_cipher && m_cipher->getDocument()!=encryptedKey.getDOM()->getOwnerDocument()) {
         XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->releaseCipher(m_cipher);
-        m_cipher=NULL;
+        m_cipher = nullptr;
     }
     
     if (!m_cipher)
-        m_cipher=XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newCipher(encryptedKey.getDOM()->getOwnerDocument());
+        m_cipher = XMLToolingInternalConfig::getInternalConfig().m_xsecProvider->newCipher(encryptedKey.getDOM()->getOwnerDocument());
     
     // Resolve key decryption keys.
     int types = CredentialCriteria::KEYINFO_EXTRACTION_KEY | CredentialCriteria::KEYINFO_EXTRACTION_KEYNAMES;
@@ -288,7 +342,7 @@ XSECCryptoKey* Decrypter::decryptKey(const EncryptedKey& encryptedKey, const XML
         throw DecryptionException("Unable to resolve any key decryption keys.");
 
     XMLByte buffer[1024];
-    for (vector<const Credential*>::const_iterator cred = creds.begin(); cred!=creds.end(); ++cred) {
+    for (vector<const Credential*>::const_iterator cred = creds.begin(); cred != creds.end(); ++cred) {
         try {
             if (!(*cred)->getPrivateKey())
                 throw DecryptionException("Credential did not contain a private key.");
@@ -316,5 +370,24 @@ XSECCryptoKey* Decrypter::decryptKey(const EncryptedKey& encryptedKey, const XML
         }
     }
     
-    throw DecryptionException("Unable to decrypt key.");
+    // Some algorithms are vulnerable to chosen ciphertext attacks, so we generate a random key
+    // to prevent discovery of the validity of the original candidate.
+    logging::Category::getInstance(XMLTOOLING_LOGCAT".Decrypter").warn(
+        "unable to decrypt key, generating random key for defensive purposes"
+        );
+    pair<const char*,unsigned int> mapped = XMLToolingConfig::getConfig().mapXMLAlgorithmToKeyAlgorithm(algorithm);
+    if (!mapped.second)
+        mapped.second = 256;
+    try {
+        if (XSECPlatformUtils::g_cryptoProvider->getRandom(reinterpret_cast<unsigned char*>(buffer),mapped.second) < mapped.second)
+            throw DecryptionException("Unable to generate random data; was PRNG seeded?");
+        return handler->createKeyForURI(algorithm, buffer, mapped.second);
+    }
+    catch(XSECException& e) {
+        auto_ptr_char temp(e.getMsg());
+        throw DecryptionException(string("XMLSecurity exception while generating key: ") + temp.get());
+    }
+    catch (XSECCryptoException& e) {
+        throw DecryptionException(string("XMLSecurity exception while generating key: ") + e.getMsg());
+    }
 }