/*
* pssl.cxx
*
* SSL implementation for PTLib using the SSLeay package
*
* Portable Windows Library
*
* Copyright (c) 1993-2002 Equivalence Pty. Ltd.
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
* the License for the specific language governing rights and limitations
* under the License.
*
* The Original Code is Portable Windows Library.
*
* The Initial Developer of the Original Code is Equivalence Pty. Ltd.
*
* Contributor(s): ______________________________________.
*
* Portions bsed upon the file crypto/buffer/bss_sock.c
* Original copyright notice appears below
*
* $Id: pssl.cxx,v 1.41 2005/11/30 12:47:41 csoutheren Exp $
* $Log: pssl.cxx,v $
* Revision 1.41 2005/11/30 12:47:41 csoutheren
* Removed tabs, reformatted some code, and changed tags for Doxygen
*
* Revision 1.40 2005/10/17 01:25:05 csoutheren
* Added check for ssl with const argumetns
*
* Revision 1.39 2004/04/09 06:52:17 rjongbloed
* Removed #pargma linker command for /delayload of DLL as documentations sais that
* you cannot do this.
*
* Revision 1.38 2004/02/23 23:52:20 csoutheren
* Added pragmas to avoid every Windows application needing to include libs explicitly
*
* Revision 1.37 2004/02/22 01:57:37 ykiryanov
* Put a fix for a compiler choke on BeOS when calling macro d2i_DHparams_bio in PSSLDiffieHellman::Load. Fast on Monday.
*
* Revision 1.36 2003/04/16 08:00:19 robertj
* Windoes psuedo autoconf support
*
* Revision 1.35 2002/11/06 22:47:25 robertj
* Fixed header comment (copyright etc)
*
* Revision 1.34 2002/06/07 02:55:23 robertj
* Fixed GNU warning
*
* Revision 1.33 2002/04/02 16:59:35 robertj
* Fixed GNU warning
*
* Revision 1.32 2002/04/02 16:17:28 robertj
* Fixed bug where returned TRUE when read count 0 or write count not len
* which is not according to Read()/Write() semantics. Could cause high CPU
* loops when sockets shutdown.
*
* Revision 1.31 2002/03/28 07:26:25 robertj
* Added Diffie-Hellman parameters wrapper class.
*
* Revision 1.30 2001/12/13 09:15:41 robertj
* Added function to get private key as ray DER binary data or as base64 string.
*
* Revision 1.29 2001/12/06 04:06:03 robertj
* Removed "Win32 SSL xxx" build configurations in favour of system
* environment variables to select optional libraries.
*
* Revision 1.28 2001/12/04 02:59:18 robertj
* Fixed problem on platforms where a pointer is not the same size as an int.
*
* Revision 1.27 2001/10/31 01:31:26 robertj
* Added enhancements for saving/loading/creating certificates and keys.
*
* Revision 1.26 2001/09/28 08:50:48 robertj
* Fixed bug where last count not set to zero if have error on read/write.
*
* Revision 1.25 2001/09/10 02:51:23 robertj
* Major change to fix problem with error codes being corrupted in a
* PChannel when have simultaneous reads and writes in threads.
*
* Revision 1.24 2001/06/01 00:53:59 robertj
* Added certificate constructor that takes a PBYTEArray
*
* Revision 1.23 2001/05/31 07:04:11 craigs
* Changed random seed function
*
* Revision 1.22 2001/05/29 03:33:54 craigs
* Added options to be compatible with OpenSSL 0.9.6
*
* Revision 1.21 2001/05/16 06:31:37 robertj
* Fixed GNU C++ compatibility
*
* Revision 1.20 2001/05/16 06:02:37 craigs
* Changed to allow detection of non-SSL connection to SecureHTTPServiceProcess
*
* Revision 1.19 2001/05/09 07:00:22 robertj
* Removed clearing of lock callbacks in context destructor, should not!
*
* Revision 1.18 2001/02/16 07:13:41 robertj
* Fixed bug in PSSLChannel error detection, thinks a zero byte write is error.
*
* Revision 1.17 2000/11/27 06:46:16 robertj
* Added asserts with SSL error message text.
*
* Revision 1.16 2000/11/14 08:33:16 robertj
* Added certificate and private key classes.
*
* Revision 1.15 2000/11/03 10:00:43 robertj
* Fixed initialisation of SSL, needed random number seed for some modes.
*
* Revision 1.14 2000/09/01 03:32:46 robertj
* Fixed assert on setting directories for CAs.
*
* Revision 1.13 2000/09/01 02:06:00 craigs
* Changed to OpenSSL_add_ssl_algorthms to fix link problem on some machines
*
* Revision 1.12 2000/08/25 13:56:46 robertj
* Fixed some GNU warnings
*
* Revision 1.11 2000/08/25 08:11:02 robertj
* Fixed OpenSSL support so can operate as a server channel.
*
* Revision 1.10 2000/08/04 12:52:18 robertj
* SSL changes, added error functions, removed need to have openssl include directory in app.
*
* Revision 1.9 2000/01/10 02:24:09 craigs
* Updated for new OpenSSL
*
* Revision 1.8 1998/12/04 13:04:18 craigs
* Changed for SSLeay 0.9
*
* Revision 1.7 1998/09/23 06:22:35 robertj
* Added open source copyright license.
*
* Revision 1.6 1998/01/26 02:50:17 robertj
* GNU Support
*
* Revision 1.5 1997/05/04 02:50:54 craigs
* Added support for client and server sertificates
*
* Revision 1.1 1996/11/15 07:38:34 craigs
* Initial revision
*
*/
/* crypto/buffer/bss_sock.c */
/* Copyright (C) 1995-1996 Eric Young (eay@mincom.oz.au)
* All rights reserved.
*
* This file is part of an SSL implementation written
* by Eric Young (eay@mincom.oz.au).
* The implementation was written so as to conform with Netscapes SSL
* specification. This library and applications are
* FREE FOR COMMERCIAL AND NON-COMMERCIAL USE
* as long as the following conditions are aheared to.
*
* Copyright remains Eric Young's, and as such any Copyright notices in
* the code are not to be removed. If this code is used in a product,
* Eric Young should be given attribution as the author of the parts used.
* This can be in the form of a textual message at program startup or
* in documentation (online or textual) provided with the package.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Eric Young (eay@mincom.oz.au)
*
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* The licence and distribution terms for any publically available version or
* derivative of this code cannot be changed. i.e. this code cannot simply be
* copied and put under another distribution licence
* [including the GNU Public Licence.]
*/
#ifdef __GNUC__
#pragma implementation "pssl.h"
#endif
#include <ptlib.h>
#include <ptclib/pssl.h>
#include <ptclib/mime.h>
#if P_SSL
#define USE_SOCKETS
extern "C" {
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
};
#ifdef _MSC_VER
#pragma comment(lib, P_SSL_LIB1)
#pragma comment(lib, P_SSL_LIB2)
#endif
///////////////////////////////////////////////////////////////////////////////
PARRAY(PSSLMutexArrayBase, PMutex);
class PSSLMutexArray : public PSSLMutexArrayBase
{
PCLASSINFO(PSSLMutexArray, PSSLMutexArrayBase);
public:
PSSLMutexArray();
};
class PSSL_BIO
{
public:
PSSL_BIO(BIO_METHOD *method = BIO_s_file_internal())
{ bio = BIO_new(method); }
~PSSL_BIO()
{ BIO_free(bio); }
operator BIO*() const
{ return bio; }
bool OpenRead(const PFilePath & filename)
{ return BIO_read_filename(bio, (char *)(const char *)filename) > 0; }
bool OpenWrite(const PFilePath & filename)
{ return BIO_write_filename(bio, (char *)(const char *)filename) > 0; }
bool OpenAppend(const PFilePath & filename)
{ return BIO_append_filename(bio, (char *)(const char *)filename) > 0; }
protected:
BIO * bio;
};
#define new PNEW
///////////////////////////////////////////////////////////////////////////////
PSSLMutexArray::PSSLMutexArray()
{
// Initialise all of the mutexes for multithreaded operation.
SetSize(CRYPTO_num_locks());
for (PINDEX i = 0; i < GetSize(); i++)
SetAt(i, new PMutex);
}
///////////////////////////////////////////////////////////////////////////////
PSSLPrivateKey::PSSLPrivateKey()
{
key = NULL;
}
PSSLPrivateKey::PSSLPrivateKey(unsigned modulus,
void (*callback)(int,int,void *),
void *cb_arg)
{
key = NULL;
Create(modulus, callback, cb_arg);
}
PSSLPrivateKey::PSSLPrivateKey(const PFilePath & keyFile, PSSLFileTypes fileType)
{
key = NULL;
Load(keyFile, fileType);
}
PSSLPrivateKey::PSSLPrivateKey(const BYTE * keyData, PINDEX keySize)
{
#if P_SSL_USE_CONST
key = d2i_AutoPrivateKey(NULL, &keyData, keySize);
#else
key = d2i_AutoPrivateKey(NULL, (BYTE **)&keyData, keySize);
#endif
}
PSSLPrivateKey::PSSLPrivateKey(const PBYTEArray & keyData)
{
const BYTE * keyPtr = keyData;
#if P_SSL_USE_CONST
key = d2i_AutoPrivateKey(NULL, &keyPtr, keyData.GetSize());
#else
key = d2i_AutoPrivateKey(NULL, (BYTE **)&keyPtr, keyData.GetSize());
#endif
}
PSSLPrivateKey::PSSLPrivateKey(const PSSLPrivateKey & privKey)
{
key = privKey.key;
}
PSSLPrivateKey & PSSLPrivateKey::operator=(const PSSLPrivateKey & privKey)
{
if (key != NULL)
EVP_PKEY_free(key);
key = privKey.key;
return *this;
}
PSSLPrivateKey::~PSSLPrivateKey()
{
if (key != NULL)
EVP_PKEY_free(key);
}
BOOL PSSLPrivateKey::Create(unsigned modulus,
void (*callback)(int,int,void *),
void *cb_arg)
{
if (key != NULL) {
EVP_PKEY_free(key);
key = NULL;
}
if (modulus < 384) {
return FALSE;
}
key = EVP_PKEY_new();
if (key == NULL)
return FALSE;
if (EVP_PKEY_assign_RSA(key, RSA_generate_key(modulus, 0x10001, callback, cb_arg)))
return TRUE;
EVP_PKEY_free(key);
key = NULL;
return FALSE;
}
PBYTEArray PSSLPrivateKey::GetData() const
{
PBYTEArray data;
if (key != NULL) {
BYTE * keyPtr = data.GetPointer(i2d_PrivateKey(key, NULL));
i2d_PrivateKey(key, &keyPtr);
}
return data;
}
PString PSSLPrivateKey::AsString() const
{
return PBase64::Encode(GetData());
}
BOOL PSSLPrivateKey::Load(const PFilePath & keyFile, PSSLFileTypes fileType)
{
if (key != NULL) {
EVP_PKEY_free(key);
key = NULL;
}
PSSL_BIO in;
if (!in.OpenRead(keyFile)) {
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,ERR_R_SYS_LIB);
return FALSE;
}
if (fileType == PSSLFileTypeDEFAULT)
fileType = keyFile.GetType() == ".pem" ? PSSLFileTypePEM : PSSLFileTypeASN1;
switch (fileType) {
case PSSLFileTypeASN1 :
key = d2i_PrivateKey_bio(in, NULL);
if (key != NULL)
return TRUE;
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE, ERR_R_ASN1_LIB);
break;
case PSSLFileTypePEM :
key = PEM_read_bio_PrivateKey(in, NULL, NULL, NULL);
if (key != NULL)
return TRUE;
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE, ERR_R_PEM_LIB);
break;
default :
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,SSL_R_BAD_SSL_FILETYPE);
}
return FALSE;
}
BOOL PSSLPrivateKey::Save(const PFilePath & keyFile, BOOL append, PSSLFileTypes fileType)
{
if (key == NULL)
return FALSE;
PSSL_BIO out;
if (!(append ? out.OpenAppend(keyFile) : out.OpenWrite(keyFile))) {
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,ERR_R_SYS_LIB);
return FALSE;
}
if (fileType == PSSLFileTypeDEFAULT)
fileType = keyFile.GetType() == ".pem" ? PSSLFileTypePEM : PSSLFileTypeASN1;
switch (fileType) {
case PSSLFileTypeASN1 :
if (i2d_PrivateKey_bio(out, key))
return TRUE;
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE, ERR_R_ASN1_LIB);
break;
case PSSLFileTypePEM :
if (PEM_write_bio_PrivateKey(out, key, NULL, NULL, 0, 0, NULL))
return TRUE;
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE, ERR_R_PEM_LIB);
break;
default :
SSLerr(SSL_F_SSL_USE_PRIVATEKEY_FILE,SSL_R_BAD_SSL_FILETYPE);
}
return FALSE;
}
///////////////////////////////////////////////////////////////////////////////
PSSLCertificate::PSSLCertificate()
{
certificate = NULL;
}
PSSLCertificate::PSSLCertificate(const PFilePath & certFile, PSSLFileTypes fileType)
{
certificate = NULL;
Load(certFile, fileType);
}
PSSLCertificate::PSSLCertificate(const BYTE * certData, PINDEX certSize)
{
#if P_SSL_USE_CONST
certificate = d2i_X509(NULL, &certData, certSize);
#else
certificate = d2i_X509(NULL, (unsigned char **)&certData, certSize);
#endif
}
PSSLCertificate::PSSLCertificate(const PBYTEArray & certData)
{
const BYTE * certPtr = certData;
#if P_SSL_USE_CONST
certificate = d2i_X509(NULL, &certPtr, certData.GetSize());
#else
certificate = d2i_X509(NULL, (unsigned char **)&certPtr, certData.GetSize());
#endif
}
PSSLCertificate::PSSLCertificate(const PString & certStr)
{
PBYTEArray certData;
PBase64::Decode(certStr, certData);
if (certData.GetSize() > 0) {
const BYTE * certPtr = certData;
#if P_SSL_USE_CONST
certificate = d2i_X509(NULL, &certPtr, certData.GetSize());
#else
certificate = d2i_X509(NULL, (unsigned char **)&certPtr, certData.GetSize());
#endif
}
else
certificate = NULL;
}
PSSLCertificate::PSSLCertificate(const PSSLCertificate & cert)
{
if (cert.certificate == NULL)
certificate = NULL;
else
certificate = X509_dup(cert.certificate);
}
PSSLCertificate & PSSLCertificate::operator=(const PSSLCertificate & cert)
{
if (certificate != NULL)
X509_free(certificate);
if (cert.certificate == NULL)
certificate = NULL;
else
certificate = X509_dup(cert.certificate);
return *this;
}
PSSLCertificate::~PSSLCertificate()
{
if (certificate != NULL)
X509_free(certificate);
}
BOOL PSSLCertificate::CreateRoot(const PString & subject,
const PSSLPrivateKey & privateKey)
{
if (certificate != NULL) {
X509_free(certificate);
certificate = NULL;
}
if (privateKey == NULL)
return FALSE;
POrdinalToString info;
PStringArray fields = subject.Tokenise('/', FALSE);
PINDEX i;
for (i = 0; i < fields.GetSize(); i++) {
PString field = fields[i];
PINDEX equals = field.Find('=');
if (equals != P_MAX_INDEX) {
int nid = OBJ_txt2nid((char *)(const char *)field.Left(equals));
if (nid != NID_undef)
info.SetAt(nid, field.Mid(equals+1));
}
}
if (info.IsEmpty())
return FALSE;
certificate = X509_new();
if (certificate == NULL)
return FALSE;
if (X509_set_version(certificate, 2)) {
/* Set version to V3 */
ASN1_INTEGER_set(X509_get_serialNumber(certificate), 0L);
X509_NAME * name = X509_NAME_new();
for (i = 0; i < info.GetSize(); i++)
X509_NAME_add_entry_by_NID(name,
info.GetKeyAt(i),
MBSTRING_ASC,
(unsigned char *)(const char *)info.GetDataAt(i),
-1,-1, 0);
X509_set_issuer_name(certificate, name);
X509_set_subject_name(certificate, name);
X509_NAME_free(name);
X509_gmtime_adj(X509_get_notBefore(certificate), 0);
X509_gmtime_adj(X509_get_notAfter(certificate), (long)60*60*24*365*5);
X509_PUBKEY * pubkey = X509_PUBKEY_new();
if (pubkey != NULL) {
X509_PUBKEY_set(&pubkey, privateKey);
EVP_PKEY * pkey = X509_PUBKEY_get(pubkey);
X509_set_pubkey(certificate, pkey);
EVP_PKEY_free(pkey);
X509_PUBKEY_free(pubkey);
if (X509_sign(certificate, privateKey, EVP_md5()) > 0)
return TRUE;
}
}
X509_free(certificate);
certificate = NULL;
return FALSE;
}
PBYTEArray PSSLCertificate::GetData() const
{
PBYTEArray data;
if (certificate != NULL) {
BYTE * certPtr = data.GetPointer(i2d_X509(certificate, NULL));
i2d_X509(certificate, &certPtr);
}
return data;
}
PString PSSLCertificate::AsString() const
{
return PBase64::Encode(GetData());
}
BOOL PSSLCertificate::Load(const PFilePath & certFile, PSSLFileTypes fileType)
{
if (certificate != NULL) {
X509_free(certificate);
certificate = NULL;
}
PSSL_BIO in;
if (!in.OpenRead(certFile)) {
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE,ERR_R_SYS_LIB);
return FALSE;
}
if (fileType == PSSLFileTypeDEFAULT)
fileType = certFile.GetType() == ".pem" ? PSSLFileTypePEM : PSSLFileTypeASN1;
switch (fileType) {
case PSSLFileTypeASN1 :
certificate = d2i_X509_bio(in, NULL);
if (certificate != NULL)
return TRUE;
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
break;
case PSSLFileTypePEM :
certificate = PEM_read_bio_X509(in, NULL, NULL, NULL);
if (certificate != NULL)
return TRUE;
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PEM_LIB);
break;
default :
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE,SSL_R_BAD_SSL_FILETYPE);
}
return FALSE;
}
BOOL PSSLCertificate::Save(const PFilePath & certFile, BOOL append, PSSLFileTypes fileType)
{
if (certificate == NULL)
return FALSE;
PSSL_BIO out;
if (!(append ? out.OpenAppend(certFile) : out.OpenWrite(certFile))) {
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE,ERR_R_SYS_LIB);
return FALSE;
}
if (fileType == PSSLFileTypeDEFAULT)
fileType = certFile.GetType() == ".pem" ? PSSLFileTypePEM : PSSLFileTypeASN1;
switch (fileType) {
case PSSLFileTypeASN1 :
if (i2d_X509_bio(out, certificate))
return TRUE;
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_ASN1_LIB);
break;
case PSSLFileTypePEM :
if (PEM_write_bio_X509(out, certificate))
return TRUE;
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE, ERR_R_PEM_LIB);
break;
default :
SSLerr(SSL_F_SSL_CTX_USE_CERTIFICATE_FILE,SSL_R_BAD_SSL_FILETYPE);
}
return FALSE;
}
///////////////////////////////////////////////////////////////////////////////
PSSLDiffieHellman::PSSLDiffieHellman()
{
dh = NULL;
}
PSSLDiffieHellman::PSSLDiffieHellman(const PFilePath & dhFile,
PSSLFileTypes fileType)
{
dh = NULL;
Load(dhFile, fileType);
}
PSSLDiffieHellman::PSSLDiffieHellman(const BYTE * pData, PINDEX pSize,
const BYTE * gData, PINDEX gSize)
{
dh = DH_new();
if (dh == NULL)
return;
dh->p = BN_bin2bn(pData, pSize, NULL);
dh->g = BN_bin2bn(gData, gSize, NULL);
if (dh->p != NULL && dh->g != NULL)
return;
DH_free(dh);
dh = NULL;
}
PSSLDiffieHellman::PSSLDiffieHellman(const PSSLDiffieHellman & diffie)
{
dh = diffie.dh;
}
PSSLDiffieHellman & PSSLDiffieHellman::operator=(const PSSLDiffieHellman & diffie)
{
if (dh != NULL)
DH_free(dh);
dh = diffie.dh;
return *this;
}
PSSLDiffieHellman::~PSSLDiffieHellman()
{
if (dh != NULL)
DH_free(dh);
}
#ifdef __BEOS__
// 2/21/04 Yuri Kiryanov - fix for compiler choke on BeOS for usage of
// SSL function d2i_DHparams_bio below in PSSLDiffieHellman::Load
#undef d2i_DHparams_bio
#define d2i_DHparams_bio(bp,x) \
(DH *)ASN1_d2i_bio( \
(char *(*)(...))(void *)DH_new, \
(char *(*)(...))(void *)d2i_DHparams, \
(bp), \
(unsigned char **)(x) \
)
#endif
BOOL PSSLDiffieHellman::Load(const PFilePath & dhFile,
PSSLFileTypes fileType)
{
if (dh != NULL) {
DH_free(dh);
dh = NULL;
}
PSSL_BIO in;
if (!in.OpenRead(dhFile)) {
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE,ERR_R_SYS_LIB);
return FALSE;
}
if (fileType == PSSLFileTypeDEFAULT)
fileType = dhFile.GetType() == ".pem" ? PSSLFileTypePEM : PSSLFileTypeASN1;
switch (fileType) {
case PSSLFileTypeASN1 :
dh = d2i_DHparams_bio(in, NULL);
if (dh != NULL)
return TRUE;
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_ASN1_LIB);
break;
case PSSLFileTypePEM :
dh = PEM_read_bio_DHparams(in, NULL, NULL, NULL);
if (dh != NULL)
return TRUE;
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE, ERR_R_PEM_LIB);
break;
default :
SSLerr(SSL_F_SSL_CTX_USE_PRIVATEKEY_FILE,SSL_R_BAD_SSL_FILETYPE);
}
return FALSE;
}
///////////////////////////////////////////////////////////////////////////////
static void LockingCallback(int mode, int n, const char * /*file*/, int /*line*/)
{
static PSSLMutexArray mutexes;
if ((mode & CRYPTO_LOCK) != 0)
mutexes[n].Wait();
else
mutexes[n].Signal();
}
static int VerifyCallBack(int ok, X509_STORE_CTX * ctx)
{
X509 * err_cert = X509_STORE_CTX_get_current_cert(ctx);
//int err = X509_STORE_CTX_get_error(ctx);
// get the subject name, just for verification
char buf[256];
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
PTRACE(1, "SSL\tVerify callback depth "
<< X509_STORE_CTX_get_error_depth(ctx)
<< " : cert name = " << buf);
return ok;
}
static void PSSLAssert(const char * msg)
{
char buf[256];
strcpy(buf, msg);
ERR_error_string(ERR_peek_error(), &buf[strlen(msg)]);
PTRACE(1, "SSL\t" << buf);
PAssertAlways(buf);
}
PSSLContext::PSSLContext(const void * sessionId, PINDEX idSize)
{
static PMutex InitialisationMutex;
InitialisationMutex.Wait();
static BOOL needInitialisation = TRUE;
if (needInitialisation) {
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
// Seed the random number generator
BYTE seed[128];
for (size_t i = 0; i < sizeof(seed); i++)
seed[i] = (BYTE)rand();
RAND_seed(seed, sizeof(seed));
// set up multithread stuff
CRYPTO_set_locking_callback(LockingCallback);
needInitialisation = FALSE;
}
InitialisationMutex.Signal();
// create the new SSL context
SSL_METHOD * meth = SSLv23_method();
context = SSL_CTX_new(meth);
if (context == NULL)
PSSLAssert("Error creating context: ");
// Shutdown
SSL_CTX_set_quiet_shutdown(context, 1);
// Set default locations
if (!SSL_CTX_load_verify_locations(context, NULL, ".") ||
!SSL_CTX_set_default_verify_paths(context))
PSSLAssert("Cannot set CAfile and path: ");
if (sessionId != NULL) {
if (idSize == 0)
idSize = ::strlen((const char *)sessionId)+1;
SSL_CTX_set_session_id_context(context, (const BYTE *)sessionId, idSize);
SSL_CTX_sess_set_cache_size(context, 128);
}
// set default verify mode
SSL_CTX_set_verify(context, SSL_VERIFY_NONE, VerifyCallBack);
}
PSSLContext::~PSSLContext()
{
SSL_CTX_free(context);
}
BOOL PSSLContext::SetCAPath(const PDirectory & caPath)
{
PString path = caPath.Left(caPath.GetLength()-1);
if (!SSL_CTX_load_verify_locations(context, NULL, path))
return FALSE;
return SSL_CTX_set_default_verify_paths(context);
}
BOOL PSSLContext::SetCAFile(const PFilePath & caFile)
{
if (!SSL_CTX_load_verify_locations(context, caFile, NULL))
return FALSE;
return SSL_CTX_set_default_verify_paths(context);
}
BOOL PSSLContext::UseCertificate(const PSSLCertificate & certificate)
{
return SSL_CTX_use_certificate(context, certificate) > 0;
}
BOOL PSSLContext::UsePrivateKey(const PSSLPrivateKey & key)
{
if (SSL_CTX_use_PrivateKey(context, key) <= 0)
return FALSE;
return SSL_CTX_check_private_key(context);
}
BOOL PSSLContext::UseDiffieHellman(const PSSLDiffieHellman & dh)
{
return SSL_CTX_set_tmp_dh(context, (dh_st *)dh) > 0;
}
BOOL PSSLContext::SetCipherList(const PString & ciphers)
{
if (ciphers.IsEmpty())
return FALSE;
return SSL_CTX_set_cipher_list(context, (char *)(const char *)ciphers);
}
/////////////////////////////////////////////////////////////////////////
//
// SSLChannel
//
PSSLChannel::PSSLChannel(PSSLContext * ctx, BOOL autoDel)
{
if (ctx != NULL) {
context = ctx;
autoDeleteContext = autoDel;
}
else {
context = new PSSLContext;
autoDeleteContext = TRUE;
}
ssl = SSL_new(*context);
if (ssl == NULL)
PSSLAssert("Error creating channel: ");
}
PSSLChannel::PSSLChannel(PSSLContext & ctx)
{
context = &ctx;
autoDeleteContext = FALSE;
ssl = SSL_new(*context);
}
PSSLChannel::~PSSLChannel()
{
// free the SSL connection
if (ssl != NULL)
SSL_free(ssl);
if (autoDeleteContext)
delete context;
}
BOOL PSSLChannel::Read(void * buf, PINDEX len)
{
flush();
channelPointerMutex.StartRead();
lastReadCount = 0;
BOOL returnValue = FALSE;
if (readChannel == NULL)
SetErrorValues(NotOpen, EBADF, LastReadError);
else if (readTimeout == 0 && SSL_pending(ssl) == 0)
SetErrorValues(Timeout, ETIMEDOUT, LastReadError);
else {
readChannel->SetReadTimeout(readTimeout);
int readResult = SSL_read(ssl, (char *)buf, len);
lastReadCount = readResult;
returnValue = readResult > 0;
if (readResult < 0 && GetErrorCode(LastReadError) == NoError)
ConvertOSError(-1, LastReadError);
}
channelPointerMutex.EndRead();
return returnValue;
}
BOOL PSSLChannel::Write(const void * buf, PINDEX len)
{
flush();
channelPointerMutex.StartRead();
lastWriteCount = 0;
BOOL returnValue;
if (writeChannel == NULL) {
SetErrorValues(NotOpen, EBADF, LastWriteError);
returnValue = FALSE;
}
else {
writeChannel->SetWriteTimeout(writeTimeout);
int writeResult = SSL_write(ssl, (const char *)buf, len);
lastWriteCount = writeResult;
returnValue = lastWriteCount >= len;
if (writeResult < 0 && GetErrorCode(LastWriteError) == NoError)
ConvertOSError(-1, LastWriteError);
}
channelPointerMutex.EndRead();
return returnValue;
}
BOOL PSSLChannel::Close()
{
BOOL ok = SSL_shutdown(ssl);
return PIndirectChannel::Close() && ok;
}
BOOL PSSLChannel::ConvertOSError(int error, ErrorGroup group)
{
Errors lastError = NoError;
DWORD osError = 0;
if (SSL_get_error(ssl, error) != SSL_ERROR_NONE && (osError = ERR_peek_error()) != 0) {
osError |= 0x80000000;
lastError = Miscellaneous;
}
return SetErrorValues(lastError, osError, group);
}
PString PSSLChannel::GetErrorText(ErrorGroup group) const
{
if ((lastErrorNumber[group]&0x80000000) == 0)
return PIndirectChannel::GetErrorText(group);
char buf[200];
return ERR_error_string(lastErrorNumber[group]&0x7fffffff, buf);
}
BOOL PSSLChannel::Accept()
{
if (IsOpen())
return ConvertOSError(SSL_accept(ssl));
return FALSE;
}
BOOL PSSLChannel::Accept(PChannel & channel)
{
if (Open(channel))
return ConvertOSError(SSL_accept(ssl));
return FALSE;
}
BOOL PSSLChannel::Accept(PChannel * channel, BOOL autoDelete)
{
if (Open(channel, autoDelete))
return ConvertOSError(SSL_accept(ssl));
return FALSE;
}
BOOL PSSLChannel::Connect()
{
if (IsOpen())
return ConvertOSError(SSL_connect(ssl));
return FALSE;
}
BOOL PSSLChannel::Connect(PChannel & channel)
{
if (Open(channel))
return ConvertOSError(SSL_connect(ssl));
return FALSE;
}
BOOL PSSLChannel::Connect(PChannel * channel, BOOL autoDelete)
{
if (Open(channel, autoDelete))
return ConvertOSError(SSL_connect(ssl));
return FALSE;
}
BOOL PSSLChannel::UseCertificate(const PSSLCertificate & certificate)
{
return SSL_use_certificate(ssl, certificate);
}
void PSSLChannel::SetVerifyMode(VerifyMode mode)
{
int verify;
switch (mode) {
default :
case VerifyNone:
verify = SSL_VERIFY_NONE;
break;
case VerifyPeer:
verify = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE;
break;
case VerifyPeerMandatory:
verify = SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
}
SSL_set_verify(ssl, verify, VerifyCallBack);
}
BOOL PSSLChannel::RawSSLRead(void * buf, PINDEX & len)
{
if (!PIndirectChannel::Read(buf, len))
return FALSE;
len = GetLastReadCount();
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
//
// Low level interface to SSLEay routines
//
#define PSSLCHANNEL(bio) ((PSSLChannel *)(bio->ptr))
extern "C" {
#if (OPENSSL_VERSION_NUMBER < 0x00906000)
typedef int (*ifptr)();
typedef long (*lfptr)();
#endif
static int Psock_new(BIO * bio)
{
bio->init = 0;
bio->num = 0;
bio->ptr = NULL; // this is really (PSSLChannel *)
bio->flags = 0;
return(1);
}
static int Psock_free(BIO * bio)
{
if (bio == NULL)
return 0;
if (bio->shutdown) {
if (bio->init) {
PSSLCHANNEL(bio)->Shutdown(PSocket::ShutdownReadAndWrite);
PSSLCHANNEL(bio)->Close();
}
bio->init = 0;
bio->flags = 0;
}
return 1;
}
static long Psock_ctrl(BIO * bio, int cmd, long num, void * /*ptr*/)
{
switch (cmd) {
case BIO_CTRL_SET_CLOSE:
bio->shutdown = (int)num;
return 1;
case BIO_CTRL_GET_CLOSE:
return bio->shutdown;
case BIO_CTRL_FLUSH:
return 1;
}
// Other BIO commands, return 0
return 0;
}
static int Psock_read(BIO * bio, char * out, int outl)
{
if (out == NULL)
return 0;
BIO_clear_retry_flags(bio);
// Skip over the polymorphic read, want to do real one
PINDEX len = outl;
if (PSSLCHANNEL(bio)->RawSSLRead(out, len))
return len;
switch (PSSLCHANNEL(bio)->GetErrorCode(PChannel::LastReadError)) {
case PChannel::Interrupted :
case PChannel::Timeout :
BIO_set_retry_read(bio);
return -1;
default :
break;
}
return 0;
}
static int Psock_write(BIO * bio, const char * in, int inl)
{
if (in == NULL)
return 0;
BIO_clear_retry_flags(bio);
// Skip over the polymorphic write, want to do real one
if (PSSLCHANNEL(bio)->PIndirectChannel::Write(in, inl))
return PSSLCHANNEL(bio)->GetLastWriteCount();
switch (PSSLCHANNEL(bio)->GetErrorCode(PChannel::LastWriteError)) {
case PChannel::Interrupted :
case PChannel::Timeout :
BIO_set_retry_write(bio);
return -1;
default :
break;
}
return 0;
}
static int Psock_puts(BIO * bio, const char * str)
{
int n,ret;
n = strlen(str);
ret = Psock_write(bio,str,n);
return ret;
}
};
static BIO_METHOD methods_Psock =
{
BIO_TYPE_SOCKET,
"PTLib-PSSLChannel",
#if (OPENSSL_VERSION_NUMBER < 0x00906000)
(ifptr)Psock_write,
(ifptr)Psock_read,
(ifptr)Psock_puts,
NULL,
(lfptr)Psock_ctrl,
(ifptr)Psock_new,
(ifptr)Psock_free
#else
Psock_write,
Psock_read,
Psock_puts,
NULL,
Psock_ctrl,
Psock_new,
Psock_free
#endif
};
BOOL PSSLChannel::OnOpen()
{
BIO * bio = BIO_new(&methods_Psock);
if (bio == NULL) {
SSLerr(SSL_F_SSL_SET_FD,ERR_R_BUF_LIB);
return FALSE;
}
// "Open" then bio
bio->ptr = this;
bio->init = 1;
SSL_set_bio(ssl, bio, bio);
return TRUE;
}
//////////////////////////////////////////////////////////////////////////
//
// misc unused code
//
#if 0
extern "C" {
static verify_depth = 0;
static verify_error = VERIFY_OK;
// should be X509 * but we can just have them as char *.
int verify_callback(int ok, X509 * xs, X509 * xi, int depth, int error)
{
char *s;
s = (char *)X509_NAME_oneline(X509_get_subject_name(xs));
if (s == NULL) {
// ERR_print_errors(bio_err);
return(0);
}
PError << "depth= " << depth << " " << (char *)s << endl;
free(s);
if (error == VERIFY_ERR_UNABLE_TO_GET_ISSUER) {
s=(char *)X509_NAME_oneline(X509_get_issuer_name(xs));
if (s == NULL) {
PError << "verify error" << endl;
//ERR_print_errors(bio_err);
return(0);
}
PError << "issuer = " << s << endl;
free(s);
}
if (!ok) {
PError << "verify error:num=" << error << " " <<
X509_cert_verify_error_string(error) << endl;
if (verify_depth <= depth) {
ok=1;
verify_error=VERIFY_OK;
} else {
ok=0;
verify_error=error;
}
}
PError << "verify return:" << ok << endl;
return(ok);
}
};
#endif
#endif // P_SSL
// End of file ////////////////////////////////////////////////////////////////
syntax highlighted by Code2HTML, v. 0.9.1