This library provides a simple and nice C++ wrapper around these libraries, so that programmers can concentrate on functionality. It offers general support for PCSC-lite, OpenSSL, PKCS#11, plus specific functionality for the SuisseID.
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
585 lines
18 KiB
585 lines
18 KiB
/*! @file |
|
|
|
@id $Id$ |
|
*/ |
|
// 1 2 3 4 5 6 7 8 |
|
// 45678901234567890123456789012345678901234567890123456789012345678901234567890 |
|
|
|
#ifndef __OPENSSL_HXX__ |
|
#define __OPENSSL_HXX__ |
|
|
|
#include <openssl/pkcs12.h> |
|
#include <openssl/x509.h> |
|
#include "openssl/bio.h" |
|
#include "openssl/ssl.h" |
|
#include <openssl/err.h> |
|
#include <vector> |
|
|
|
#include <cryptaux.hxx> |
|
#include <cstdio> |
|
|
|
/*! @defgroup gopenssl C++ Wrapper around OpenSSL API */ |
|
//@{ |
|
//! @defgroup openssllib OpenSSL C++ Library |
|
//! @defgroup opensslexceptions OpenSSL Exceptions |
|
|
|
//! @see gopenssl |
|
namespace openssl { |
|
|
|
|
|
//============================================================================ |
|
//! @addtogroup opensslexceptions |
|
//@{ |
|
|
|
//---------------------------------------------------------------------------- |
|
class exception: public std::exception { |
|
public: |
|
exception(const std::string& reason) throw(): |
|
_what("openssl: "+reason) { |
|
} |
|
~exception() throw() {} |
|
const char* what() const throw() { |
|
return _what.c_str(); |
|
} |
|
private: |
|
std::string _what; |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class openssl_error: public exception { |
|
public: |
|
openssl_error(const std::string& reason) throw(): |
|
exception(reason+'\n'+ERR_error_string(ERR_get_error(), 0)) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class pkcs12_error: public openssl_error { |
|
public: |
|
pkcs12_error(const std::string& reason) throw(): |
|
openssl_error("pkcs12: "+reason) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class x509_error: public openssl_error { |
|
public: |
|
x509_error(const std::string& reason) throw(): |
|
openssl_error("x509: "+reason) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class bio_error: public openssl_error { |
|
public: |
|
bio_error(const std::string& reason) throw(): |
|
openssl_error("bio: "+reason) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class ssl_error: public openssl_error { |
|
public: |
|
ssl_error(const std::string& reason) throw(): |
|
openssl_error("ssl: "+reason) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class allocation_failed: public x509_error { |
|
public: |
|
allocation_failed() throw(): |
|
x509_error("memory allocation failed") { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class x509_decoding_failed: public x509_error { |
|
public: |
|
x509_decoding_failed(const std::string& der) throw(): |
|
x509_error("certificate decoding failed:\n"+crypto::readable(der)) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class undefined_certificate: public x509_error { |
|
public: |
|
undefined_certificate() throw(): |
|
x509_error("certificate must not be 0") { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class x509_parsing_failed: public x509_error { |
|
public: |
|
x509_parsing_failed() throw(): |
|
x509_error("parsing DER encoded certificate failed") { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class x509_copy_failed: public x509_error { |
|
public: |
|
x509_copy_failed() throw(): |
|
x509_error("certificate object copy failed") { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class pkcs12_reading_failed: public pkcs12_error { |
|
public: |
|
pkcs12_reading_failed(const std::string& file) throw(): |
|
pkcs12_error("reading DER encoded p12 file failed: "+file) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class pkcs12_parsing_failed: public pkcs12_error { |
|
public: |
|
pkcs12_parsing_failed(const std::string& file) throw(): |
|
pkcs12_error("parsing DER encoded p12 file failed: "+file) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class pkcs12_no_private_key: public pkcs12_error { |
|
public: |
|
pkcs12_no_private_key() throw(): pkcs12_error("no private key") {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class pkcs12_no_x509: public pkcs12_error { |
|
public: |
|
pkcs12_no_x509() throw(): pkcs12_error("no x509 certificate") {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class cannot_open_file: public exception { |
|
public: |
|
cannot_open_file(const std::string& file) throw(): |
|
exception("cannot open file: "+file) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class bio_connection_failed: public bio_error { |
|
public: |
|
bio_connection_failed(const std::string& hostPort) throw(): |
|
bio_error("connection failed to: "+hostPort) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class bio_closed_connection: public bio_error { |
|
public: |
|
bio_closed_connection() throw(): bio_error("closed connection") {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class bio_read_error: public bio_error { |
|
public: |
|
bio_read_error() throw(): bio_error("read error") {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class bio_write_error: public bio_error { |
|
public: |
|
bio_write_error() throw(): bio_error("write error") {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class ssl_cannot_create_context: public ssl_error { |
|
public: |
|
ssl_cannot_create_context() throw(): ssl_error("cannot create context") {} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class ssl_certificate_file_not_loaded: public ssl_error { |
|
public: |
|
ssl_certificate_file_not_loaded(const std::string& file) throw(): |
|
ssl_error("certificate file not loaded: "+file) { |
|
} |
|
}; |
|
//---------------------------------------------------------------------------- |
|
class ssl_certificate_folder_not_loaded: public ssl_error { |
|
public: |
|
ssl_certificate_folder_not_loaded(const std::string& path) throw(): |
|
ssl_error("certificate folder not loaded: "+path) { |
|
} |
|
}; |
|
//@} |
|
|
|
//! @addtogroup openssllib |
|
//@{ |
|
|
|
//============================================================================ |
|
class Init { |
|
public: |
|
Init() { |
|
SSL_load_error_strings(); |
|
ERR_load_BIO_strings(); |
|
OpenSSL_add_all_algorithms(); |
|
} |
|
}; |
|
|
|
//============================================================================ |
|
class X509 { |
|
public: |
|
//! Construct empty certificate. |
|
X509(): _x509(X509_new()) { |
|
if (!_x509) throw allocation_failed(); |
|
} |
|
//! Initialize from DER encoded cerificate. |
|
X509(const std::string& der): _x509(0) { |
|
const unsigned char* c((const unsigned char*)der.begin().operator->()); |
|
if (!(_x509=d2i_X509(0, &c, der.size())) || |
|
(const char*)c!=der.begin().operator->()+der.size()) |
|
throw x509_decoding_failed(der); |
|
} |
|
X509(const X509& o): _x509(0) { |
|
unsigned char* d(0); |
|
int len(i2d_X509(o._x509, &d)); |
|
if (!len) throw x509_copy_failed(); |
|
const unsigned char* d2(d); |
|
_x509 = d2i_X509(0, &d2, len); |
|
OPENSSL_free(d); |
|
if (!_x509) throw x509_copy_failed(); |
|
} |
|
//! Take over OpenSSL allocated certificate. |
|
X509(::X509 *x509): _x509(x509) { |
|
if (!_x509) throw undefined_certificate(); |
|
} |
|
~X509() { |
|
X509_free(_x509); |
|
} |
|
X509& operator=(const X509& o) { |
|
X509_free(_x509); |
|
_x509 = 0; |
|
unsigned char* d(0); |
|
int len(i2d_X509(o._x509, &d)); |
|
if (!len) throw x509_copy_failed(); |
|
const unsigned char* d2(d); |
|
_x509 = d2i_X509(0, &d2, len); |
|
OPENSSL_free(d); |
|
if (!_x509) throw x509_copy_failed(); |
|
} |
|
//! Get DER encoded subject. |
|
std::string subjectDER() const { |
|
unsigned char* c(0); |
|
int len(i2d_X509_NAME(X509_get_subject_name(_x509), &c)); |
|
std::string res((char*)c, len); |
|
OPENSSL_free(c); |
|
return res; |
|
} |
|
//! Get DER encoded issuer. |
|
std::string issuerDER() const { |
|
unsigned char* c(0); |
|
int len(i2d_X509_NAME(X509_get_issuer_name(_x509), &c)); |
|
std::string res((char*)c, len); |
|
OPENSSL_free(c); |
|
return res; |
|
} |
|
//! Get DER encoded value. |
|
std::string valueDER() const { |
|
unsigned char* c(0); |
|
int len(i2d_X509(_x509, &c)); |
|
std::string res((char*)c, len); |
|
OPENSSL_free(c); |
|
return res; |
|
} |
|
//! Get serial number. |
|
std::string serial() const { |
|
/* @bug http://albistechnologies.com reports: «could be a |
|
failure in openSSL: len too short by 1 if serial number |
|
starts with 00 ASN1_INTEGER* ser = |
|
X509_get_serialNumber(_x509);» */ |
|
ASN1_INTEGER* ser(X509_get_serialNumber(_x509)); |
|
//! @todo requires memory free? |
|
/*! @todo ser->type?!? http://albistechnologies.com prepends |
|
tag and length in the first two char-fields. */ |
|
return std::string((char*)ser->data, ser->length); |
|
} |
|
//! Get id. |
|
std::string id() const { |
|
unsigned char c[SHA_DIGEST_LENGTH]; |
|
SHA1(_x509->cert_info->key->public_key->data, |
|
_x509->cert_info->key->public_key->length, |
|
c); |
|
return std::string((char*)c, SHA_DIGEST_LENGTH); |
|
} |
|
//! Get common name. |
|
std::string commonName() const { |
|
X509_NAME *name(X509_get_subject_name(_x509)); |
|
ASN1_STRING* cn |
|
(X509_NAME_ENTRY_get_data |
|
(X509_NAME_get_entry |
|
(name, X509_NAME_get_index_by_NID(name, NID_commonName, -1)))); |
|
return std::string((char*)M_ASN1_STRING_data(cn), |
|
M_ASN1_STRING_length(cn)); |
|
} |
|
//! Get country name. |
|
std::string countryName() const { |
|
X509_NAME *name(X509_get_subject_name(_x509)); |
|
ASN1_STRING* cn |
|
(X509_NAME_ENTRY_get_data |
|
(X509_NAME_get_entry |
|
(name, X509_NAME_get_index_by_NID(name, NID_countryName, -1)))); |
|
return std::string((char*)M_ASN1_STRING_data(cn), |
|
M_ASN1_STRING_length(cn)); |
|
} |
|
//! Get locality name. |
|
std::string localityName() const { |
|
X509_NAME *name(X509_get_subject_name(_x509)); |
|
ASN1_STRING* cn |
|
(X509_NAME_ENTRY_get_data |
|
(X509_NAME_get_entry |
|
(name, X509_NAME_get_index_by_NID(name, NID_localityName, -1)))); |
|
return std::string((char*)M_ASN1_STRING_data(cn), |
|
M_ASN1_STRING_length(cn)); |
|
} |
|
//! Get state or province name. |
|
std::string stateOrProvinceName() const { |
|
X509_NAME *name(X509_get_subject_name(_x509)); |
|
ASN1_STRING* cn |
|
(X509_NAME_ENTRY_get_data |
|
(X509_NAME_get_entry |
|
(name, X509_NAME_get_index_by_NID |
|
(name, NID_stateOrProvinceName, -1)))); |
|
return std::string((char*)M_ASN1_STRING_data(cn), |
|
M_ASN1_STRING_length(cn)); |
|
} |
|
//! Get organization name. |
|
std::string organizationName() const { |
|
X509_NAME *name(X509_get_subject_name(_x509)); |
|
ASN1_STRING* cn |
|
(X509_NAME_ENTRY_get_data |
|
(X509_NAME_get_entry |
|
(name, X509_NAME_get_index_by_NID |
|
(name, NID_organizationName, -1)))); |
|
return std::string((char*)M_ASN1_STRING_data(cn), |
|
M_ASN1_STRING_length(cn)); |
|
} |
|
//! Get organizational unit name. |
|
std::string organizationalUnitName() const { |
|
X509_NAME *name(X509_get_subject_name(_x509)); |
|
ASN1_STRING* cn |
|
(X509_NAME_ENTRY_get_data |
|
(X509_NAME_get_entry |
|
(name, X509_NAME_get_index_by_NID |
|
(name, NID_organizationalUnitName, -1)))); |
|
return std::string((char*)M_ASN1_STRING_data(cn), |
|
M_ASN1_STRING_length(cn)); |
|
} |
|
private: |
|
::X509* _x509; |
|
}; |
|
|
|
//============================================================================ |
|
class PrivateKey { |
|
public: |
|
PrivateKey(EVP_PKEY *) { |
|
} |
|
}; |
|
|
|
//============================================================================ |
|
class PKCS12 { |
|
|
|
//...............................................................typedefs |
|
public: |
|
typedef std::vector<X509*> X509List; |
|
|
|
//................................................................methods |
|
public: |
|
|
|
//! Read from a PKCS#12 (.p12) file. |
|
PKCS12(std::string filename, std::string password): |
|
_key(0), _cert(0) { |
|
FILE* file(fopen(filename.c_str(), "rb")); |
|
if (!file) throw cannot_open_file(filename); |
|
::PKCS12 *p12(d2i_PKCS12_fp(file, 0)); |
|
fclose(file); |
|
if (!p12) throw pkcs12_reading_failed(filename); |
|
try { |
|
EVP_PKEY *pkey(0); |
|
::X509 *cert(0); |
|
STACK_OF(X509) *ca(0); |
|
if (!PKCS12_parse(p12, password.c_str(), &pkey, &cert, &ca)) |
|
throw pkcs12_parsing_failed(filename); |
|
if (pkey) _key = new PrivateKey(pkey); |
|
if (cert) _cert = new X509(cert); |
|
for (int i(sk_num(ca)); i>0; --i) |
|
_ca.push_back(new X509((::X509*)sk_pop(ca))); |
|
PKCS12_free(p12); |
|
} catch (...) { |
|
PKCS12_free(p12); |
|
throw; |
|
} |
|
} |
|
|
|
~PKCS12() { |
|
delete _key; |
|
delete _cert; |
|
for (X509List::iterator it(_ca.begin()); it!=_ca.end(); ++it) |
|
delete *it; |
|
} |
|
|
|
bool hasPrivateKey() { |
|
return _key; |
|
} |
|
|
|
bool hasCert() { |
|
return _cert; |
|
} |
|
|
|
const PrivateKey& privateKey() { |
|
if (!_key) throw pkcs12_no_private_key(); |
|
return *_key; |
|
}; |
|
|
|
const X509& x509() { |
|
if (!_cert) throw pkcs12_no_x509(); |
|
return *_cert; |
|
}; |
|
|
|
const X509List& ca() const { |
|
return _ca; |
|
} |
|
|
|
private: |
|
PrivateKey *_key; |
|
X509 *_cert; |
|
X509List _ca; |
|
}; |
|
|
|
//============================================================================ |
|
class BIO { |
|
|
|
private: |
|
|
|
BIO(const BIO&); |
|
BIO& operator=(const BIO&); |
|
|
|
public: |
|
|
|
BIO(): _bio(0) {} |
|
|
|
~BIO() { |
|
try { |
|
close(); |
|
} catch (...) { |
|
if (!std::uncaught_exception()) throw; |
|
} |
|
} |
|
|
|
BIO& connect(const std::string& hostPort) { |
|
close(); |
|
if (!(_bio=BIO_new_connect(const_cast<char*>(hostPort.c_str()))) || |
|
BIO_do_connect(_bio)<=0) |
|
throw bio_connection_failed(hostPort); |
|
return *this; |
|
} |
|
|
|
BIO& operator>>(std::string& s) { |
|
s += read(); |
|
return *this; |
|
} |
|
|
|
BIO& operator<<(const std::string& s) { |
|
return write(s); |
|
} |
|
|
|
/*! @todo How can I find out, whether there's still data |
|
available, or whether a response has been finished and |
|
server is waiting for next request, but connection is still |
|
open? */ |
|
std::string read() { |
|
if (!_bio) throw bio_closed_connection(); |
|
const int BUFF_SZ(1024); |
|
char buff[BUFF_SZ]; |
|
int x(BIO_read(_bio, buff, BUFF_SZ)); |
|
if (x<=0) |
|
if (BIO_should_retry(_bio)) return read(); |
|
else throw bio_read_error(); |
|
return std::string(buff, x); |
|
} |
|
|
|
BIO& write(const std::string& s) { |
|
int x(BIO_write(_bio, s.begin().operator->(), s.size())); |
|
if (x<=0) |
|
if (BIO_should_retry(_bio)) return write(s); |
|
else throw bio_write_error(); |
|
else |
|
if (x<s.size()) return write(s.substr(x)); |
|
return *this; |
|
} |
|
|
|
void close() { |
|
BIO_free_all(_bio); |
|
} |
|
|
|
private: |
|
|
|
friend class SSL; |
|
::BIO* _bio; |
|
|
|
}; |
|
|
|
//! Read authorized certificate from a single pem file. |
|
class TrustStore { |
|
public: |
|
TrustStore(const std::string& pathToPemFile): |
|
_file(pathToPemFile) { |
|
} |
|
const std::string& file() const { |
|
return _file; |
|
} |
|
private: |
|
std::string _file; |
|
}; |
|
|
|
class CertificateFolder { |
|
public: |
|
CertificateFolder(const std::string& certificateFolder): |
|
_path(certificateFolder) { |
|
} |
|
const std::string& path() const { |
|
return _path; |
|
} |
|
private: |
|
std::string _path; |
|
}; |
|
|
|
class SSL { |
|
private: |
|
SSL(); |
|
SSL(const SSL&); |
|
SSL& operator=(const SSL&); |
|
public: |
|
SSL(const TrustStore& file): |
|
_ctx(SSL_CTX_new(SSLv23_client_method())), |
|
_ssl(0) { |
|
if (!_ctx) throw ssl_cannot_create_context(); |
|
if (!SSL_CTX_load_verify_locations(_ctx, file.file().c_str(), 0)) |
|
throw ssl_certificate_file_not_loaded(file.file()); |
|
} |
|
SSL(const CertificateFolder& folder): |
|
_ctx(SSL_CTX_new(SSLv23_client_method())), |
|
_ssl(0) { |
|
if (!_ctx) throw ssl_cannot_create_context(); |
|
if (!SSL_CTX_load_verify_locations(_ctx, 0, folder.path().c_str())) |
|
throw ssl_certificate_folder_not_loaded(folder.path()); |
|
} |
|
~SSL() { |
|
close(); |
|
SSL_CTX_free(_ctx); |
|
} |
|
BIO& connect(const std::string& hostPort) { |
|
close(); |
|
if (!(_bio._bio=BIO_new_ssl_connect(_ctx))) |
|
throw bio_connection_failed(hostPort); |
|
BIO_get_ssl(_bio._bio, &_ssl); |
|
if (!_ssl) |
|
SSL_set_mode(_ssl, SSL_MODE_AUTO_RETRY); |
|
BIO_set_conn_hostname(_bio._bio, const_cast<char*>(hostPort.c_str())); |
|
if (BIO_do_connect(_bio._bio)<=0) throw bio_connection_failed(hostPort); |
|
return _bio; |
|
} |
|
SSL& close() { |
|
_bio.close(); |
|
_ssl = 0; //! @todo is this correct? <-- |
|
return *this; |
|
} |
|
bool verifyResult() { |
|
return _ssl && SSL_get_verify_result(_ssl)==X509_V_OK; |
|
} |
|
private: |
|
SSL_CTX *_ctx; |
|
::SSL *_ssl; |
|
BIO _bio; |
|
}; |
|
|
|
//@} |
|
|
|
} |
|
//@} |
|
|
|
#endif
|
|
|