1 /**
2  * Licensed to the University Corporation for Advanced Internet
3  * Development, Inc. (UCAID) under one or more contributor license
4  * agreements. See the NOTICE file distributed with this work for
5  * additional information regarding copyright ownership.
6  *
7  * UCAID licenses this file to you under the Apache License,
8  * Version 2.0 (the "License"); you may not use this file except
9  * in compliance with the License. You may obtain a copy of the
10  * License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
17  * either express or implied. See the License for the specific
18  * language governing permissions and limitations under the License.
19  */
20 
21 /**
22  * DataSealer.cpp
23  *
24  * Generic data protection interface.
25  */
26 
27 #include "internal.h"
28 #include "security/DataSealer.h"
29 #include "util/XMLHelper.h"
30 
31 #include <sstream>
32 #include <xercesc/util/Base64.hpp>
33 #include <xercesc/util/XMLDateTime.hpp>
34 #include <xsec/enc/XSECCryptoException.hpp>
35 #include <xsec/framework/XSECAlgorithmHandler.hpp>
36 #include <xsec/framework/XSECAlgorithmMapper.hpp>
37 #include <xsec/framework/XSECEnv.hpp>
38 #include <xsec/framework/XSECException.hpp>
39 #include <xsec/transformers/TXFMChain.hpp>
40 #include <xsec/transformers/TXFMBase64.hpp>
41 #include <xsec/transformers/TXFMChar.hpp>
42 #include <xsec/xenc/XENCEncryptionMethod.hpp>
43 
44 using namespace xmltooling::logging;
45 using namespace xmltooling;
46 using xercesc::Base64;
47 using xercesc::DOMDocument;
48 using xercesc::Janitor;
49 using xercesc::XMLDateTime;
50 using xercesc::XMLException;
51 using boost::scoped_ptr;
52 using namespace std;
53 
54 namespace xmltooling {
55     XMLTOOL_DLLLOCAL PluginManager<DataSealerKeyStrategy, string, const xercesc::DOMElement*>::Factory StaticDataSealerKeyStrategyFactory;
56     XMLTOOL_DLLLOCAL PluginManager<DataSealerKeyStrategy, string, const xercesc::DOMElement*>::Factory VersionedDataSealerKeyStrategyFactory;
57 };
58 
registerDataSealerKeyStrategies()59 void XMLTOOL_API xmltooling::registerDataSealerKeyStrategies()
60 {
61     XMLToolingConfig& conf = XMLToolingConfig::getConfig();
62     conf.DataSealerKeyStrategyManager.registerFactory(STATIC_DATA_SEALER_KEY_STRATEGY, StaticDataSealerKeyStrategyFactory);
63     conf.DataSealerKeyStrategyManager.registerFactory(VERSIONED_DATA_SEALER_KEY_STRATEGY, VersionedDataSealerKeyStrategyFactory);
64 }
65 
DataSealerKeyStrategy()66 DataSealerKeyStrategy::DataSealerKeyStrategy()
67 {
68 }
69 
~DataSealerKeyStrategy()70 DataSealerKeyStrategy::~DataSealerKeyStrategy()
71 {
72 }
73 
DataSealer(DataSealerKeyStrategy * strategy)74 DataSealer::DataSealer(DataSealerKeyStrategy* strategy) : m_log(Category::getInstance(XMLTOOLING_LOGCAT".DataSealer")), m_strategy(strategy)
75 {
76     if (!strategy)
77         throw XMLSecurityException("DataSealer requires DataSealerKeyStrategy");
78 }
79 
~DataSealer()80 DataSealer::~DataSealer()
81 {
82 }
83 
wrap(const char * s,time_t exp) const84 string DataSealer::wrap(const char* s, time_t exp) const
85 {
86     Locker locker(m_strategy.get());
87 
88     m_log.debug("wrapping data with default key");
89 
90     // Get default key to use.
91     pair<string,const XSECCryptoSymmetricKey*> defaultKey = m_strategy->getDefaultKey();
92 
93     const XMLCh* algorithm = nullptr;
94     switch (defaultKey.second->getSymmetricKeyType()) {
95         case XSECCryptoSymmetricKey::KEY_AES_128:
96             algorithm = DSIGConstants::s_unicodeStrURIAES128_GCM;
97             break;
98 
99         case XSECCryptoSymmetricKey::KEY_AES_192:
100             algorithm = DSIGConstants::s_unicodeStrURIAES192_GCM;
101             break;
102 
103         case XSECCryptoSymmetricKey::KEY_AES_256:
104             algorithm = DSIGConstants::s_unicodeStrURIAES256_GCM;
105             break;
106 
107         default:
108             throw XMLSecurityException("Unknown key type.");
109     }
110 
111     const XSECAlgorithmHandler* handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(algorithm);
112     if (!handler) {
113         throw XMLSecurityException("Unable to obtain algorithm handler.");
114     }
115 
116 #ifndef HAVE_GMTIME_R
117     struct tm* ptime = gmtime(&exp);
118 #else
119     struct tm res;
120     struct tm* ptime = gmtime_r(&exp, &res);
121 #endif
122     char timebuf[32];
123     strftime(timebuf, 32, "%Y-%m-%dT%H:%M:%SZ", ptime);
124 
125     m_log.debug("using key (%s), data will expire on %s", defaultKey.first.c_str(), timebuf);
126 
127     // The data format of the plaintext packet is:
128     //    PLAINTEXT := KEYLABEL + ':' + ISOEXPTIME + DATA
129     // The plaintext is zipped, encrypted, base64'd, and prefixed with the
130     // KEYLABEL and a colon on the outside, as a key hint.
131 
132     // Construct the plaintext packet.
133     string sb(defaultKey.first);
134     sb = sb + ':' + timebuf + s;
135 
136     m_log.debug("deflating data");
137 
138     // zip the plaintext packet
139     unsigned int len;
140     char* deflated = XMLHelper::deflate(const_cast<char*>(sb.c_str()), sb.length(), &len);
141     if (!deflated || !len)
142         throw IOException("Failed to deflate data.");
143     auto_arrayptr<char> arrayjan(deflated);
144 
145     // Finally we encrypt the data. We have to hack this a bit to reuse the xmlsec routines.
146 
147     m_log.debug("encrypting data");
148 
149     DOMDocument* dummydoc = XMLToolingConfig::getConfig().getParser().newDocument();
150     Janitor<DOMDocument> docjan(dummydoc);
151     scoped_ptr<XSECEnv> env(new XSECEnv(dummydoc));
152 
153     TXFMChar* ct = new TXFMChar(dummydoc);
154     ct->setInput(deflated, len);
155     TXFMChain tx(ct);
156 
157     safeBuffer ciphertext;
158     try {
159         // Keys are not threadsafe, use a clone to encrypt.
160         scoped_ptr<XSECCryptoKey> clonedKey(defaultKey.second->clone());
161         scoped_ptr<XENCEncryptionMethod> method(XENCEncryptionMethod::create(env.get(), algorithm));
162         if (!handler->encryptToSafeBuffer(&tx, method.get(), clonedKey.get(), dummydoc, ciphertext)) {
163             throw XMLSecurityException("Data encryption failed.");
164         }
165     }
166     catch (const XSECException& ex) {
167         auto_ptr_char msg(ex.getMsg());
168         throw XMLSecurityException(msg.get());
169     }
170     catch (const XSECCryptoException& ex) {
171         auto_ptr_char msg(ex.getMsg());
172         throw XMLSecurityException(msg.get());
173     }
174 
175     defaultKey.first.append(":");
176     defaultKey.first.append(ciphertext.rawCharBuffer(), ciphertext.sbRawBufferSize());
177 
178     m_log.debug("final data size: %lu", defaultKey.first.length());
179 
180     return defaultKey.first;
181 }
182 
unwrap(const char * s) const183 string DataSealer::unwrap(const char* s) const
184 {
185     Locker locker(m_strategy.get());
186 
187     // The data format of the plaintext packet is:
188     //    PLAINTEXT := KEYLABEL + ':' + ISOEXPTIME + DATA
189     // The plaintext is zipped, encrypted, base64'd, and prefixed with the
190     // KEYLABEL and a colon on the outside, as a key hint.
191 
192     // First extract the key label up to the first colon.
193     pair<string, const XSECCryptoSymmetricKey*> requiredKey = make_pair<string, const XSECCryptoSymmetricKey*>(string(), nullptr);
194     const char* delim = strchr(s ? s : "", ':');
195     if (delim && delim > s) {
196         requiredKey.first.append(s, delim - s);
197         requiredKey.second = m_strategy->getKey(requiredKey.first.c_str());
198     }
199     if (!requiredKey.second)
200         throw IOException("Required decryption key ($1) not available.", params(1, requiredKey.first.c_str()));
201 
202     m_log.debug("decrypting data with key (%s)", requiredKey.first.c_str());
203 
204     const XMLCh* algorithm = nullptr;
205     switch (requiredKey.second->getSymmetricKeyType()) {
206     case XSECCryptoSymmetricKey::KEY_AES_128:
207         algorithm = DSIGConstants::s_unicodeStrURIAES128_GCM;
208         break;
209 
210     case XSECCryptoSymmetricKey::KEY_AES_192:
211         algorithm = DSIGConstants::s_unicodeStrURIAES192_GCM;
212         break;
213 
214     case XSECCryptoSymmetricKey::KEY_AES_256:
215         algorithm = DSIGConstants::s_unicodeStrURIAES256_GCM;
216         break;
217 
218     default:
219         throw XMLSecurityException("Unknown key type.");
220     }
221 
222     const XSECAlgorithmHandler* handler = XSECPlatformUtils::g_algorithmMapper->mapURIToHandler(algorithm);
223     if (!handler) {
224         throw XMLSecurityException("Unable to obtain algorithm handler.");
225     }
226 
227     DOMDocument* dummydoc = XMLToolingConfig::getConfig().getParser().newDocument();
228     Janitor<DOMDocument> docjan(dummydoc);
229     scoped_ptr<XSECEnv> env(new XSECEnv(dummydoc));
230 
231     TXFMChar* ct = new TXFMChar(dummydoc);
232     ct->setInput(++delim);
233     TXFMChain tx(ct);
234     TXFMBase64* b64 = new TXFMBase64(dummydoc, true); // decodes
235     tx.appendTxfm(b64);
236 
237     unsigned int len = 0;
238     safeBuffer plaintext;
239     try {
240         // Keys are not threadsafe, use a clone to decrypt.
241         scoped_ptr<XSECCryptoKey> clonedKey(requiredKey.second->clone());
242         scoped_ptr<XENCEncryptionMethod> method(XENCEncryptionMethod::create(env.get(), algorithm));
243         len = handler->decryptToSafeBuffer(&tx, method.get(), clonedKey.get(), dummydoc, plaintext);
244     }
245     catch (const XSECException& ex) {
246         auto_ptr_char msg(ex.getMsg());
247         throw XMLSecurityException(msg.get());
248     }
249     catch (const XSECCryptoException& ex) {
250         auto_ptr_char msg(ex.getMsg());
251         throw XMLSecurityException(msg.get());
252     }
253 
254     if (len == 0)
255         throw XMLSecurityException("No decrypted data available.");
256 
257     // Now we have to inflate it.
258 
259     m_log.debug("inflating data");
260 
261     stringstream out;
262     if (XMLHelper::inflate(const_cast<char*>(plaintext.rawCharBuffer()), len, out) == 0) {
263         throw IOException("Unable to inflate wrapped data.");
264     }
265 
266     string decrypted = out.str();
267 
268     // Pull off the key label to verify it.
269     size_t i = decrypted.find(':');
270     if (i == string::npos)
271         throw IOException("Unable to verify key used to decrypt data.");
272     string keyLabel = decrypted.substr(0, i);
273     if (keyLabel != requiredKey.first) {
274         m_log.warn("key mismatch, outside (%s), inside (%s)", requiredKey.first.c_str(), keyLabel.c_str());
275         throw IOException("Embedded key label does not match key used to decrypt data.");
276     }
277 
278     string dstr = decrypted.substr(++i, 20);
279     auto_ptr_XMLCh expstr(dstr.c_str());
280     try {
281         XMLDateTime exp(expstr.get());
282         exp.parseDateTime();
283         if (exp.getEpoch() < time(nullptr) - XMLToolingConfig::getConfig().clock_skew_secs) {
284             m_log.debug("decrypted data expired at %s", dstr.c_str());
285             throw IOException("Decrypted data has expired.");
286         }
287     }
288     catch (const XMLException& ex) {
289         auto_ptr_char msg(ex.getMessage());
290         throw IOException(msg.get());
291     }
292 
293     return decrypted.substr(i + 20);
294 }
295