2 * Copyright 2001-2008 Internet2
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
20 * A helper class for working with keys, certificates, etc.
25 #include "security/OpenSSLCryptoX509CRL.h"
26 #include "security/SecurityHelper.h"
30 #include <openssl/pem.h>
31 #include <openssl/pkcs12.h>
32 #include <xsec/enc/OpenSSL/OpenSSLCryptoX509.hpp>
33 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyRSA.hpp>
34 #include <xsec/enc/OpenSSL/OpenSSLCryptoKeyDSA.hpp>
36 using namespace xmltooling::logging;
37 using namespace xmltooling;
40 // OpenSSL password callback...
41 static int passwd_callback(char* buf, int len, int verify, void* passwd)
45 if(passwd && len > strlen(reinterpret_cast<char*>(passwd)))
47 strcpy(buf,reinterpret_cast<char*>(passwd));
54 XSECCryptoKey* SecurityHelper::loadKeyFromFile(const char* pathname, const char* format, const char* password)
57 NDC ndc("loadKeyFromFile");
59 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
60 log.info("loading private key from file (%s)", pathname);
66 BIO* in=BIO_new(BIO_s_file_internal());
67 if (in && BIO_read_filename(in, pathname)>0) {
68 // If the format isn't set, try and guess it.
70 const int READSIZE = 1;
74 // Examine the first byte.
76 if ((mark = BIO_tell(in)) < 0)
77 throw XMLSecurityException("Error loading key: BIO_tell() can't get the file position.");
78 if (BIO_read(in, buf, READSIZE) <= 0)
79 throw XMLSecurityException("Error loading key: BIO_read() can't read from the stream.");
80 if (BIO_seek(in, mark) < 0)
81 throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
89 // Check the first byte of the file. If it's some kind of DER-encoded structure
90 // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
95 // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
96 // If it fails, must be another kind of DER-encoded structure.
97 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
99 if (BIO_seek(in, mark) < 0) {
102 throw XMLSecurityException("Error loading key: BIO_seek() can't reset the file position.");
109 log.debug("key encoding format for (%s) dynamically resolved as (%s)", pathname, format);
112 // The format should be known, so parse accordingly.
113 if (!strcmp(format, "PEM")) {
114 pkey = PEM_read_bio_PrivateKey(in, NULL, passwd_callback, const_cast<char*>(password));
116 else if (!strcmp(format, "DER")) {
117 pkey=d2i_PrivateKey_bio(in, NULL);
119 else if (!strcmp(format, "PKCS12")) {
121 p12 = d2i_PKCS12_bio(in, NULL);
124 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, NULL);
130 log.error("unknown key encoding format (%s)", format);
136 // Now map it to an XSEC wrapper.
138 XSECCryptoKey* ret=NULL;
139 switch (pkey->type) {
141 ret=new OpenSSLCryptoKeyRSA(pkey);
145 ret=new OpenSSLCryptoKeyDSA(pkey);
149 log.error("unsupported private key type");
157 throw XMLSecurityException("Unable to load private key from file ($1).", params(1, pathname));
160 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromFile(
161 vector<XSECCryptoX509*>& certs, const char* pathname, const char* format, const char* password
165 NDC ndc("loadCertificatesFromFile");
167 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
168 log.info("loading certificate(s) from file (%s)", pathname);
170 vector<XSECCryptoX509*>::size_type count = certs.size();
176 BIO* in=BIO_new(BIO_s_file_internal());
177 if (in && BIO_read_filename(in, pathname)>0) {
178 // If the format isn't set, try and guess it.
180 const int READSIZE = 1;
184 // Examine the first byte.
186 if ((mark = BIO_tell(in)) < 0)
187 throw XMLSecurityException("Error loading certificate: BIO_tell() can't get the file position.");
188 if (BIO_read(in, buf, READSIZE) <= 0)
189 throw XMLSecurityException("Error loading certificate: BIO_read() can't read from the stream.");
190 if (BIO_seek(in, mark) < 0)
191 throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
199 // Check the first byte of the file. If it's some kind of DER-encoded structure
200 // (including PKCS12), it will begin with ASCII 048. Otherwise, assume it's PEM.
205 // Here we know it's DER-encoded, now try to parse it as a PKCS12 ASN.1 structure.
206 // If it fails, must be another kind of DER-encoded structure.
207 if ((p12=d2i_PKCS12_bio(in, NULL)) == NULL) {
209 if (BIO_seek(in, mark) < 0) {
212 throw XMLSecurityException("Error loading certificate: BIO_seek() can't reset the file position.");
221 // The format should be known, so parse accordingly.
222 if (!strcmp(format, "PEM")) {
223 while (x=PEM_read_bio_X509(in, NULL, NULL, NULL)) {
224 certs.push_back(new OpenSSLCryptoX509(x));
228 else if (!strcmp(format, "DER")) {
229 x=d2i_X509_bio(in, NULL);
231 certs.push_back(new OpenSSLCryptoX509(x));
235 else if (!strcmp(format, "PKCS12")) {
237 p12 = d2i_PKCS12_bio(in, NULL);
240 STACK_OF(X509)* CAstack = sk_X509_new_null();
241 PKCS12_parse(p12, const_cast<char*>(password), &pkey, &x, &CAstack);
245 certs.push_back(new OpenSSLCryptoX509(x));
248 x = sk_X509_pop(CAstack);
250 certs.push_back(new OpenSSLCryptoX509(x));
252 x = sk_X509_pop(CAstack);
254 sk_X509_free(CAstack);
261 if (certs.size() == count) {
263 throw XMLSecurityException("Unable to load certificate(s) from file ($1).", params(1, pathname));
269 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromFile(
270 vector<XSECCryptoX509CRL*>& crls, const char* pathname, const char* format
274 NDC ndc("loadCRLsFromFile");
276 Category& log = Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper");
277 log.info("loading CRL(s) from file (%s)", pathname);
279 vector<XSECCryptoX509CRL*>::size_type count = crls.size();
281 BIO* in=BIO_new(BIO_s_file_internal());
282 if (in && BIO_read_filename(in, pathname)>0) {
283 // If the format isn't set, try and guess it.
285 const int READSIZE = 1;
289 // Examine the first byte.
291 if ((mark = BIO_tell(in)) < 0)
292 throw XMLSecurityException("Error loading CRL: BIO_tell() can't get the file position.");
293 if (BIO_read(in, buf, READSIZE) <= 0)
294 throw XMLSecurityException("Error loading CRL: BIO_read() can't read from the stream.");
295 if (BIO_seek(in, mark) < 0)
296 throw XMLSecurityException("Error loading CRL: BIO_seek() can't reset the file position.");
304 // Check the first byte of the file. If it's some kind of DER-encoded structure
305 // it will begin with ASCII 048. Otherwise, assume it's PEM.
312 log.debug("CRL encoding format for (%s) dynamically resolved as (%s)", pathname, format);
316 if (!strcmp(format, "PEM")) {
317 while (crl=PEM_read_bio_X509_CRL(in, NULL, NULL, NULL)) {
318 crls.push_back(new OpenSSLCryptoX509CRL(crl));
322 else if (!strcmp(format, "DER")) {
323 crl=d2i_X509_CRL_bio(in, NULL);
325 crls.push_back(new OpenSSLCryptoX509CRL(crl));
330 log.error("unknown CRL encoding format (%s)", format);
336 if (crls.size() == count) {
338 throw XMLSecurityException("Unable to load CRL(s) from file ($1).", params(1, pathname));
344 XSECCryptoKey* SecurityHelper::loadKeyFromURL(SOAPTransport& transport, const char* backing, const char* format, const char* password)
348 transport.send(dummy);
349 istream& msg = transport.receive();
351 // Dump to output file.
352 ofstream out(backing, fstream::trunc|fstream::binary);
355 return loadKeyFromFile(backing, format, password);
358 vector<XSECCryptoX509*>::size_type SecurityHelper::loadCertificatesFromURL(
359 vector<XSECCryptoX509*>& certs, SOAPTransport& transport, const char* backing, const char* format, const char* password
364 transport.send(dummy);
365 istream& msg = transport.receive();
367 // Dump to output file.
368 ofstream out(backing, fstream::trunc|fstream::binary);
371 return loadCertificatesFromFile(certs, backing, format, password);
374 vector<XSECCryptoX509CRL*>::size_type SecurityHelper::loadCRLsFromURL(
375 vector<XSECCryptoX509CRL*>& crls, SOAPTransport& transport, const char* backing, const char* format
380 transport.send(dummy);
381 istream& msg = transport.receive();
383 // Dump to output file.
384 ofstream out(backing, fstream::trunc|fstream::binary);
387 return loadCRLsFromFile(crls, backing, format);
390 bool SecurityHelper::matches(const XSECCryptoKey* key1, const XSECCryptoKey* key2)
392 if (key1->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL ||
393 key2->getProviderName()!=DSIGConstants::s_unicodeStrPROVOpenSSL) {
394 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("comparison of non-OpenSSL keys not supported");
398 // If one key is public or both, just compare the public key half.
399 if (key1->getKeyType()==XSECCryptoKey::KEY_RSA_PUBLIC || key1->getKeyType()==XSECCryptoKey::KEY_RSA_PAIR) {
400 if (key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PUBLIC && key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
402 const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA*>(key1)->getOpenSSLRSA();
403 const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA*>(key2)->getOpenSSLRSA();
404 return (BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->e,rsa2->e) == 0);
407 // For a private key, compare the private half.
408 if (key1->getKeyType()==XSECCryptoKey::KEY_RSA_PRIVATE) {
409 if (key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PRIVATE && key2->getKeyType()!=XSECCryptoKey::KEY_RSA_PAIR)
411 const RSA* rsa1 = static_cast<const OpenSSLCryptoKeyRSA*>(key1)->getOpenSSLRSA();
412 const RSA* rsa2 = static_cast<const OpenSSLCryptoKeyRSA*>(key2)->getOpenSSLRSA();
413 return (BN_cmp(rsa1->n,rsa2->n) == 0 && BN_cmp(rsa1->d,rsa2->d) == 0);
416 // If one key is public or both, just compare the public key half.
417 if (key1->getKeyType()==XSECCryptoKey::KEY_DSA_PUBLIC || key1->getKeyType()==XSECCryptoKey::KEY_DSA_PAIR) {
418 if (key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PUBLIC && key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
420 const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA*>(key1)->getOpenSSLDSA();
421 const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA*>(key2)->getOpenSSLDSA();
422 return (BN_cmp(dsa1->pub_key,dsa2->pub_key) == 0);
425 // For a private key, compare the private half.
426 if (key1->getKeyType()==XSECCryptoKey::KEY_DSA_PRIVATE) {
427 if (key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PRIVATE && key2->getKeyType()!=XSECCryptoKey::KEY_DSA_PAIR)
429 const DSA* dsa1 = static_cast<const OpenSSLCryptoKeyDSA*>(key1)->getOpenSSLDSA();
430 const DSA* dsa2 = static_cast<const OpenSSLCryptoKeyDSA*>(key2)->getOpenSSLDSA();
431 return (BN_cmp(dsa1->priv_key,dsa2->priv_key) == 0);
434 Category::getInstance(XMLTOOLING_LOGCAT".SecurityHelper").warn("unsupported key type for comparison");