diff options
Diffstat (limited to 'tqca-tls.cpp')
-rw-r--r-- | tqca-tls.cpp | 1510 |
1 files changed, 1510 insertions, 0 deletions
diff --git a/tqca-tls.cpp b/tqca-tls.cpp new file mode 100644 index 0000000..df9eff1 --- /dev/null +++ b/tqca-tls.cpp @@ -0,0 +1,1510 @@ +/* + * qca-tls.cpp - TLS plugin for TQCA + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "tqca-tls.h" + +#include <tqregexp.h> + +#include <openssl/sha.h> +#include <openssl/md5.h> +#include <openssl/evp.h> +#include <openssl/bio.h> +#include <openssl/pem.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> +#include <openssl/x509v3.h> +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/rand.h> + +#ifndef OSSL_097 +#define NO_AES +#endif + +static TQByteArray lib_randomArray(int size) +{ + if(RAND_status() == 0) { + srand(time(NULL)); + char buf[128]; + for(int n = 0; n < 128; ++n) + buf[n] = rand(); + RAND_seed(buf, 128); + } + TQByteArray a(size); + RAND_bytes((unsigned char *)a.data(), a.size()); + return a; +} + +static bool lib_generateKeyIV(const EVP_CIPHER *_type, const TQByteArray &data, const TQByteArray &salt, TQByteArray *key, TQByteArray *iv, int keysize=-1) +{ + TQByteArray k, i; + unsigned char *kp = 0; + unsigned char *ip = 0; +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + EVP_CIPHER type = *_type; + EVP_CIPHER *loctype = &type; + if(keysize != -1) + type.key_len = keysize; +#else + EVP_CIPHER *loctype = EVP_CIPHER_meth_dup(_type); + Q_UNUSED(keysize) +#endif + if(key) { + k.resize(EVP_CIPHER_key_length(loctype)); + kp = (unsigned char *)k.data(); + } + if(iv) { + i.resize(EVP_CIPHER_iv_length(loctype)); + ip = (unsigned char *)i.data(); + } + int res = EVP_BytesToKey(loctype, EVP_sha1(), (unsigned char *)salt.data(), (unsigned char *)data.data(), data.size(), 1, kp, ip); +#if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) + EVP_CIPHER_meth_free(loctype); +#endif + if (!res) + return false; + if(key) + *key = k; + if(iv) + *iv = i; + return true; +} + +static void appendArray(TQByteArray *a, const TQByteArray &b) +{ + int oldsize = a->size(); + a->resize(oldsize + b.size()); + memcpy(a->data() + oldsize, b.data(), b.size()); +} + +static TQByteArray bio2buf(BIO *b) +{ + TQByteArray buf; + while(1) { + char block[1024]; + int ret = BIO_read(b, block, 1024); + int oldsize = buf.size(); + buf.resize(oldsize + ret); + memcpy(buf.data() + oldsize, block, ret); + if(ret != 1024) + break; + } + BIO_free(b); + return buf; +} + +class SHA1Context : public TQCA_HashContext +{ +public: + SHA1Context() + { + reset(); + } + + TQCA_HashContext *clone() + { + return new SHA1Context(*this); + } + + void reset() + { + SHA1_Init(&c); + } + + void update(const char *in, unsigned int len) + { + SHA1_Update(&c, in, len); + } + + void final(TQByteArray *out) + { + TQByteArray buf(20); + SHA1_Final((unsigned char *)buf.data(), &c); + *out = buf; + } + + SHA_CTX c; +}; + +class MD5Context : public TQCA_HashContext +{ +public: + MD5Context() + { + reset(); + } + + TQCA_HashContext *clone() + { + return new MD5Context(*this); + } + + void reset() + { + MD5_Init(&c); + } + + void update(const char *in, unsigned int len) + { + MD5_Update(&c, in, len); + } + + void final(TQByteArray *out) + { + TQByteArray buf(16); + MD5_Final((unsigned char *)buf.data(), &c); + *out = buf; + } + + MD5_CTX c; +}; + +class EVPCipherContext : public TQCA_CipherContext +{ +public: + EVPCipherContext() + { + type = 0; + } + + virtual ~EVPCipherContext() + { + if(type) { +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + EVP_CIPHER_CTX_cleanup(c); + OPENSSL_free(c); +#else + EVP_CIPHER_CTX_free(c); +#endif + type = 0; + } + } + + TQCA_CipherContext *clone() + { + EVPCipherContext *cc = cloneSelf(); + cc->r = r.copy(); + return cc; + } + + virtual EVPCipherContext *cloneSelf() const=0; + virtual const EVP_CIPHER *getType(int mode) const=0; + + int keySize() { return EVP_CIPHER_key_length(getType(TQCA::CBC)); } + int blockSize() { return EVP_CIPHER_block_size(getType(TQCA::CBC)); } + + bool generateKey(char *out, int keysize) + { + TQByteArray a; + if(!lib_generateKeyIV(getType(TQCA::CBC), lib_randomArray(128), lib_randomArray(2), &a, 0, keysize)) + return false; + memcpy(out, a.data(), a.size()); + return true; + } + + bool generateIV(char *out) + { + TQByteArray a; + if(!lib_generateKeyIV(getType(TQCA::CBC), lib_randomArray(128), lib_randomArray(2), 0, &a)) + return false; + memcpy(out, a.data(), a.size()); + return true; + } + + bool setup(int _dir, int mode, const char *key, int keysize, const char *iv, bool _pad) + { + dir = _dir; + pad = _pad; + type = getType(mode); + r.resize(0); +#if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) + c = (EVP_CIPHER_CTX*)OPENSSL_malloc(sizeof(EVP_CIPHER_CTX)); + EVP_CIPHER_CTX_init(c); +#else + c = EVP_CIPHER_CTX_new(); +#endif + + if(dir == TQCA::Encrypt) { + if(!EVP_EncryptInit(c, type, NULL, NULL)) + return false; + if(keysize != EVP_CIPHER_key_length(type)) + EVP_CIPHER_CTX_set_key_length(c, keysize); + if(!EVP_EncryptInit(c, NULL, (unsigned char *)key, (unsigned char *)iv)) + return false; + } + else { + if(!EVP_DecryptInit(c, type, NULL, NULL)) + return false; + if(keysize != EVP_CIPHER_key_length(type)) + EVP_CIPHER_CTX_set_key_length(c, keysize); + if(!EVP_DecryptInit(c, NULL, (unsigned char *)key, (unsigned char *)iv)) + return false; + } + return true; + } + + bool update(const char *in, unsigned int len) + { + TQByteArray result(len + EVP_CIPHER_block_size(type)); + int olen; + if(dir == TQCA::Encrypt || !pad) { + if(!EVP_EncryptUpdate(c, (unsigned char *)result.data(), &olen, (unsigned char *)in, len)) + return false; + } + else { + if(!EVP_DecryptUpdate(c, (unsigned char *)result.data(), &olen, (unsigned char *)in, len)) + return false; + } + result.resize(olen); + appendArray(&r, result); + return true; + } + + bool final(TQByteArray *out) + { + if(pad) { + TQByteArray result(EVP_CIPHER_block_size(type)); + int olen; + if(dir == TQCA::Encrypt) { + if(!EVP_EncryptFinal_ex(c, (unsigned char *)result.data(), &olen)) + return false; + } + else { + if(!EVP_DecryptFinal_ex(c, (unsigned char *)result.data(), &olen)) + return false; + } + result.resize(olen); + appendArray(&r, result); + } + + *out = r.copy(); + r.resize(0); + return true; + } + + EVP_CIPHER_CTX *c; + const EVP_CIPHER *type; + TQByteArray r; + int dir; + bool pad; +}; + +class BlowFishContext : public EVPCipherContext +{ +public: + EVPCipherContext *cloneSelf() const { return new BlowFishContext(*this); } + const EVP_CIPHER *getType(int mode) const + { + if(mode == TQCA::CBC) + return EVP_bf_cbc(); + else if(mode == TQCA::CFB) + return EVP_bf_cfb(); + else + return 0; + } +}; + +class TripleDESContext : public EVPCipherContext +{ +public: + EVPCipherContext *cloneSelf() const { return new TripleDESContext(*this); } + const EVP_CIPHER *getType(int mode) const + { + if(mode == TQCA::CBC) + return EVP_des_ede3_cbc(); + else if(mode == TQCA::CFB) + return EVP_des_ede3_cfb(); + else + return 0; + } +}; + +#ifndef NO_AES +class AES128Context : public EVPCipherContext +{ +public: + EVPCipherContext *cloneSelf() const { return new AES128Context(*this); } + const EVP_CIPHER *getType(int mode) const + { + if(mode == TQCA::CBC) + return EVP_aes_128_cbc(); + else if(mode == TQCA::CFB) + return EVP_aes_128_cfb(); + else + return 0; + } +}; + +class AES256Context : public EVPCipherContext +{ +public: + EVPCipherContext *cloneSelf() const { return new AES256Context(*this); } + const EVP_CIPHER *getType(int mode) const + { + if(mode == TQCA::CBC) + return EVP_aes_256_cbc(); + else if(mode == TQCA::CFB) + return EVP_aes_256_cfb(); + else + return 0; + } +}; +#endif + +class RSAKeyContext : public TQCA_RSAKeyContext +{ +public: + RSAKeyContext() + { + pub = 0; + sec = 0; + } + + ~RSAKeyContext() + { + reset(); + } + + void reset() + { + if(pub) { + RSA_free(pub); + pub = 0; + } + if(sec) { + RSA_free(sec); + sec = 0; + } + } + + void separate(RSA *r, RSA **_pub, RSA **_sec) + { + // public + unsigned char *buf, *p; + int len = i2d_RSAPublicKey(r, NULL); + if(len > 0) { + buf = (unsigned char *)malloc(len); + p = buf; + i2d_RSAPublicKey(r, &p); + p = buf; +#ifdef OSSL_097 + *_pub = d2i_RSAPublicKey(NULL, (const unsigned char **)&p, len); +#else + *_pub = d2i_RSAPublicKey(NULL, (unsigned char **)&p, len); +#endif + free(buf); + } + + len = i2d_RSAPrivateKey(r, NULL); + if(len > 0) { + buf = (unsigned char *)malloc(len); + p = buf; + i2d_RSAPrivateKey(r, &p); + p = buf; +#ifdef OSSL_097 + *_sec = d2i_RSAPrivateKey(NULL, (const unsigned char **)&p, len); +#else + *_sec = d2i_RSAPrivateKey(NULL, (unsigned char **)&p, len); +#endif + free(buf); + } + } + + bool isNull() const + { + if(!pub && !sec) + return true; + return false; + } + + bool havePublic() const + { + return pub ? true : false; + } + + bool havePrivate() const + { + return sec ? true : false; + } + + bool createFromDER(const char *in, unsigned int len) + { + RSA *r; + void *p; + + // private? + p = (void *)in; +#ifdef OSSL_097 + r = d2i_RSAPrivateKey(NULL, (const unsigned char **)&p, len); +#else + r = d2i_RSAPrivateKey(NULL, (unsigned char **)&p, len); +#endif + if(r) { + reset(); + + // private means both, I think, so separate them + separate(r, &pub, &sec); + return true; + } + else { + // public? + p = (void *)in; +#ifdef OSSL_097 + r = d2i_RSAPublicKey(NULL, (const unsigned char **)&p, len); +#else + r = d2i_RSAPublicKey(NULL, (unsigned char **)&p, len); +#endif + if(!r) { + // try this other public function, for whatever reason + p = (void *)in; + r = d2i_RSA_PUBKEY(NULL, (const unsigned char **)&p, len); + } + if(r) { + if(pub) { + RSA_free(pub); + } + pub = r; + return true; + } + } + + return false; + } + + bool createFromPEM(const char *in, unsigned int len) + { + BIO *bi; + + // private? + bi = BIO_new(BIO_s_mem()); + BIO_write(bi, in, len); + RSA *r = PEM_read_bio_RSAPrivateKey(bi, NULL, NULL, NULL); + BIO_free(bi); + if(r) { + reset(); + separate(r, &pub, &sec); + return true; + } + else { + // public? + bi = BIO_new(BIO_s_mem()); + BIO_write(bi, in, len); + r = PEM_read_bio_RSAPublicKey(bi, NULL, NULL, NULL); + BIO_free(bi); + if(r) { + if(pub) { + RSA_free(pub); + } + pub = r; + return true; + } + } + + return false; + } + + bool createFromNative(void *in) + { + reset(); + separate((RSA *)in, &pub, &sec); + return true; + } + + bool generate(unsigned int bits) + { + BIGNUM *bign = BN_new(); + if (BN_set_word(bign, RSA_F4) != 1) + { + BN_free(bign); + return false; + } + RSA *r = RSA_new(); + if(!r) + { + BN_free(bign); + return false; + } + RSA_generate_key_ex(r, bits, bign, NULL); + separate(r, &pub, &sec); + RSA_free(r); + BN_free(bign); + return true; + } + + TQCA_RSAKeyContext *clone() const + { + // deep copy + RSAKeyContext *c = new RSAKeyContext; + if(pub) { + c->pub = RSAPublicKey_dup(pub); + } + if(sec) { + c->sec = RSAPrivateKey_dup(sec); + } + return c; + } + + bool toDER(TQByteArray *out, bool publicOnly) + { + if(sec && !publicOnly) { + int len = i2d_RSAPrivateKey(sec, NULL); + TQByteArray buf(len); + unsigned char *p; + p = (unsigned char *)buf.data(); + i2d_RSAPrivateKey(sec, &p); + *out = buf; + return true; + } + else if(pub) { + int len = i2d_RSAPublicKey(pub, NULL); + TQByteArray buf(len); + unsigned char *p; + p = (unsigned char *)buf.data(); + i2d_RSAPublicKey(pub, &p); + *out = buf; + return true; + } + else + return false; + } + + bool toPEM(TQByteArray *out, bool publicOnly) + { + if(sec && !publicOnly) { + BIO *bo = BIO_new(BIO_s_mem()); + PEM_write_bio_RSAPrivateKey(bo, sec, NULL, NULL, 0, NULL, NULL); + *out = bio2buf(bo); + return true; + } + else if(pub) { + BIO *bo = BIO_new(BIO_s_mem()); + PEM_write_bio_RSAPublicKey(bo, pub); + *out = bio2buf(bo); + return true; + } + else + return false; + + } + + bool encrypt(const TQByteArray &in, TQByteArray *out, bool oaep) + { + if(!pub) + return false; + + int size = RSA_size(pub); + int flen = in.size(); + if(oaep) { + if(flen >= size - 41) + flen = size - 41; + } + else { + if(flen >= size - 11) + flen = size - 11; + } + TQByteArray result(size); + unsigned char *from = (unsigned char *)in.data(); + unsigned char *to = (unsigned char *)result.data(); + int ret = RSA_public_encrypt(flen, from, to, pub, oaep ? RSA_PKCS1_OAEP_PADDING : RSA_PKCS1_PADDING); + if(ret == -1) + return false; + result.resize(ret); + + *out = result; + return true; + } + + bool decrypt(const TQByteArray &in, TQByteArray *out, bool oaep) + { + if(!sec) + return false; + + int size = RSA_size(sec); + int flen = in.size(); + TQByteArray result(size); + unsigned char *from = (unsigned char *)in.data(); + unsigned char *to = (unsigned char *)result.data(); + int ret = RSA_private_decrypt(flen, from, to, sec, oaep ? RSA_PKCS1_OAEP_PADDING : RSA_PKCS1_PADDING); + if(ret == -1) + return false; + result.resize(ret); + + *out = result; + return true; + } + + RSA *pub, *sec; +}; + +static TQValueList<TQCA_CertProperty> nameToProperties(struct X509_name_st *name) +{ + TQValueList<TQCA_CertProperty> list; + + for(int n = 0; n < X509_NAME_entry_count(name); ++n) { + X509_NAME_ENTRY *ne = X509_NAME_get_entry(name, n); + TQCA_CertProperty p; + + ASN1_OBJECT *ao = X509_NAME_ENTRY_get_object(ne); + int nid = OBJ_obj2nid(ao); + if(nid == NID_undef) + continue; + p.var = OBJ_nid2sn(nid); + + ASN1_STRING *as = X509_NAME_ENTRY_get_data(ne); + TQCString c; + c.resize(as->length+1); + strncpy(c.data(), (char *)as->data, as->length); + p.val = TQString::fromLatin1(c); + list += p; + } + + return list; +} + +// (taken from tdelibs) -- Justin +// +// This code is mostly taken from OpenSSL v0.9.5a +// by Eric Young +TQDateTime ASN1_UTCTIME_TQDateTime(ASN1_UTCTIME *tm, int *isGmt) +{ + TQDateTime qdt; + char *v; + int gmt=0; + int i; + int y=0,M=0,d=0,h=0,m=0,s=0; + TQDate qdate; + TQTime qtime; + + i = tm->length; + v = (char *)tm->data; + + if (i < 10) goto auq_err; + if (v[i-1] == 'Z') gmt=1; + for (i=0; i<10; i++) + if ((v[i] > '9') || (v[i] < '0')) goto auq_err; + y = (v[0]-'0')*10+(v[1]-'0'); + if (y < 50) y+=100; + M = (v[2]-'0')*10+(v[3]-'0'); + if ((M > 12) || (M < 1)) goto auq_err; + d = (v[4]-'0')*10+(v[5]-'0'); + h = (v[6]-'0')*10+(v[7]-'0'); + m = (v[8]-'0')*10+(v[9]-'0'); + if ( (v[10] >= '0') && (v[10] <= '9') && + (v[11] >= '0') && (v[11] <= '9')) + s = (v[10]-'0')*10+(v[11]-'0'); + + // localize the date and display it. + qdate.setYMD(y+1900, M, d); + qtime.setHMS(h,m,s); + qdt.setDate(qdate); qdt.setTime(qtime); +auq_err: + if (isGmt) *isGmt = gmt; + return qdt; +} + +// (adapted from tdelibs) -- Justin +static bool cnMatchesAddress(const TQString &_cn, const TQString &peerHost) +{ + TQString cn = _cn.stripWhiteSpace().lower(); + TQRegExp rx; + + // Check for invalid characters + if(TQRegExp("[^a-zA-Z0-9\\.\\*\\-]").search(cn) >= 0) + return false; + + // Domains can legally end with '.'s. We don't need them though. + while(cn.endsWith(".")) + cn.truncate(cn.length()-1); + + // Do not let empty CN's get by!! + if(cn.isEmpty()) + return false; + + // Check for IPv4 address + rx.setPattern("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}"); + if(rx.exactMatch(peerHost)) + return peerHost == cn; + + // Check for IPv6 address here... + rx.setPattern("^\\[.*\\]$"); + if(rx.exactMatch(peerHost)) + return peerHost == cn; + + if(cn.contains('*')) { + // First make sure that there are at least two valid parts + // after the wildcard (*). + TQStringList parts = TQStringList::split('.', cn, false); + + while(parts.count() > 2) + parts.remove(parts.begin()); + + if(parts.count() != 2) { + return false; // we don't allow *.root - that's bad + } + + if(parts[0].contains('*') || parts[1].contains('*')) { + return false; + } + + // RFC2818 says that *.example.com should match against + // foo.example.com but not bar.foo.example.com + // (ie. they must have the same number of parts) + if(TQRegExp(cn, false, true).exactMatch(peerHost) && + TQStringList::split('.', cn, false).count() == + TQStringList::split('.', peerHost, false).count()) + return true; + + return false; + } + + // We must have an exact match in this case (insensitive though) + // (note we already did .lower()) + if(cn == peerHost) + return true; + return false; +} + +class CertContext : public TQCA_CertContext +{ +public: + CertContext() + { + x = 0; + } + + ~CertContext() + { + reset(); + } + + TQCA_CertContext *clone() const + { + CertContext *c = new CertContext(*this); + if(x) { + c->x = X509_dup(x); + } + return c; + } + + void reset() + { + if(x) { + X509_free(x); + x = 0; + + serial = ""; + v_subject = ""; + v_issuer = ""; + cp_subject.clear(); + cp_issuer.clear(); + na = TQDateTime(); + nb = TQDateTime(); + } + } + + bool isNull() const + { + return (x ? false: true); + } + + bool createFromDER(const char *in, unsigned int len) + { + const unsigned char *p = (const unsigned char *)in; + X509 *t = d2i_X509(NULL, &p, len); + if(!t) + return false; + fromX509(t); + X509_free(t); + return true; + } + + bool createFromPEM(const char *in, unsigned int len) + { + BIO *bi = BIO_new(BIO_s_mem()); + BIO_write(bi, in, len); + X509 *t = PEM_read_bio_X509(bi, NULL, NULL, NULL); + BIO_free(bi); + if(!t) + return false; + fromX509(t); + X509_free(t); + return true; + } + + bool toDER(TQByteArray *out) + { + int len = i2d_X509(x, NULL); + TQByteArray buf(len); + unsigned char *p = (unsigned char *)buf.data(); + i2d_X509(x, &p); + *out = buf; + return true; + } + + bool toPEM(TQByteArray *out) + { + BIO *bo = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(bo, x); + *out = bio2buf(bo); + return true; + } + + void fromX509(X509 *t) + { + reset(); + x = X509_dup(t); + + // serial number + ASN1_INTEGER *ai = X509_get_serialNumber(x); + if(ai) { + char *rep = i2s_ASN1_INTEGER(NULL, ai); + serial = rep; + OPENSSL_free(rep); + } + + // validity dates + nb = ASN1_UTCTIME_TQDateTime(X509_get_notBefore(x), NULL); + na = ASN1_UTCTIME_TQDateTime(X509_get_notAfter(x), NULL); + + // extract the subject/issuer strings + struct X509_name_st *sn = X509_get_subject_name(x); + struct X509_name_st *in = X509_get_issuer_name(x); + char buf[1024]; + X509_NAME_oneline(sn, buf, 1024); + v_subject = buf; + X509_NAME_oneline(in, buf, 1024); + v_issuer = buf; + + // extract the subject/issuer contents + cp_subject = nameToProperties(sn); + cp_issuer = nameToProperties(in); + } + + TQString serialNumber() const + { + return serial; + } + + TQString subjectString() const + { + return v_subject; + } + + TQString issuerString() const + { + return v_issuer; + } + + TQValueList<TQCA_CertProperty> subject() const + { + return cp_subject; + } + + TQValueList<TQCA_CertProperty> issuer() const + { + return cp_issuer; + } + + TQDateTime notBefore() const + { + return nb; + } + + TQDateTime notAfter() const + { + return na; + } + + bool matchesAddress(const TQString &realHost) const + { + TQString peerHost = realHost.stripWhiteSpace(); + while(peerHost.endsWith(".")) + peerHost.truncate(peerHost.length()-1); + peerHost = peerHost.lower(); + + TQString cn; + for(TQValueList<TQCA_CertProperty>::ConstIterator it = cp_subject.begin(); it != cp_subject.end(); ++it) { + if((*it).var == "CN") { + cn = (*it).val; + break; + } + } + if(cnMatchesAddress(cn, peerHost)) + return true; + return false; + } + + X509 *x; + TQString serial, v_subject, v_issuer; + TQValueList<TQCA_CertProperty> cp_subject, cp_issuer; + TQDateTime nb, na; +}; + +static bool ssl_init = false; +class TLSContext : public TQCA_TLSContext +{ +public: + enum { Good, TryAgain, Bad }; + enum { Idle, Connect, Accept, Handshake, Active, Closing }; + + bool serv; + int mode; + TQByteArray sendQueue, recvQueue; + + CertContext *cert; + RSAKeyContext *key; + + SSL *ssl; + SSL_METHOD *method; + SSL_CTX *context; + BIO *rbio, *wbio; + CertContext cc; + int vr; + bool v_eof; + + TLSContext() + { + if(!ssl_init) { + SSL_library_init(); + SSL_load_error_strings(); + ssl_init = true; + } + + ssl = 0; + context = 0; + cert = 0; + key = 0; + } + + ~TLSContext() + { + reset(); + } + + void reset() + { + if(ssl) { + SSL_free(ssl); + ssl = 0; + } + if(context) { + SSL_CTX_free(context); + context = 0; + } + if(cert) { + delete cert; + cert = 0; + } + if(key) { + delete key; + key = 0; + } + + sendQueue.resize(0); + recvQueue.resize(0); + mode = Idle; + cc.reset(); + vr = TQCA::TLS::Unknown; + v_eof = false; + } + + bool eof() const + { + return v_eof; + } + + bool startClient(const TQPtrList<TQCA_CertContext> &store, const TQCA_CertContext &_cert, const TQCA_RSAKeyContext &_key) + { + reset(); + serv = false; + method = const_cast<SSL_METHOD*>(SSLv23_client_method()); + + if(!setup(store, _cert, _key)) + return false; + + mode = Connect; + return true; + } + + bool startServer(const TQPtrList<TQCA_CertContext> &store, const TQCA_CertContext &_cert, const TQCA_RSAKeyContext &_key) + { + reset(); + serv = true; + method = const_cast<SSL_METHOD*>(SSLv23_server_method()); + + if(!setup(store, _cert, _key)) + return false; + + mode = Accept; + return true; + } + + bool setup(const TQPtrList<TQCA_CertContext> &list, const TQCA_CertContext &_cc, const TQCA_RSAKeyContext &kc) + { + context = SSL_CTX_new(method); + if(!context) { + reset(); + return false; + } + + // load the cert store + if(!list.isEmpty()) { + X509_STORE *store = SSL_CTX_get_cert_store(context); + TQPtrListIterator<TQCA_CertContext> it(list); + for(CertContext *i; (i = (CertContext *)it.current()); ++it) + X509_STORE_add_cert(store, i->x); + } + + ssl = SSL_new(context); + if(!ssl) { + reset(); + return false; + } + SSL_set_ssl_method(ssl, method); // can this return error? + + // setup the memory bio + rbio = BIO_new(BIO_s_mem()); + wbio = BIO_new(BIO_s_mem()); + + // this passes control of the bios to ssl. we don't need to free them. + SSL_set_bio(ssl, rbio, wbio); + + // setup the cert to send + if(!_cc.isNull() && !kc.isNull()) { + cert = static_cast<CertContext*>(_cc.clone()); + key = static_cast<RSAKeyContext*>(kc.clone()); + if(SSL_use_certificate(ssl, cert->x) != 1) { + reset(); + return false; + } + if(SSL_use_RSAPrivateKey(ssl, key->sec) != 1) { + reset(); + return false; + } + } + + return true; + } + + int handshake(const TQByteArray &in, TQByteArray *out) + { + if(!in.isEmpty()) + BIO_write(rbio, in.data(), in.size()); + + if(mode == Connect) { + int ret = doConnect(); + if(ret == Good) { + mode = Handshake; + } + else if(ret == Bad) { + reset(); + return Error; + } + } + + if(mode == Accept) { + int ret = doAccept(); + if(ret == Good) { + getCert(); + mode = Active; + } + else if(ret == Bad) { + reset(); + return Error; + } + } + + if(mode == Handshake) { + int ret = doHandshake(); + if(ret == Good) { + getCert(); + mode = Active; + } + else if(ret == Bad) { + reset(); + return Error; + } + } + + // process outgoing + *out = readOutgoing(); + + if(mode == Active) + return Success; + else + return Continue; + } + + int shutdown(const TQByteArray &in, TQByteArray *out) + { + if(!in.isEmpty()) + BIO_write(rbio, in.data(), in.size()); + + int ret = doShutdown(); + if(ret == Bad) { + reset(); + return Error; + } + + *out = readOutgoing(); + + if(ret == Good) { + mode = Idle; + return Success; + } + else { + mode = Closing; + return Continue; + } + } + + void getCert() + { + // verify the certificate + int code = TQCA::TLS::Unknown; + X509 *x = SSL_get_peer_certificate(ssl); + if(x) { + cc.fromX509(x); + X509_free(x); + int ret = SSL_get_verify_result(ssl); + if(ret == X509_V_OK) + code = TQCA::TLS::Valid; + else + code = resultToCV(ret); + } + else { + cc.reset(); + code = TQCA::TLS::NoCert; + } + vr = code; + } + + bool encode(const TQByteArray &plain, TQByteArray *to_net, int *enc) + { + if(mode != Active) + return false; + appendArray(&sendQueue, plain); + + int encoded = 0; + if(sendQueue.size() > 0) { + int ret = SSL_write(ssl, sendQueue.data(), sendQueue.size()); + + enum { Good, Continue, Done, Error }; + int m; + if(ret <= 0) { + int x = SSL_get_error(ssl, ret); + if(x == SSL_ERROR_WANT_READ || x == SSL_ERROR_WANT_WRITE) + m = Continue; + else if(x == SSL_ERROR_ZERO_RETURN) + m = Done; + else + m = Error; + } + else { + m = Good; + encoded = ret; + int newsize = sendQueue.size() - encoded; + char *r = sendQueue.data(); + memmove(r, r + encoded, newsize); + sendQueue.resize(newsize); + } + + if(m == Done) { + sendQueue.resize(0); + v_eof = true; + return false; + } + if(m == Error) { + sendQueue.resize(0); + return false; + } + } + + *to_net = readOutgoing(); + *enc = encoded; + return true; + } + + bool decode(const TQByteArray &from_net, TQByteArray *plain, TQByteArray *to_net) + { + if(mode != Active) + return false; + if(!from_net.isEmpty()) + BIO_write(rbio, from_net.data(), from_net.size()); + + TQByteArray a; + while(!v_eof) { + a.resize(8192); + int ret = SSL_read(ssl, a.data(), a.size()); + if(ret > 0) { + if(ret != (int)a.size()) + a.resize(ret); + appendArray(&recvQueue, a); + } + else if(ret <= 0) { + int x = SSL_get_error(ssl, ret); + if(x == SSL_ERROR_WANT_READ || x == SSL_ERROR_WANT_WRITE) + break; + else if(x == SSL_ERROR_ZERO_RETURN) + v_eof = true; + else + return false; + } + } + + *plain = recvQueue.copy(); + recvQueue.resize(0); + + // could be outgoing data also + *to_net = readOutgoing(); + return true; + } + + TQByteArray unprocessed() + { + TQByteArray a; + int size = BIO_pending(rbio); + if(size <= 0) + return a; + a.resize(size); + + int r = BIO_read(rbio, a.data(), size); + if(r <= 0) { + a.resize(0); + return a; + } + if(r != size) + a.resize(r); + return a; + } + + TQByteArray readOutgoing() + { + TQByteArray a; + int size = BIO_pending(wbio); + if(size <= 0) + return a; + a.resize(size); + + int r = BIO_read(wbio, a.data(), size); + if(r <= 0) { + a.resize(0); + return a; + } + if(r != size) + a.resize(r); + return a; + } + + int doConnect() + { + int ret = SSL_connect(ssl); + if(ret < 0) { + int x = SSL_get_error(ssl, ret); + if(x == SSL_ERROR_WANT_CONNECT || x == SSL_ERROR_WANT_READ || x == SSL_ERROR_WANT_WRITE) + return TryAgain; + else + return Bad; + } + else if(ret == 0) + return Bad; + return Good; + } + + int doAccept() + { + int ret = SSL_accept(ssl); + if(ret < 0) { + int x = SSL_get_error(ssl, ret); + if(x == SSL_ERROR_WANT_CONNECT || x == SSL_ERROR_WANT_READ || x == SSL_ERROR_WANT_WRITE) + return TryAgain; + else + return Bad; + } + else if(ret == 0) + return Bad; + return Good; + } + + int doHandshake() + { + int ret = SSL_do_handshake(ssl); + if(ret < 0) { + int x = SSL_get_error(ssl, ret); + if(x == SSL_ERROR_WANT_READ || x == SSL_ERROR_WANT_WRITE) + return TryAgain; + else + return Bad; + } + else if(ret == 0) + return Bad; + return Good; + } + + int doShutdown() + { + int ret = SSL_shutdown(ssl); + if(ret >= 1) + return Good; + else { + if(ret == 0) + return TryAgain; + int x = SSL_get_error(ssl, ret); + if(x == SSL_ERROR_WANT_READ || x == SSL_ERROR_WANT_WRITE) + return TryAgain; + return Bad; + } + } + + TQCA_CertContext *peerCertificate() const + { + return cc.clone(); + } + + int validityResult() const + { + return vr; + } + + int resultToCV(int ret) const + { + int rc; + + switch(ret) { + case X509_V_ERR_CERT_REJECTED: + rc = TQCA::TLS::Rejected; + break; + case X509_V_ERR_CERT_UNTRUSTED: + rc = TQCA::TLS::Untrusted; + break; + case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE: + case X509_V_ERR_CERT_SIGNATURE_FAILURE: + case X509_V_ERR_CRL_SIGNATURE_FAILURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE: + case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE: + rc = TQCA::TLS::SignatureFailed; + break; + case X509_V_ERR_INVALID_CA: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT: + case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY: + case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY: + rc = TQCA::TLS::InvalidCA; + break; + case X509_V_ERR_INVALID_PURPOSE: + rc = TQCA::TLS::InvalidPurpose; + break; + case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT: + case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN: + rc = TQCA::TLS::SelfSigned; + break; + case X509_V_ERR_CERT_REVOKED: + rc = TQCA::TLS::Revoked; + break; + case X509_V_ERR_PATH_LENGTH_EXCEEDED: + rc = TQCA::TLS::PathLengthExceeded; + break; + case X509_V_ERR_CERT_NOT_YET_VALID: + case X509_V_ERR_CERT_HAS_EXPIRED: + case X509_V_ERR_CRL_NOT_YET_VALID: + case X509_V_ERR_CRL_HAS_EXPIRED: + case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD: + case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD: + case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD: + case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD: + rc = TQCA::TLS::Expired; + break; + case X509_V_ERR_APPLICATION_VERIFICATION: + case X509_V_ERR_OUT_OF_MEM: + case X509_V_ERR_UNABLE_TO_GET_CRL: + case X509_V_ERR_CERT_CHAIN_TOO_LONG: + default: + rc = TQCA::TLS::Unknown; + break; + } + return rc; + } +}; + +class TQCAOpenSSL : public TQCAProvider +{ +public: + TQCAOpenSSL() {} + ~TQCAOpenSSL() {} + + void init() + { + } + + int qcaVersion() const + { + return TQCA_PLUGIN_VERSION; + } + + int capabilities() const + { + int caps = + TQCA::CAP_SHA1 | + TQCA::CAP_MD5 | + TQCA::CAP_BlowFish | + TQCA::CAP_TripleDES | +#ifndef NO_AES + TQCA::CAP_AES128 | + TQCA::CAP_AES256 | +#endif + TQCA::CAP_RSA | + TQCA::CAP_X509 | + TQCA::CAP_TLS; + return caps; + } + + void *context(int cap) + { + if(cap == TQCA::CAP_SHA1) + return new SHA1Context; + else if(cap == TQCA::CAP_MD5) + return new MD5Context; + else if(cap == TQCA::CAP_BlowFish) + return new BlowFishContext; + else if(cap == TQCA::CAP_TripleDES) + return new TripleDESContext; +#ifndef NO_AES + else if(cap == TQCA::CAP_AES128) + return new AES128Context; + else if(cap == TQCA::CAP_AES256) + return new AES256Context; +#endif + else if(cap == TQCA::CAP_RSA) + return new RSAKeyContext; + else if(cap == TQCA::CAP_X509) + return new CertContext; + else if(cap == TQCA::CAP_TLS) + return new TLSContext; + return 0; + } +}; + +#ifdef TQCA_PLUGIN +TQCAProvider *createProvider() +#else +TQCAProvider *createProviderTLS() +#endif +{ + return (new TQCAOpenSSL); +} |