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