/* $Id$ */ /* * Copyright (C) 2009-2011 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include "os_symbian.h" #include #include #include #define THIS_FILE "ssl_sock_symbian.cpp" /* Cipher name structure */ typedef struct cipher_name_t { pj_ssl_cipher cipher; const char *name; } cipher_name_t; /* Cipher name constants */ static cipher_name_t cipher_names[] = { {PJ_TLS_UNKNOWN_CIPHER, "UNKNOWN"}, {PJ_TLS_NULL_WITH_NULL_NULL, "NULL"}, /* TLS/SSLv3 */ {PJ_TLS_RSA_WITH_NULL_MD5, "TLS_RSA_WITH_NULL_MD5"}, {PJ_TLS_RSA_WITH_NULL_SHA, "TLS_RSA_WITH_NULL_SHA"}, {PJ_TLS_RSA_WITH_NULL_SHA256, "TLS_RSA_WITH_NULL_SHA256"}, {PJ_TLS_RSA_WITH_RC4_128_MD5, "TLS_RSA_WITH_RC4_128_MD5"}, {PJ_TLS_RSA_WITH_RC4_128_SHA, "TLS_RSA_WITH_RC4_128_SHA"}, {PJ_TLS_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_RSA_WITH_3DES_EDE_CBC_SHA"}, {PJ_TLS_RSA_WITH_AES_128_CBC_SHA, "TLS_RSA_WITH_AES_128_CBC_SHA"}, {PJ_TLS_RSA_WITH_AES_256_CBC_SHA, "TLS_RSA_WITH_AES_256_CBC_SHA"}, {PJ_TLS_RSA_WITH_AES_128_CBC_SHA256, "TLS_RSA_WITH_AES_128_CBC_SHA256"}, {PJ_TLS_RSA_WITH_AES_256_CBC_SHA256, "TLS_RSA_WITH_AES_256_CBC_SHA256"}, {PJ_TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA, "TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA"}, {PJ_TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA"}, {PJ_TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA, "TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA"}, {PJ_TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA, "TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA"}, {PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA, "TLS_DH_DSS_WITH_AES_128_CBC_SHA"}, {PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA, "TLS_DH_RSA_WITH_AES_128_CBC_SHA"}, {PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA"}, {PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA"}, {PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA, "TLS_DH_DSS_WITH_AES_256_CBC_SHA"}, {PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA, "TLS_DH_RSA_WITH_AES_256_CBC_SHA"}, {PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA"}, {PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA"}, {PJ_TLS_DH_DSS_WITH_AES_128_CBC_SHA256, "TLS_DH_DSS_WITH_AES_128_CBC_SHA256"}, {PJ_TLS_DH_RSA_WITH_AES_128_CBC_SHA256, "TLS_DH_RSA_WITH_AES_128_CBC_SHA256"}, {PJ_TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, "TLS_DHE_DSS_WITH_AES_128_CBC_SHA256"}, {PJ_TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, "TLS_DHE_RSA_WITH_AES_128_CBC_SHA256"}, {PJ_TLS_DH_DSS_WITH_AES_256_CBC_SHA256, "TLS_DH_DSS_WITH_AES_256_CBC_SHA256"}, {PJ_TLS_DH_RSA_WITH_AES_256_CBC_SHA256, "TLS_DH_RSA_WITH_AES_256_CBC_SHA256"}, {PJ_TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, "TLS_DHE_DSS_WITH_AES_256_CBC_SHA256"}, {PJ_TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, "TLS_DHE_RSA_WITH_AES_256_CBC_SHA256"}, {PJ_TLS_DH_anon_WITH_RC4_128_MD5, "TLS_DH_anon_WITH_RC4_128_MD5"}, {PJ_TLS_DH_anon_WITH_3DES_EDE_CBC_SHA, "TLS_DH_anon_WITH_3DES_EDE_CBC_SHA"}, {PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA, "TLS_DH_anon_WITH_AES_128_CBC_SHA"}, {PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA, "TLS_DH_anon_WITH_AES_256_CBC_SHA"}, {PJ_TLS_DH_anon_WITH_AES_128_CBC_SHA256, "TLS_DH_anon_WITH_AES_128_CBC_SHA256"}, {PJ_TLS_DH_anon_WITH_AES_256_CBC_SHA256, "TLS_DH_anon_WITH_AES_256_CBC_SHA256"}, /* TLS (deprecated) */ {PJ_TLS_RSA_EXPORT_WITH_RC4_40_MD5, "TLS_RSA_EXPORT_WITH_RC4_40_MD5"}, {PJ_TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5, "TLS_RSA_EXPORT_WITH_RC2_CBC_40_MD5"}, {PJ_TLS_RSA_WITH_IDEA_CBC_SHA, "TLS_RSA_WITH_IDEA_CBC_SHA"}, {PJ_TLS_RSA_EXPORT_WITH_DES40_CBC_SHA, "TLS_RSA_EXPORT_WITH_DES40_CBC_SHA"}, {PJ_TLS_RSA_WITH_DES_CBC_SHA, "TLS_RSA_WITH_DES_CBC_SHA"}, {PJ_TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA, "TLS_DH_DSS_EXPORT_WITH_DES40_CBC_SHA"}, {PJ_TLS_DH_DSS_WITH_DES_CBC_SHA, "TLS_DH_DSS_WITH_DES_CBC_SHA"}, {PJ_TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA, "TLS_DH_RSA_EXPORT_WITH_DES40_CBC_SHA"}, {PJ_TLS_DH_RSA_WITH_DES_CBC_SHA, "TLS_DH_RSA_WITH_DES_CBC_SHA"}, {PJ_TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, "TLS_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA"}, {PJ_TLS_DHE_DSS_WITH_DES_CBC_SHA, "TLS_DHE_DSS_WITH_DES_CBC_SHA"}, {PJ_TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, "TLS_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA"}, {PJ_TLS_DHE_RSA_WITH_DES_CBC_SHA, "TLS_DHE_RSA_WITH_DES_CBC_SHA"}, {PJ_TLS_DH_anon_EXPORT_WITH_RC4_40_MD5, "TLS_DH_anon_EXPORT_WITH_RC4_40_MD5"}, {PJ_TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA, "TLS_DH_anon_EXPORT_WITH_DES40_CBC_SHA"}, {PJ_TLS_DH_anon_WITH_DES_CBC_SHA, "TLS_DH_anon_WITH_DES_CBC_SHA"}, /* SSLv3 */ {PJ_SSL_FORTEZZA_KEA_WITH_NULL_SHA, "SSL_FORTEZZA_KEA_WITH_NULL_SHA"}, {PJ_SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA,"SSL_FORTEZZA_KEA_WITH_FORTEZZA_CBC_SHA"}, {PJ_SSL_FORTEZZA_KEA_WITH_RC4_128_SHA, "SSL_FORTEZZA_KEA_WITH_RC4_128_SHA"}, /* SSLv2 */ {PJ_SSL_CK_RC4_128_WITH_MD5, "SSL_CK_RC4_128_WITH_MD5"}, {PJ_SSL_CK_RC4_128_EXPORT40_WITH_MD5, "SSL_CK_RC4_128_EXPORT40_WITH_MD5"}, {PJ_SSL_CK_RC2_128_CBC_WITH_MD5, "SSL_CK_RC2_128_CBC_WITH_MD5"}, {PJ_SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5, "SSL_CK_RC2_128_CBC_EXPORT40_WITH_MD5"}, {PJ_SSL_CK_IDEA_128_CBC_WITH_MD5, "SSL_CK_IDEA_128_CBC_WITH_MD5"}, {PJ_SSL_CK_DES_64_CBC_WITH_MD5, "SSL_CK_DES_64_CBC_WITH_MD5"}, {PJ_SSL_CK_DES_192_EDE3_CBC_WITH_MD5, "SSL_CK_DES_192_EDE3_CBC_WITH_MD5"} }; /* Get cipher name string */ static const char* get_cipher_name(pj_ssl_cipher cipher) { unsigned i, n; n = PJ_ARRAY_SIZE(cipher_names); for (i = 0; i < n; ++i) { if (cipher == cipher_names[i].cipher) return cipher_names[i].name; } return "CIPHER_UNKNOWN"; } typedef void (*CPjSSLSocket_cb)(int err, void *key); class CPjSSLSocketReader : public CActive { public: static CPjSSLSocketReader *NewL(CSecureSocket &sock) { CPjSSLSocketReader *self = new (ELeave) CPjSSLSocketReader(sock); CleanupStack::PushL(self); self->ConstructL(); CleanupStack::Pop(self); return self; } ~CPjSSLSocketReader() { Cancel(); } /* Asynchronous read from the socket. */ int Read(CPjSSLSocket_cb cb, void *key, TPtr8 &data, TUint flags) { PJ_ASSERT_RETURN(!IsActive(), PJ_EBUSY); cb_ = cb; key_ = key; sock_.RecvOneOrMore(data, iStatus, len_received_); SetActive(); return PJ_EPENDING; } private: CSecureSocket &sock_; CPjSSLSocket_cb cb_; void *key_; TSockXfrLength len_received_; /* not really useful? */ void DoCancel() { sock_.CancelAll(); } void RunL() { (*cb_)(iStatus.Int(), key_); } CPjSSLSocketReader(CSecureSocket &sock) : CActive(0), sock_(sock), cb_(NULL), key_(NULL) {} void ConstructL() { CActiveScheduler::Add(this); } }; class CPjSSLSocket : public CActive { public: enum ssl_state { SSL_STATE_NULL, SSL_STATE_CONNECTING, SSL_STATE_HANDSHAKING, SSL_STATE_ESTABLISHED }; static CPjSSLSocket *NewL(const TDesC8 &ssl_proto, pj_qos_type qos_type, const pj_qos_params &qos_params) { CPjSSLSocket *self = new (ELeave) CPjSSLSocket(qos_type, qos_params); CleanupStack::PushL(self); self->ConstructL(ssl_proto); CleanupStack::Pop(self); return self; } ~CPjSSLSocket() { Cancel(); CleanupSubObjects(); } int Connect(CPjSSLSocket_cb cb, void *key, const TInetAddr &local_addr, const TInetAddr &rem_addr, const TDesC8 &servername = TPtrC8(NULL,0), const TDesC8 &ciphers = TPtrC8(NULL,0)); int Send(CPjSSLSocket_cb cb, void *key, const TDesC8 &aDesc, TUint flags); int SendSync(const TDesC8 &aDesc, TUint flags); CPjSSLSocketReader* GetReader(); enum ssl_state GetState() const { return state_; } const TInetAddr* GetLocalAddr() const { return &local_addr_; } int GetCipher(TDes8 &cipher) const { if (securesock_) return securesock_->CurrentCipherSuite(cipher); return KErrNotFound; } const CX509Certificate *GetPeerCert() { if (securesock_) return securesock_->ServerCert(); return NULL; } private: enum ssl_state state_; pj_sock_t sock_; CSecureSocket *securesock_; bool is_connected_; pj_qos_type qos_type_; pj_qos_params qos_params_; CPjSSLSocketReader *reader_; TBuf<32> ssl_proto_; TInetAddr rem_addr_; TPtrC8 servername_; TPtrC8 ciphers_; TInetAddr local_addr_; TSockXfrLength sent_len_; CPjSSLSocket_cb cb_; void *key_; void DoCancel(); void RunL(); CPjSSLSocket(pj_qos_type qos_type, const pj_qos_params &qos_params) : CActive(0), state_(SSL_STATE_NULL), sock_(PJ_INVALID_SOCKET), securesock_(NULL), is_connected_(false), qos_type_(qos_type), qos_params_(qos_params), reader_(NULL), cb_(NULL), key_(NULL) {} void ConstructL(const TDesC8 &ssl_proto) { ssl_proto_.Copy(ssl_proto); CActiveScheduler::Add(this); } void CleanupSubObjects() { delete reader_; reader_ = NULL; if (securesock_) { if (state_ == SSL_STATE_ESTABLISHED) securesock_->Close(); delete securesock_; securesock_ = NULL; } if (sock_ != PJ_INVALID_SOCKET) { pj_sock_close(sock_); sock_ = PJ_INVALID_SOCKET; } } }; int CPjSSLSocket::Connect(CPjSSLSocket_cb cb, void *key, const TInetAddr &local_addr, const TInetAddr &rem_addr, const TDesC8 &servername, const TDesC8 &ciphers) { pj_status_t status; PJ_ASSERT_RETURN(state_ == SSL_STATE_NULL, PJ_EINVALIDOP); status = pj_sock_socket(rem_addr.Family(), pj_SOCK_STREAM(), 0, &sock_); if (status != PJ_SUCCESS) return status; // Apply QoS status = pj_sock_apply_qos2(sock_, qos_type_, &qos_params_, 2, THIS_FILE, NULL); RSocket &rSock = ((CPjSocket*)sock_)->Socket(); local_addr_ = local_addr; if (!local_addr_.IsUnspecified()) { TInt err = rSock.Bind(local_addr_); if (err != KErrNone) return PJ_RETURN_OS_ERROR(err); } cb_ = cb; key_ = key; rem_addr_ = rem_addr; /* Note: the following members only keep the pointer, not the data */ servername_.Set(servername); ciphers_.Set(ciphers); rSock.Connect(rem_addr_, iStatus); SetActive(); state_ = SSL_STATE_CONNECTING; rSock.LocalName(local_addr_); return PJ_EPENDING; } int CPjSSLSocket::Send(CPjSSLSocket_cb cb, void *key, const TDesC8 &aDesc, TUint flags) { PJ_UNUSED_ARG(flags); PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP); if (IsActive()) return PJ_EBUSY; cb_ = cb; key_ = key; securesock_->Send(aDesc, iStatus, sent_len_); SetActive(); return PJ_EPENDING; } int CPjSSLSocket::SendSync(const TDesC8 &aDesc, TUint flags) { PJ_UNUSED_ARG(flags); PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, PJ_EINVALIDOP); TRequestStatus reqStatus; securesock_->Send(aDesc, reqStatus, sent_len_); User::WaitForRequest(reqStatus); return PJ_RETURN_OS_ERROR(reqStatus.Int()); } CPjSSLSocketReader* CPjSSLSocket::GetReader() { PJ_ASSERT_RETURN(state_ == SSL_STATE_ESTABLISHED, NULL); if (reader_) return reader_; TRAPD(err, reader_ = CPjSSLSocketReader::NewL(*securesock_)); if (err != KErrNone) return NULL; return reader_; } void CPjSSLSocket::DoCancel() { /* Operation to be cancelled depends on current state */ switch (state_) { case SSL_STATE_CONNECTING: { RSocket &rSock = ((CPjSocket*)sock_)->Socket(); rSock.CancelConnect(); CleanupSubObjects(); state_ = SSL_STATE_NULL; } break; case SSL_STATE_HANDSHAKING: { securesock_->CancelHandshake(); CleanupSubObjects(); state_ = SSL_STATE_NULL; } break; case SSL_STATE_ESTABLISHED: securesock_->CancelSend(); break; default: break; } } void CPjSSLSocket::RunL() { switch (state_) { case SSL_STATE_CONNECTING: if (iStatus != KErrNone) { CleanupSubObjects(); state_ = SSL_STATE_NULL; /* Dispatch connect failure notification */ if (cb_) (*cb_)(iStatus.Int(), key_); } else { RSocket &rSock = ((CPjSocket*)sock_)->Socket(); /* Get local addr */ rSock.LocalName(local_addr_); /* Prepare and start handshake */ securesock_ = CSecureSocket::NewL(rSock, ssl_proto_); securesock_->SetDialogMode(EDialogModeAttended); if (servername_.Length() > 0) securesock_->SetOpt(KSoSSLDomainName, KSolInetSSL, servername_); if (ciphers_.Length() > 0) securesock_->SetAvailableCipherSuites(ciphers_); // FlushSessionCache() seems to also fire signals to all // completed AOs (something like CActiveScheduler::RunIfReady()) // which may cause problem, e.g: we've experienced that when // SSL timeout is set to 1s, the SSL timeout timer fires up // at this point and securesock_ instance gets deleted here! // So be careful using this. And we don't think we need it here. //securesock_->FlushSessionCache(); securesock_->StartClientHandshake(iStatus); SetActive(); state_ = SSL_STATE_HANDSHAKING; } break; case SSL_STATE_HANDSHAKING: if (iStatus == KErrNone) { state_ = SSL_STATE_ESTABLISHED; } else { state_ = SSL_STATE_NULL; CleanupSubObjects(); } /* Dispatch connect status notification */ if (cb_) (*cb_)(iStatus.Int(), key_); break; case SSL_STATE_ESTABLISHED: /* Dispatch data sent notification */ if (cb_) (*cb_)(iStatus.Int(), key_); break; default: pj_assert(0); break; } } typedef void (*CPjTimer_cb)(void *user_data); class CPjTimer : public CActive { public: CPjTimer(const pj_time_val *delay, CPjTimer_cb cb, void *user_data) : CActive(0), cb_(cb), user_data_(user_data) { CActiveScheduler::Add(this); rtimer_.CreateLocal(); pj_int32_t interval = PJ_TIME_VAL_MSEC(*delay) * 1000; if (interval < 0) { interval = 0; } rtimer_.After(iStatus, interval); SetActive(); } ~CPjTimer() { Cancel(); } private: RTimer rtimer_; CPjTimer_cb cb_; void *user_data_; void RunL() { if (cb_) (*cb_)(user_data_); } void DoCancel() { rtimer_.Cancel(); } }; /* * Structure of recv/read state. */ typedef struct read_state_t { TPtr8 *read_buf; TPtr8 *orig_buf; pj_uint32_t flags; } read_state_t; /* * Structure of send/write data. */ typedef struct write_data_t { pj_size_t len; pj_ioqueue_op_key_t *key; pj_size_t data_len; char data[1]; } write_data_t; /* * Structure of send/write state. */ typedef struct write_state_t { char *buf; pj_size_t max_len; char *start; pj_size_t len; write_data_t *current_data; TPtrC8 send_ptr; } write_state_t; /* * Secure socket structure definition. */ struct pj_ssl_sock_t { pj_pool_t *pool; pj_ssl_sock_cb cb; void *user_data; pj_bool_t established; write_state_t write_state; read_state_t read_state; CPjTimer *connect_timer; CPjSSLSocket *sock; int sock_af; int sock_type; pj_sockaddr local_addr; pj_sockaddr rem_addr; /* QoS settings */ pj_qos_type qos_type; pj_qos_params qos_params; pj_bool_t qos_ignore_error; pj_ssl_sock_proto proto; pj_time_val timeout; pj_str_t servername; pj_str_t ciphers; pj_ssl_cert_info remote_cert_info; }; static pj_str_t get_cert_name(char *buf, unsigned buf_len, const CX500DistinguishedName &name) { TInt i; TUint8 *p; TInt l = buf_len; p = (TUint8*)buf; for(i = 0; i < name.Count(); ++i) { const CX520AttributeTypeAndValue &attr = name.Element(i); /* Print element separator */ *p++ = '/'; if (0 == --l) break; /* Print the type. */ TPtr8 type(p, l); type.Copy(attr.Type()); p += type.Length(); l -= type.Length(); if (0 >= --l) break; /* Print equal sign */ *p++ = '='; if (0 == --l) break; /* Print the value. Let's just get the raw data here */ TPtr8 value(p, l); value.Copy(attr.EncodedValue().Mid(2)); p += value.Length(); l -= value.Length(); if (0 >= --l) break; } pj_str_t src; pj_strset(&src, buf, buf_len - l); return src; } /* Get certificate info from CX509Certificate. */ static void get_cert_info(pj_pool_t *pool, pj_ssl_cert_info *ci, const CX509Certificate *x) { enum { tmp_buf_len = 512 }; char *tmp_buf; unsigned len; pj_assert(pool && ci && x); /* Init */ tmp_buf = new char[tmp_buf_len]; pj_bzero(ci, sizeof(*ci)); /* Version */ ci->version = x->Version(); /* Serial number */ len = x->SerialNumber().Length(); if (len > sizeof(ci->serial_no)) len = sizeof(ci->serial_no); pj_memcpy(ci->serial_no + sizeof(ci->serial_no) - len, x->SerialNumber().Ptr(), len); /* Subject */ { HBufC *subject = NULL; TRAPD(err, subject = x->SubjectL()); if (err == KErrNone) { TPtr16 ptr16(subject->Des()); len = ptr16.Length(); TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len); ptr8.Copy(ptr16); pj_strset(&ci->subject.cn, (char*)ptr8.Ptr(), ptr8.Length()); } pj_str_t tmp = get_cert_name(tmp_buf, tmp_buf_len, x->SubjectName()); pj_strdup(pool, &ci->subject.info, &tmp); } /* Issuer */ { HBufC *issuer = NULL; TRAPD(err, issuer = x->IssuerL()); if (err == KErrNone) { TPtr16 ptr16(issuer->Des()); len = ptr16.Length(); TPtr8 ptr8((TUint8*)pj_pool_alloc(pool, len), len); ptr8.Copy(ptr16); pj_strset(&ci->issuer.cn, (char*)ptr8.Ptr(), ptr8.Length()); } pj_str_t tmp = get_cert_name(tmp_buf, tmp_buf_len, x->IssuerName()); pj_strdup(pool, &ci->issuer.info, &tmp); } /* Validity */ const CValidityPeriod &valid_period = x->ValidityPeriod(); TTime base_time(TDateTime(1970, EJanuary, 0, 0, 0, 0, 0)); TTimeIntervalSeconds tmp_sec; valid_period.Start().SecondsFrom(base_time, tmp_sec); ci->validity.start.sec = tmp_sec.Int(); valid_period.Finish().SecondsFrom(base_time, tmp_sec); ci->validity.end.sec = tmp_sec.Int(); /* Deinit */ delete [] tmp_buf; } /* Update certificates info. This function should be called after handshake * or renegotiation successfully completed. */ static void update_certs_info(pj_ssl_sock_t *ssock) { const CX509Certificate *x; pj_assert(ssock && ssock->sock && ssock->sock->GetState() == CPjSSLSocket::SSL_STATE_ESTABLISHED); /* Active remote certificate */ x = ssock->sock->GetPeerCert(); if (x) { get_cert_info(ssock->pool, &ssock->remote_cert_info, x); } else { pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info)); } } /* Available ciphers */ static unsigned ciphers_num_ = 0; static struct ciphers_t { pj_ssl_cipher id; const char *name; } ciphers_[64]; /* * Get cipher list supported by SSL/TLS backend. */ PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables (pj_ssl_cipher ciphers[], unsigned *cipher_num) { unsigned i; PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL); if (ciphers_num_ == 0) { RSocket sock; CSecureSocket *secure_sock; TPtrC16 proto(_L16("TLS1.0")); secure_sock = CSecureSocket::NewL(sock, proto); if (secure_sock) { TBuf8<128> ciphers_buf(0); secure_sock->AvailableCipherSuites(ciphers_buf); ciphers_num_ = ciphers_buf.Length() / 2; if (ciphers_num_ > PJ_ARRAY_SIZE(ciphers_)) ciphers_num_ = PJ_ARRAY_SIZE(ciphers_); for (i = 0; i < ciphers_num_; ++i) { ciphers_[i].id = (pj_ssl_cipher)(ciphers_buf[i*2]*10 + ciphers_buf[i*2+1]); ciphers_[i].name = get_cipher_name(ciphers_[i].id); } } delete secure_sock; } if (ciphers_num_ == 0) { *cipher_num = 0; return PJ_ENOTFOUND; } *cipher_num = PJ_MIN(*cipher_num, ciphers_num_); for (i = 0; i < *cipher_num; ++i) ciphers[i] = ciphers_[i].id; return PJ_SUCCESS; } /* Get cipher name string */ PJ_DEF(const char*) pj_ssl_cipher_name(pj_ssl_cipher cipher) { unsigned i; if (ciphers_num_ == 0) { pj_ssl_cipher c[1]; i = 0; pj_ssl_cipher_get_availables(c, &i); } for (i = 0; i < ciphers_num_; ++i) { if (cipher == ciphers_[i].id) return ciphers_[i].name; } return NULL; } /* Get cipher identifier */ PJ_DEF(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name) { unsigned i; if (ciphers_num_ == 0) { pj_ssl_cipher c[1]; i = 0; pj_ssl_cipher_get_availables(c, &i); } for (i = 0; i < ciphers_num_; ++i) { if (!pj_ansi_stricmp(ciphers_[i].name, cipher_name)) return ciphers_[i].id; } return PJ_TLS_UNKNOWN_CIPHER; } /* Check if the specified cipher is supported by SSL/TLS backend. */ PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher) { unsigned i; if (ciphers_num_ == 0) { pj_ssl_cipher c[1]; i = 0; pj_ssl_cipher_get_availables(c, &i); } for (i = 0; i < ciphers_num_; ++i) { if (cipher == ciphers_[i].id) return PJ_TRUE; } return PJ_FALSE; } /* * Create SSL socket instance. */ PJ_DEF(pj_status_t) pj_ssl_sock_create (pj_pool_t *pool, const pj_ssl_sock_param *param, pj_ssl_sock_t **p_ssock) { pj_ssl_sock_t *ssock; PJ_ASSERT_RETURN(param->async_cnt == 1, PJ_EINVAL); PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL); /* Allocate secure socket */ ssock = PJ_POOL_ZALLOC_T(pool, pj_ssl_sock_t); /* Allocate write buffer */ ssock->write_state.buf = (char*)pj_pool_alloc(pool, param->send_buffer_size); ssock->write_state.max_len = param->send_buffer_size; ssock->write_state.start = ssock->write_state.buf; /* Init secure socket */ ssock->pool = pool; ssock->sock_af = param->sock_af; ssock->sock_type = param->sock_type; ssock->cb = param->cb; ssock->user_data = param->user_data; ssock->timeout = param->timeout; if (param->ciphers_num > 0) { /* Cipher list in Symbian is represented as array of two-octets. */ ssock->ciphers.slen = param->ciphers_num*2; ssock->ciphers.ptr = (char*)pj_pool_alloc(pool, ssock->ciphers.slen); pj_uint8_t *c = (pj_uint8_t*)ssock->ciphers.ptr; for (unsigned i = 0; i < param->ciphers_num; ++i) { *c++ = (pj_uint8_t)(param->ciphers[i] & 0xFF00) >> 8; *c++ = (pj_uint8_t)(param->ciphers[i] & 0xFF); } } pj_strdup_with_null(pool, &ssock->servername, ¶m->server_name); ssock->qos_type = param->qos_type; ssock->qos_ignore_error = param->qos_ignore_error; pj_memcpy(&ssock->qos_params, ¶m->qos_params, sizeof(param->qos_params)); /* Finally */ *p_ssock = ssock; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *cert_file, const pj_str_t *privkey_file, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert) { return pj_ssl_cert_load_from_files2(pool, CA_file, NULL, cert_file, privkey_file, privkey_pass, p_cert); } PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files2(pj_pool_t *pool, const pj_str_t *CA_file, const pj_str_t *CA_path, const pj_str_t *cert_file, const pj_str_t *privkey_file, const pj_str_t *privkey_pass, pj_ssl_cert_t **p_cert) { PJ_UNUSED_ARG(pool); PJ_UNUSED_ARG(CA_file); PJ_UNUSED_ARG(CA_path); PJ_UNUSED_ARG(cert_file); PJ_UNUSED_ARG(privkey_file); PJ_UNUSED_ARG(privkey_pass); PJ_UNUSED_ARG(p_cert); return PJ_ENOTSUP; } /* * Set SSL socket credential. */ PJ_DEF(pj_status_t) pj_ssl_sock_set_certificate( pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_ssl_cert_t *cert) { PJ_UNUSED_ARG(ssock); PJ_UNUSED_ARG(pool); PJ_UNUSED_ARG(cert); return PJ_ENOTSUP; } /* * Close the SSL socket. */ PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock) { PJ_ASSERT_RETURN(ssock, PJ_EINVAL); delete ssock->connect_timer; ssock->connect_timer = NULL; delete ssock->sock; ssock->sock = NULL; delete ssock->read_state.read_buf; delete ssock->read_state.orig_buf; ssock->read_state.read_buf = NULL; ssock->read_state.orig_buf = NULL; return PJ_SUCCESS; } /* * Associate arbitrary data with the SSL socket. */ PJ_DEF(pj_status_t) pj_ssl_sock_set_user_data (pj_ssl_sock_t *ssock, void *user_data) { PJ_ASSERT_RETURN(ssock, PJ_EINVAL); ssock->user_data = user_data; return PJ_SUCCESS; } /* * Retrieve the user data previously associated with this SSL * socket. */ PJ_DEF(void*) pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock) { PJ_ASSERT_RETURN(ssock, NULL); return ssock->user_data; } /* * Retrieve the local address and port used by specified SSL socket. */ PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock, pj_ssl_sock_info *info) { PJ_ASSERT_RETURN(ssock && info, PJ_EINVAL); pj_bzero(info, sizeof(*info)); info->established = ssock->established; /* Local address */ if (ssock->sock) { const TInetAddr* local_addr_ = ssock->sock->GetLocalAddr(); int addrlen = sizeof(pj_sockaddr); pj_status_t status; status = PjSymbianOS::Addr2pj(*local_addr_, info->local_addr, &addrlen); if (status != PJ_SUCCESS) return status; } else { pj_sockaddr_cp(&info->local_addr, &ssock->local_addr); } if (info->established) { /* Cipher suite */ TBuf8<4> cipher; if (ssock->sock->GetCipher(cipher) == KErrNone) { info->cipher = (pj_ssl_cipher)cipher[1]; } /* Remote address */ pj_sockaddr_cp((pj_sockaddr_t*)&info->remote_addr, (pj_sockaddr_t*)&ssock->rem_addr); /* Certificates info */ info->remote_cert_info = &ssock->remote_cert_info; } /* Protocol */ info->proto = ssock->proto; return PJ_SUCCESS; } /* * Starts read operation on this SSL socket. */ PJ_DEF(pj_status_t) pj_ssl_sock_start_read (pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) { PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL); PJ_ASSERT_RETURN(ssock->established, PJ_EINVALIDOP); /* Reading is already started */ if (ssock->read_state.orig_buf) { return PJ_SUCCESS; } void *readbuf[1]; readbuf[0] = pj_pool_alloc(pool, buff_size); return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags); } static void read_cb(int err, void *key) { pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key; pj_status_t status; status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err); /* Check connection status */ if (err == KErrEof || !PjSymbianOS::Instance()->IsConnectionUp() || !ssock->established) { status = PJ_EEOF; } /* Notify data arrival */ if (ssock->cb.on_data_read) { pj_size_t remainder = 0; char *data = (char*)ssock->read_state.orig_buf->Ptr(); pj_size_t data_len = ssock->read_state.read_buf->Length() + ssock->read_state.read_buf->Ptr() - ssock->read_state.orig_buf->Ptr(); if (data_len > 0) { /* Notify received data */ pj_bool_t ret = (*ssock->cb.on_data_read)(ssock, data, data_len, status, &remainder); if (!ret) { /* We've been destroyed */ return; } /* Calculate available data for next READ operation */ if (remainder > 0) { pj_size_t data_maxlen = ssock->read_state.orig_buf->MaxLength(); /* There is some data left unconsumed by application, we give * smaller buffer for next READ operation. */ ssock->read_state.read_buf->Set((TUint8*)data+remainder, 0, data_maxlen - remainder); } else { /* Give all buffer for next READ operation. */ ssock->read_state.read_buf->Set(*ssock->read_state.orig_buf); } } } if (status == PJ_SUCCESS) { /* Perform the "next" READ operation */ CPjSSLSocketReader *reader = ssock->sock->GetReader(); ssock->read_state.read_buf->SetLength(0); status = reader->Read(&read_cb, ssock, *ssock->read_state.read_buf, ssock->read_state.flags); } /* Connection closed or something goes wrong */ if (status != PJ_SUCCESS && status != PJ_EPENDING) { /* Notify error */ if (ssock->cb.on_data_read) { pj_bool_t ret = (*ssock->cb.on_data_read)(ssock, NULL, 0, status, NULL); if (!ret) { /* We've been destroyed */ return; } } delete ssock->read_state.read_buf; delete ssock->read_state.orig_buf; ssock->read_state.read_buf = NULL; ssock->read_state.orig_buf = NULL; ssock->established = PJ_FALSE; } } /* * Same as #pj_ssl_sock_start_read(), except that the application * supplies the buffers for the read operation so that the acive socket * does not have to allocate the buffers. */ PJ_DEF(pj_status_t) pj_ssl_sock_start_read2 (pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], pj_uint32_t flags) { PJ_ASSERT_RETURN(ssock && buff_size && readbuf, PJ_EINVAL); PJ_ASSERT_RETURN(ssock->established, PJ_EINVALIDOP); /* Return failure if access point is marked as down by app. */ PJ_SYMBIAN_CHECK_CONNECTION(); /* Reading is already started */ if (ssock->read_state.orig_buf) { return PJ_SUCCESS; } PJ_UNUSED_ARG(pool); /* Get reader instance */ CPjSSLSocketReader *reader = ssock->sock->GetReader(); if (!reader) return PJ_ENOMEM; /* We manage two buffer pointers here: * 1. orig_buf keeps the orginal buffer address (and its max length). * 2. read_buf provides buffer for READ operation, mind that there may be * some remainder data left by application. */ ssock->read_state.read_buf = new TPtr8((TUint8*)readbuf[0], 0, buff_size); ssock->read_state.orig_buf = new TPtr8((TUint8*)readbuf[0], 0, buff_size); ssock->read_state.flags = flags; pj_status_t status; status = reader->Read(&read_cb, ssock, *ssock->read_state.read_buf, ssock->read_state.flags); if (status != PJ_SUCCESS && status != PJ_EPENDING) { delete ssock->read_state.read_buf; delete ssock->read_state.orig_buf; ssock->read_state.read_buf = NULL; ssock->read_state.orig_buf = NULL; return status; } return PJ_SUCCESS; } /* * Same as pj_ssl_sock_start_read(), except that this function is used * only for datagram sockets, and it will trigger \a on_data_recvfrom() * callback instead. */ PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom (pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, pj_uint32_t flags) { PJ_UNUSED_ARG(ssock); PJ_UNUSED_ARG(pool); PJ_UNUSED_ARG(buff_size); PJ_UNUSED_ARG(flags); return PJ_ENOTSUP; } /* * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() * operation takes the buffer from the argument rather than creating * new ones. */ PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom2 (pj_ssl_sock_t *ssock, pj_pool_t *pool, unsigned buff_size, void *readbuf[], pj_uint32_t flags) { PJ_UNUSED_ARG(ssock); PJ_UNUSED_ARG(pool); PJ_UNUSED_ARG(buff_size); PJ_UNUSED_ARG(readbuf); PJ_UNUSED_ARG(flags); return PJ_ENOTSUP; } static void send_cb(int err, void *key) { pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key; write_state_t *st = &ssock->write_state; /* Check connection status */ if (err != KErrNone || !PjSymbianOS::Instance()->IsConnectionUp() || !ssock->established) { ssock->established = PJ_FALSE; return; } /* Remove sent data from buffer */ st->start += st->current_data->len; st->len -= st->current_data->len; /* Reset current outstanding send */ st->current_data = NULL; /* Let's check if there is pending data to send */ if (st->len) { write_data_t *wdata = (write_data_t*)st->start; pj_status_t status; st->send_ptr.Set((TUint8*)wdata->data, (TInt)wdata->data_len); st->current_data = wdata; status = ssock->sock->Send(&send_cb, ssock, st->send_ptr, 0); if (status != PJ_EPENDING) { ssock->established = PJ_FALSE; st->len = 0; return; } } else { /* Buffer empty, reset the start position */ st->start = st->buf; } } /* * Send data using the socket. */ PJ_DEF(pj_status_t) pj_ssl_sock_send (pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, unsigned flags) { PJ_CHECK_STACK(); PJ_ASSERT_RETURN(ssock && data && size, PJ_EINVAL); PJ_ASSERT_RETURN(ssock->write_state.max_len == 0 || ssock->write_state.max_len >= (pj_size_t)*size, PJ_ETOOSMALL); /* Check connection status */ if (!PjSymbianOS::Instance()->IsConnectionUp() || !ssock->established) { ssock->established = PJ_FALSE; return PJ_ECANCELLED; } write_state_t *st = &ssock->write_state; /* Synchronous mode */ if (st->max_len == 0) { st->send_ptr.Set((TUint8*)data, (TInt)*size); return ssock->sock->SendSync(st->send_ptr, flags); } /* CSecureSocket only allows one outstanding send operation, so * we use buffering mechanism to allow application to perform send * operations at any time. */ pj_size_t needed_len = *size + sizeof(write_data_t) - 1; /* Align needed_len to be multiplication of 4 */ needed_len = ((needed_len + 3) >> 2) << 2; /* Block until there is buffer slot available and contiguous! */ while (st->start + st->len + needed_len > st->buf + st->max_len) { pj_symbianos_poll(-1, -1); } /* Push back the send data into the buffer */ write_data_t *wdata = (write_data_t*)(st->start + st->len); wdata->len = needed_len; wdata->key = send_key; wdata->data_len = (pj_size_t)*size; pj_memcpy(wdata->data, data, *size); st->len += needed_len; /* If no outstanding send, send it */ if (st->current_data == NULL) { pj_status_t status; wdata = (write_data_t*)st->start; st->current_data = wdata; st->send_ptr.Set((TUint8*)wdata->data, (TInt)wdata->data_len); status = ssock->sock->Send(&send_cb, ssock, st->send_ptr, flags); if (status != PJ_EPENDING) { *size = -status; return status; } } return PJ_SUCCESS; } /* * Send datagram using the socket. */ PJ_DEF(pj_status_t) pj_ssl_sock_sendto (pj_ssl_sock_t *ssock, pj_ioqueue_op_key_t *send_key, const void *data, pj_ssize_t *size, unsigned flags, const pj_sockaddr_t *addr, int addr_len) { PJ_UNUSED_ARG(ssock); PJ_UNUSED_ARG(send_key); PJ_UNUSED_ARG(data); PJ_UNUSED_ARG(size); PJ_UNUSED_ARG(flags); PJ_UNUSED_ARG(addr); PJ_UNUSED_ARG(addr_len); return PJ_ENOTSUP; } /* * Starts asynchronous socket accept() operations on this SSL socket. */ PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *local_addr, int addr_len) { PJ_UNUSED_ARG(ssock); PJ_UNUSED_ARG(pool); PJ_UNUSED_ARG(local_addr); PJ_UNUSED_ARG(addr_len); return PJ_ENOTSUP; } static void connect_cb(int err, void *key) { pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)key; pj_status_t status; if (ssock->connect_timer) { delete ssock->connect_timer; ssock->connect_timer = NULL; } status = (err == KErrNone)? PJ_SUCCESS : PJ_RETURN_OS_ERROR(err); if (status == PJ_SUCCESS) { ssock->established = PJ_TRUE; update_certs_info(ssock); } else { delete ssock->sock; ssock->sock = NULL; if (err == KErrTimedOut) status = PJ_ETIMEDOUT; } if (ssock->cb.on_connect_complete) { pj_bool_t ret = (*ssock->cb.on_connect_complete)(ssock, status); if (!ret) { /* We've been destroyed */ return; } } } static void connect_timer_cb(void *key) { connect_cb(KErrTimedOut, key); } /* * Starts asynchronous socket connect() operation and SSL/TLS handshaking * for this socket. Once the connection is done (either successfully or not), * the \a on_connect_complete() callback will be called. */ PJ_DEF(pj_status_t) pj_ssl_sock_start_connect (pj_ssl_sock_t *ssock, pj_pool_t *pool, const pj_sockaddr_t *localaddr, const pj_sockaddr_t *remaddr, int addr_len) { CPjSSLSocket *sock = NULL; pj_status_t status; PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len, PJ_EINVAL); /* Check connection status */ PJ_SYMBIAN_CHECK_CONNECTION(); if (ssock->sock != NULL) { CPjSSLSocket::ssl_state state = ssock->sock->GetState(); switch (state) { case CPjSSLSocket::SSL_STATE_ESTABLISHED: return PJ_SUCCESS; default: return PJ_EPENDING; } } /* Set SSL protocol */ TPtrC8 proto; if (ssock->proto == PJ_SSL_SOCK_PROTO_DEFAULT) ssock->proto = PJ_SSL_SOCK_PROTO_TLS1; /* CSecureSocket only support TLS1.0 and SSL3.0 */ if (ssock->proto & PJ_SSL_SOCK_PROTO_TLS1==PJ_SSL_SOCK_PROTO_TLS1) { proto.Set((const TUint8*)"TLS1.0", 6); } else if (ssock->proto & PJ_SSL_SOCK_PROTO_SSL3==PJ_SSL_SOCK_PROTO_SSL3) { proto.Set((const TUint8*)"SSL3.0", 6); } else { return PJ_ENOTSUP; } /* Prepare addresses */ TInetAddr localaddr_, remaddr_; status = PjSymbianOS::pj2Addr(*(pj_sockaddr*)localaddr, addr_len, localaddr_); if (status != PJ_SUCCESS) return status; status = PjSymbianOS::pj2Addr(*(pj_sockaddr*)remaddr, addr_len, remaddr_); if (status != PJ_SUCCESS) return status; pj_sockaddr_cp((pj_sockaddr_t*)&ssock->rem_addr, remaddr); /* Init SSL engine */ TRAPD(err, sock = CPjSSLSocket::NewL(proto, ssock->qos_type, ssock->qos_params)); if (err != KErrNone) return PJ_ENOMEM; if (ssock->timeout.sec != 0 || ssock->timeout.msec != 0) { ssock->connect_timer = new CPjTimer(&ssock->timeout, &connect_timer_cb, ssock); } /* Convert server name to Symbian descriptor */ TPtrC8 servername_((TUint8*)ssock->servername.ptr, ssock->servername.slen); /* Convert cipher list to Symbian descriptor */ TPtrC8 ciphers_((TUint8*)ssock->ciphers.ptr, ssock->ciphers.slen); /* Try to connect */ status = sock->Connect(&connect_cb, ssock, localaddr_, remaddr_, servername_, ciphers_); if (status != PJ_SUCCESS && status != PJ_EPENDING) { delete sock; return status; } ssock->sock = sock; return status; } PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock) { PJ_UNUSED_ARG(ssock); return PJ_ENOTSUP; }