blob: 2e78adfc0e2ae9ec994c63d6ccd9018067c5eca1 [file] [log] [blame]
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <vector>
#if HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H
#if HAVE_NSS_SSL_H
#include "webrtc/base/nssstreamadapter.h"
#include "keyhi.h"
#include "nspr.h"
#include "nss.h"
#include "pk11pub.h"
#include "secerr.h"
#ifdef NSS_SSL_RELATIVE_PATH
#include "ssl.h"
#include "sslerr.h"
#include "sslproto.h"
#else
#include "net/third_party/nss/ssl/ssl.h"
#include "net/third_party/nss/ssl/sslerr.h"
#include "net/third_party/nss/ssl/sslproto.h"
#endif
#include "webrtc/base/nssidentity.h"
#include "webrtc/base/safe_conversions.h"
#include "webrtc/base/thread.h"
namespace rtc {
PRDescIdentity NSSStreamAdapter::nspr_layer_identity = PR_INVALID_IO_LAYER;
#define UNIMPLEMENTED \
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); \
LOG(LS_ERROR) \
<< "Call to unimplemented function "<< __FUNCTION__; ASSERT(false)
#ifdef SRTP_AES128_CM_HMAC_SHA1_80
#define HAVE_DTLS_SRTP
#endif
#ifdef HAVE_DTLS_SRTP
// SRTP cipher suite table
struct SrtpCipherMapEntry {
const char* external_name;
PRUint16 cipher_id;
};
// This isn't elegant, but it's better than an external reference
static const SrtpCipherMapEntry kSrtpCipherMap[] = {
{"AES_CM_128_HMAC_SHA1_80", SRTP_AES128_CM_HMAC_SHA1_80 },
{"AES_CM_128_HMAC_SHA1_32", SRTP_AES128_CM_HMAC_SHA1_32 },
{NULL, 0}
};
#endif
// Ciphers to enable to get ECDHE encryption with endpoints that support it.
static const uint32_t kEnabledCiphers[] = {
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256};
// Default cipher used between NSS stream adapters.
// This needs to be updated when the default of the SSL library changes.
static const char kDefaultSslCipher10[] =
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA";
static const char kDefaultSslCipher12[] =
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256";
static const char kDefaultSslEcCipher10[] =
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA";
static const char kDefaultSslEcCipher12[] =
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256";
// Implementation of NSPR methods
static PRStatus StreamClose(PRFileDesc *socket) {
ASSERT(!socket->lower);
socket->dtor(socket);
return PR_SUCCESS;
}
static PRInt32 StreamRead(PRFileDesc *socket, void *buf, PRInt32 length) {
StreamInterface *stream = reinterpret_cast<StreamInterface *>(socket->secret);
size_t read;
int error;
StreamResult result = stream->Read(buf, length, &read, &error);
if (result == SR_SUCCESS) {
return checked_cast<PRInt32>(read);
}
if (result == SR_EOS) {
return 0;
}
if (result == SR_BLOCK) {
PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
return -1;
}
PR_SetError(PR_UNKNOWN_ERROR, error);
return -1;
}
static PRInt32 StreamWrite(PRFileDesc *socket, const void *buf,
PRInt32 length) {
StreamInterface *stream = reinterpret_cast<StreamInterface *>(socket->secret);
size_t written;
int error;
StreamResult result = stream->Write(buf, length, &written, &error);
if (result == SR_SUCCESS) {
return checked_cast<PRInt32>(written);
}
if (result == SR_BLOCK) {
LOG(LS_INFO) <<
"NSSStreamAdapter: write to underlying transport would block";
PR_SetError(PR_WOULD_BLOCK_ERROR, 0);
return -1;
}
LOG(LS_ERROR) << "Write error";
PR_SetError(PR_UNKNOWN_ERROR, error);
return -1;
}
static PRInt32 StreamAvailable(PRFileDesc *socket) {
UNIMPLEMENTED;
return -1;
}
PRInt64 StreamAvailable64(PRFileDesc *socket) {
UNIMPLEMENTED;
return -1;
}
static PRStatus StreamSync(PRFileDesc *socket) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PROffset32 StreamSeek(PRFileDesc *socket, PROffset32 offset,
PRSeekWhence how) {
UNIMPLEMENTED;
return -1;
}
static PROffset64 StreamSeek64(PRFileDesc *socket, PROffset64 offset,
PRSeekWhence how) {
UNIMPLEMENTED;
return -1;
}
static PRStatus StreamFileInfo(PRFileDesc *socket, PRFileInfo *info) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PRStatus StreamFileInfo64(PRFileDesc *socket, PRFileInfo64 *info) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PRInt32 StreamWritev(PRFileDesc *socket, const PRIOVec *iov,
PRInt32 iov_size, PRIntervalTime timeout) {
UNIMPLEMENTED;
return -1;
}
static PRStatus StreamConnect(PRFileDesc *socket, const PRNetAddr *addr,
PRIntervalTime timeout) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PRFileDesc *StreamAccept(PRFileDesc *sd, PRNetAddr *addr,
PRIntervalTime timeout) {
UNIMPLEMENTED;
return NULL;
}
static PRStatus StreamBind(PRFileDesc *socket, const PRNetAddr *addr) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PRStatus StreamListen(PRFileDesc *socket, PRIntn depth) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PRStatus StreamShutdown(PRFileDesc *socket, PRIntn how) {
UNIMPLEMENTED;
return PR_FAILURE;
}
// Note: this is always nonblocking and ignores the timeout.
// TODO(ekr@rtfm.com): In future verify that the socket is
// actually in non-blocking mode.
// This function does not support peek.
static PRInt32 StreamRecv(PRFileDesc *socket, void *buf, PRInt32 amount,
PRIntn flags, PRIntervalTime to) {
ASSERT(flags == 0);
if (flags != 0) {
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
return -1;
}
return StreamRead(socket, buf, amount);
}
// Note: this is always nonblocking and assumes a zero timeout.
// This function does not support peek.
static PRInt32 StreamSend(PRFileDesc *socket, const void *buf,
PRInt32 amount, PRIntn flags,
PRIntervalTime to) {
ASSERT(flags == 0);
return StreamWrite(socket, buf, amount);
}
static PRInt32 StreamRecvfrom(PRFileDesc *socket, void *buf,
PRInt32 amount, PRIntn flags,
PRNetAddr *addr, PRIntervalTime to) {
UNIMPLEMENTED;
return -1;
}
static PRInt32 StreamSendto(PRFileDesc *socket, const void *buf,
PRInt32 amount, PRIntn flags,
const PRNetAddr *addr, PRIntervalTime to) {
UNIMPLEMENTED;
return -1;
}
static PRInt16 StreamPoll(PRFileDesc *socket, PRInt16 in_flags,
PRInt16 *out_flags) {
UNIMPLEMENTED;
return -1;
}
static PRInt32 StreamAcceptRead(PRFileDesc *sd, PRFileDesc **nd,
PRNetAddr **raddr,
void *buf, PRInt32 amount, PRIntervalTime t) {
UNIMPLEMENTED;
return -1;
}
static PRInt32 StreamTransmitFile(PRFileDesc *sd, PRFileDesc *socket,
const void *headers, PRInt32 hlen,
PRTransmitFileFlags flags, PRIntervalTime t) {
UNIMPLEMENTED;
return -1;
}
static PRStatus StreamGetPeerName(PRFileDesc *socket, PRNetAddr *addr) {
// TODO(ekr@rtfm.com): Modify to return unique names for each channel
// somehow, as opposed to always the same static address. The current
// implementation messes up the session cache, which is why it's off
// elsewhere
addr->inet.family = PR_AF_INET;
addr->inet.port = 0;
addr->inet.ip = 0;
return PR_SUCCESS;
}
static PRStatus StreamGetSockName(PRFileDesc *socket, PRNetAddr *addr) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PRStatus StreamGetSockOption(PRFileDesc *socket, PRSocketOptionData *opt) {
switch (opt->option) {
case PR_SockOpt_Nonblocking:
opt->value.non_blocking = PR_TRUE;
return PR_SUCCESS;
default:
UNIMPLEMENTED;
break;
}
return PR_FAILURE;
}
// Imitate setting socket options. These are mostly noops.
static PRStatus StreamSetSockOption(PRFileDesc *socket,
const PRSocketOptionData *opt) {
switch (opt->option) {
case PR_SockOpt_Nonblocking:
return PR_SUCCESS;
case PR_SockOpt_NoDelay:
return PR_SUCCESS;
default:
UNIMPLEMENTED;
break;
}
return PR_FAILURE;
}
static PRInt32 StreamSendfile(PRFileDesc *out, PRSendFileData *in,
PRTransmitFileFlags flags, PRIntervalTime to) {
UNIMPLEMENTED;
return -1;
}
static PRStatus StreamConnectContinue(PRFileDesc *socket, PRInt16 flags) {
UNIMPLEMENTED;
return PR_FAILURE;
}
static PRIntn StreamReserved(PRFileDesc *socket) {
UNIMPLEMENTED;
return -1;
}
static const struct PRIOMethods nss_methods = {
PR_DESC_LAYERED,
StreamClose,
StreamRead,
StreamWrite,
StreamAvailable,
StreamAvailable64,
StreamSync,
StreamSeek,
StreamSeek64,
StreamFileInfo,
StreamFileInfo64,
StreamWritev,
StreamConnect,
StreamAccept,
StreamBind,
StreamListen,
StreamShutdown,
StreamRecv,
StreamSend,
StreamRecvfrom,
StreamSendto,
StreamPoll,
StreamAcceptRead,
StreamTransmitFile,
StreamGetSockName,
StreamGetPeerName,
StreamReserved,
StreamReserved,
StreamGetSockOption,
StreamSetSockOption,
StreamSendfile,
StreamConnectContinue,
StreamReserved,
StreamReserved,
StreamReserved,
StreamReserved
};
NSSStreamAdapter::NSSStreamAdapter(StreamInterface *stream)
: SSLStreamAdapterHelper(stream),
ssl_fd_(NULL),
cert_ok_(false) {
}
bool NSSStreamAdapter::Init() {
if (nspr_layer_identity == PR_INVALID_IO_LAYER) {
nspr_layer_identity = PR_GetUniqueIdentity("nssstreamadapter");
}
PRFileDesc *pr_fd = PR_CreateIOLayerStub(nspr_layer_identity, &nss_methods);
if (!pr_fd)
return false;
pr_fd->secret = reinterpret_cast<PRFilePrivate *>(stream());
PRFileDesc *ssl_fd;
if (ssl_mode_ == SSL_MODE_DTLS) {
ssl_fd = DTLS_ImportFD(NULL, pr_fd);
} else {
ssl_fd = SSL_ImportFD(NULL, pr_fd);
}
ASSERT(ssl_fd != NULL); // This should never happen
if (!ssl_fd) {
PR_Close(pr_fd);
return false;
}
SECStatus rv;
// Turn on security.
rv = SSL_OptionSet(ssl_fd, SSL_SECURITY, PR_TRUE);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Error enabling security on SSL Socket";
return false;
}
// Disable SSLv2.
rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SSL2, PR_FALSE);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Error disabling SSL2";
return false;
}
// Disable caching.
// TODO(ekr@rtfm.com): restore this when I have the caching
// identity set.
rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Error disabling cache";
return false;
}
// Disable session tickets.
rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Error enabling tickets";
return false;
}
// Disable renegotiation.
rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION,
SSL_RENEGOTIATE_NEVER);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Error disabling renegotiation";
return false;
}
// Disable false start.
rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Error disabling false start";
return false;
}
// Disable reusing of ECDHE keys. By default NSS, when in server mode, uses
// the same key for multiple connections, so disable this behaviour to get
// ephemeral keys.
rv = SSL_OptionSet(ssl_fd, SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Error disabling ECDHE key reuse";
return false;
}
ssl_fd_ = ssl_fd;
return true;
}
NSSStreamAdapter::~NSSStreamAdapter() {
if (ssl_fd_)
PR_Close(ssl_fd_);
};
int NSSStreamAdapter::BeginSSL() {
SECStatus rv;
if (!Init()) {
Error("Init", -1, false);
return -1;
}
ASSERT(state_ == SSL_CONNECTING);
// The underlying stream has been opened. If we are in peer-to-peer mode
// then a peer certificate must have been specified by now.
ASSERT(!ssl_server_name_.empty() ||
peer_certificate_.get() != NULL ||
!peer_certificate_digest_algorithm_.empty());
LOG(LS_INFO) << "BeginSSL: "
<< (!ssl_server_name_.empty() ? ssl_server_name_ :
"with peer");
if (role_ == SSL_CLIENT) {
LOG(LS_INFO) << "BeginSSL: as client";
rv = SSL_GetClientAuthDataHook(ssl_fd_, GetClientAuthDataHook,
this);
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
} else {
LOG(LS_INFO) << "BeginSSL: as server";
NSSIdentity *identity;
if (identity_.get()) {
identity = static_cast<NSSIdentity *>(identity_.get());
} else {
LOG(LS_ERROR) << "Can't be an SSL server without an identity";
Error("BeginSSL", -1, false);
return -1;
}
rv = SSL_ConfigSecureServer(ssl_fd_, identity->certificate().certificate(),
identity->keypair()->privkey(),
identity->keypair()->ssl_kea_type());
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
// Insist on a certificate from the client
rv = SSL_OptionSet(ssl_fd_, SSL_REQUEST_CERTIFICATE, PR_TRUE);
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
// TODO(juberti): Check for client_auth_enabled()
rv = SSL_OptionSet(ssl_fd_, SSL_REQUIRE_CERTIFICATE, PR_TRUE);
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
}
// Set the version range.
SSLVersionRange vrange;
if (ssl_mode_ == SSL_MODE_DTLS) {
vrange.min = SSL_LIBRARY_VERSION_TLS_1_1;
switch (ssl_max_version_) {
case SSL_PROTOCOL_DTLS_10:
vrange.max = SSL_LIBRARY_VERSION_TLS_1_1;
break;
case SSL_PROTOCOL_DTLS_12:
default:
vrange.max = SSL_LIBRARY_VERSION_TLS_1_2;
break;
}
} else {
// SSL_MODE_TLS
vrange.min = SSL_LIBRARY_VERSION_TLS_1_0;
switch (ssl_max_version_) {
case SSL_PROTOCOL_TLS_10:
vrange.max = SSL_LIBRARY_VERSION_TLS_1_0;
break;
case SSL_PROTOCOL_TLS_11:
vrange.max = SSL_LIBRARY_VERSION_TLS_1_1;
break;
case SSL_PROTOCOL_TLS_12:
default:
vrange.max = SSL_LIBRARY_VERSION_TLS_1_2;
break;
}
}
rv = SSL_VersionRangeSet(ssl_fd_, &vrange);
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
// SRTP
#ifdef HAVE_DTLS_SRTP
if (!srtp_ciphers_.empty()) {
rv = SSL_SetSRTPCiphers(
ssl_fd_, &srtp_ciphers_[0],
checked_cast<unsigned int>(srtp_ciphers_.size()));
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
}
#endif
// Enable additional ciphers.
for (size_t i = 0; i < ARRAY_SIZE(kEnabledCiphers); i++) {
rv = SSL_CipherPrefSet(ssl_fd_, kEnabledCiphers[i], PR_TRUE);
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
}
// Certificate validation
rv = SSL_AuthCertificateHook(ssl_fd_, AuthCertificateHook, this);
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
// Now start the handshake
rv = SSL_ResetHandshake(ssl_fd_, role_ == SSL_SERVER ? PR_TRUE : PR_FALSE);
if (rv != SECSuccess) {
Error("BeginSSL", -1, false);
return -1;
}
return ContinueSSL();
}
int NSSStreamAdapter::ContinueSSL() {
LOG(LS_INFO) << "ContinueSSL";
ASSERT(state_ == SSL_CONNECTING);
// Clear the DTLS timer
Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT);
SECStatus rv = SSL_ForceHandshake(ssl_fd_);
if (rv == SECSuccess) {
LOG(LS_INFO) << "Handshake complete";
ASSERT(cert_ok_);
if (!cert_ok_) {
Error("ContinueSSL", -1, true);
return -1;
}
state_ = SSL_CONNECTED;
StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
return 0;
}
PRInt32 err = PR_GetError();
switch (err) {
case SSL_ERROR_RX_MALFORMED_HANDSHAKE:
if (ssl_mode_ != SSL_MODE_DTLS) {
Error("ContinueSSL", -1, true);
return -1;
} else {
LOG(LS_INFO) << "Malformed DTLS message. Ignoring.";
FALLTHROUGH(); // Fall through
}
case PR_WOULD_BLOCK_ERROR:
LOG(LS_INFO) << "Would have blocked";
if (ssl_mode_ == SSL_MODE_DTLS) {
PRIntervalTime timeout;
SECStatus rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout);
if (rv == SECSuccess) {
LOG(LS_INFO) << "Timeout is " << timeout << " ms";
Thread::Current()->PostDelayed(PR_IntervalToMilliseconds(timeout),
this, MSG_DTLS_TIMEOUT, 0);
}
}
return 0;
default:
LOG(LS_INFO) << "Error " << err;
break;
}
Error("ContinueSSL", -1, true);
return -1;
}
void NSSStreamAdapter::Cleanup() {
if (state_ != SSL_ERROR) {
state_ = SSL_CLOSED;
}
if (ssl_fd_) {
PR_Close(ssl_fd_);
ssl_fd_ = NULL;
}
identity_.reset();
peer_certificate_.reset();
Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT);
}
bool NSSStreamAdapter::GetDigestLength(const std::string& algorithm,
size_t* length) {
return NSSCertificate::GetDigestLength(algorithm, length);
}
StreamResult NSSStreamAdapter::Read(void* data, size_t data_len,
size_t* read, int* error) {
// SSL_CONNECTED sanity check.
switch (state_) {
case SSL_NONE:
case SSL_WAIT:
case SSL_CONNECTING:
return SR_BLOCK;
case SSL_CONNECTED:
break;
case SSL_CLOSED:
return SR_EOS;
case SSL_ERROR:
default:
if (error)
*error = ssl_error_code_;
return SR_ERROR;
}
PRInt32 rv = PR_Read(ssl_fd_, data, checked_cast<PRInt32>(data_len));
if (rv == 0) {
return SR_EOS;
}
// Error
if (rv < 0) {
PRInt32 err = PR_GetError();
switch (err) {
case PR_WOULD_BLOCK_ERROR:
return SR_BLOCK;
default:
Error("Read", -1, false);
*error = err; // libjingle semantics are that this is impl-specific
return SR_ERROR;
}
}
// Success
*read = rv;
return SR_SUCCESS;
}
StreamResult NSSStreamAdapter::Write(const void* data, size_t data_len,
size_t* written, int* error) {
// SSL_CONNECTED sanity check.
switch (state_) {
case SSL_NONE:
case SSL_WAIT:
case SSL_CONNECTING:
return SR_BLOCK;
case SSL_CONNECTED:
break;
case SSL_ERROR:
case SSL_CLOSED:
default:
if (error)
*error = ssl_error_code_;
return SR_ERROR;
}
PRInt32 rv = PR_Write(ssl_fd_, data, checked_cast<PRInt32>(data_len));
// Error
if (rv < 0) {
PRInt32 err = PR_GetError();
switch (err) {
case PR_WOULD_BLOCK_ERROR:
return SR_BLOCK;
default:
Error("Write", -1, false);
*error = err; // libjingle semantics are that this is impl-specific
return SR_ERROR;
}
}
// Success
*written = rv;
return SR_SUCCESS;
}
void NSSStreamAdapter::OnEvent(StreamInterface* stream, int events,
int err) {
int events_to_signal = 0;
int signal_error = 0;
ASSERT(stream == this->stream());
if ((events & SE_OPEN)) {
LOG(LS_INFO) << "NSSStreamAdapter::OnEvent SE_OPEN";
if (state_ != SSL_WAIT) {
ASSERT(state_ == SSL_NONE);
events_to_signal |= SE_OPEN;
} else {
state_ = SSL_CONNECTING;
if (int err = BeginSSL()) {
Error("BeginSSL", err, true);
return;
}
}
}
if ((events & (SE_READ|SE_WRITE))) {
LOG(LS_INFO) << "NSSStreamAdapter::OnEvent"
<< ((events & SE_READ) ? " SE_READ" : "")
<< ((events & SE_WRITE) ? " SE_WRITE" : "");
if (state_ == SSL_NONE) {
events_to_signal |= events & (SE_READ|SE_WRITE);
} else if (state_ == SSL_CONNECTING) {
if (int err = ContinueSSL()) {
Error("ContinueSSL", err, true);
return;
}
} else if (state_ == SSL_CONNECTED) {
if (events & SE_WRITE) {
LOG(LS_INFO) << " -- onStreamWriteable";
events_to_signal |= SE_WRITE;
}
if (events & SE_READ) {
LOG(LS_INFO) << " -- onStreamReadable";
events_to_signal |= SE_READ;
}
}
}
if ((events & SE_CLOSE)) {
LOG(LS_INFO) << "NSSStreamAdapter::OnEvent(SE_CLOSE, " << err << ")";
Cleanup();
events_to_signal |= SE_CLOSE;
// SE_CLOSE is the only event that uses the final parameter to OnEvent().
ASSERT(signal_error == 0);
signal_error = err;
}
if (events_to_signal)
StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
}
void NSSStreamAdapter::OnMessage(Message* msg) {
// Process our own messages and then pass others to the superclass
if (MSG_DTLS_TIMEOUT == msg->message_id) {
LOG(LS_INFO) << "DTLS timeout expired";
ContinueSSL();
} else {
StreamInterface::OnMessage(msg);
}
}
// Certificate verification callback. Called to check any certificate
SECStatus NSSStreamAdapter::AuthCertificateHook(void *arg,
PRFileDesc *fd,
PRBool checksig,
PRBool isServer) {
LOG(LS_INFO) << "NSSStreamAdapter::AuthCertificateHook";
// SSL_PeerCertificate returns a pointer that is owned by the caller, and
// the NSSCertificate constructor copies its argument, so |raw_peer_cert|
// must be destroyed in this function.
CERTCertificate* raw_peer_cert = SSL_PeerCertificate(fd);
NSSCertificate peer_cert(raw_peer_cert);
CERT_DestroyCertificate(raw_peer_cert);
NSSStreamAdapter *stream = reinterpret_cast<NSSStreamAdapter *>(arg);
stream->cert_ok_ = false;
// Read the peer's certificate chain.
CERTCertList* cert_list = SSL_PeerCertificateChain(fd);
ASSERT(cert_list != NULL);
// If the peer provided multiple certificates, check that they form a valid
// chain as defined by RFC 5246 Section 7.4.2: "Each following certificate
// MUST directly certify the one preceding it.". This check does NOT
// verify other requirements, such as whether the chain reaches a trusted
// root, self-signed certificates have valid signatures, certificates are not
// expired, etc.
// Even if the chain is valid, the leaf certificate must still match a
// provided certificate or digest.
if (!NSSCertificate::IsValidChain(cert_list)) {
CERT_DestroyCertList(cert_list);
PORT_SetError(SEC_ERROR_BAD_SIGNATURE);
return SECFailure;
}
if (stream->peer_certificate_.get()) {
LOG(LS_INFO) << "Checking against specified certificate";
// The peer certificate was specified
if (reinterpret_cast<NSSCertificate *>(stream->peer_certificate_.get())->
Equals(&peer_cert)) {
LOG(LS_INFO) << "Accepted peer certificate";
stream->cert_ok_ = true;
}
} else if (!stream->peer_certificate_digest_algorithm_.empty()) {
LOG(LS_INFO) << "Checking against specified digest";
// The peer certificate digest was specified
unsigned char digest[64]; // Maximum size
size_t digest_length;
if (!peer_cert.ComputeDigest(
stream->peer_certificate_digest_algorithm_,
digest, sizeof(digest), &digest_length)) {
LOG(LS_ERROR) << "Digest computation failed";
} else {
Buffer computed_digest(digest, digest_length);
if (computed_digest == stream->peer_certificate_digest_value_) {
LOG(LS_INFO) << "Accepted peer certificate";
stream->cert_ok_ = true;
}
}
} else {
// Other modes, but we haven't implemented yet
// TODO(ekr@rtfm.com): Implement real certificate validation
UNIMPLEMENTED;
}
if (!stream->cert_ok_ && stream->ignore_bad_cert()) {
LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
stream->cert_ok_ = true;
}
if (stream->cert_ok_)
stream->peer_certificate_.reset(new NSSCertificate(cert_list));
CERT_DestroyCertList(cert_list);
if (stream->cert_ok_)
return SECSuccess;
PORT_SetError(SEC_ERROR_UNTRUSTED_CERT);
return SECFailure;
}
SECStatus NSSStreamAdapter::GetClientAuthDataHook(void *arg, PRFileDesc *fd,
CERTDistNames *caNames,
CERTCertificate **pRetCert,
SECKEYPrivateKey **pRetKey) {
LOG(LS_INFO) << "Client cert requested";
NSSStreamAdapter *stream = reinterpret_cast<NSSStreamAdapter *>(arg);
if (!stream->identity_.get()) {
LOG(LS_ERROR) << "No identity available";
return SECFailure;
}
NSSIdentity *identity = static_cast<NSSIdentity *>(stream->identity_.get());
// Destroyed internally by NSS
*pRetCert = CERT_DupCertificate(identity->certificate().certificate());
*pRetKey = SECKEY_CopyPrivateKey(identity->keypair()->privkey());
return SECSuccess;
}
bool NSSStreamAdapter::GetSslCipher(std::string* cipher) {
ASSERT(state_ == SSL_CONNECTED);
if (state_ != SSL_CONNECTED)
return false;
SSLChannelInfo channel_info;
SECStatus rv = SSL_GetChannelInfo(ssl_fd_, &channel_info,
sizeof(channel_info));
if (rv == SECFailure)
return false;
SSLCipherSuiteInfo ciphersuite_info;
rv = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, &ciphersuite_info,
sizeof(ciphersuite_info));
if (rv == SECFailure)
return false;
*cipher = ciphersuite_info.cipherSuiteName;
return true;
}
// RFC 5705 Key Exporter
bool NSSStreamAdapter::ExportKeyingMaterial(const std::string& label,
const uint8* context,
size_t context_len,
bool use_context,
uint8* result,
size_t result_len) {
SECStatus rv = SSL_ExportKeyingMaterial(
ssl_fd_,
label.c_str(),
checked_cast<unsigned int>(label.size()),
use_context,
context,
checked_cast<unsigned int>(context_len),
result,
checked_cast<unsigned int>(result_len));
return rv == SECSuccess;
}
bool NSSStreamAdapter::SetDtlsSrtpCiphers(
const std::vector<std::string>& ciphers) {
#ifdef HAVE_DTLS_SRTP
std::vector<PRUint16> internal_ciphers;
if (state_ != SSL_NONE)
return false;
for (std::vector<std::string>::const_iterator cipher = ciphers.begin();
cipher != ciphers.end(); ++cipher) {
bool found = false;
for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; entry->cipher_id;
++entry) {
if (*cipher == entry->external_name) {
found = true;
internal_ciphers.push_back(entry->cipher_id);
break;
}
}
if (!found) {
LOG(LS_ERROR) << "Could not find cipher: " << *cipher;
return false;
}
}
if (internal_ciphers.empty())
return false;
srtp_ciphers_ = internal_ciphers;
return true;
#else
return false;
#endif
}
bool NSSStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) {
#ifdef HAVE_DTLS_SRTP
ASSERT(state_ == SSL_CONNECTED);
if (state_ != SSL_CONNECTED)
return false;
PRUint16 selected_cipher;
SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, &selected_cipher);
if (rv == SECFailure)
return false;
for (const SrtpCipherMapEntry *entry = kSrtpCipherMap;
entry->cipher_id; ++entry) {
if (selected_cipher == entry->cipher_id) {
*cipher = entry->external_name;
return true;
}
}
ASSERT(false); // This should never happen
#endif
return false;
}
GlobalLockPod NSSContext::lock;
NSSContext *NSSContext::global_nss_context;
// Static initialization and shutdown
NSSContext *NSSContext::Instance() {
lock.Lock();
if (!global_nss_context) {
scoped_ptr<NSSContext> new_ctx(new NSSContext(PK11_GetInternalSlot()));
if (new_ctx->slot_)
global_nss_context = new_ctx.release();
}
lock.Unlock();
return global_nss_context;
}
bool NSSContext::InitializeSSL(VerificationCallback callback) {
ASSERT(!callback);
static bool initialized = false;
if (!initialized) {
SECStatus rv;
rv = NSS_NoDB_Init(NULL);
if (rv != SECSuccess) {
LOG(LS_ERROR) << "Couldn't initialize NSS error=" <<
PORT_GetError();
return false;
}
NSS_SetDomesticPolicy();
initialized = true;
}
return true;
}
bool NSSContext::InitializeSSLThread() {
// Not needed
return true;
}
bool NSSContext::CleanupSSL() {
// Not needed
return true;
}
bool NSSStreamAdapter::HaveDtls() {
return true;
}
bool NSSStreamAdapter::HaveDtlsSrtp() {
#ifdef HAVE_DTLS_SRTP
return true;
#else
return false;
#endif
}
bool NSSStreamAdapter::HaveExporter() {
return true;
}
std::string NSSStreamAdapter::GetDefaultSslCipher(SSLProtocolVersion version,
KeyType key_type) {
if (key_type == KT_RSA) {
switch (version) {
case SSL_PROTOCOL_TLS_10:
case SSL_PROTOCOL_TLS_11:
return kDefaultSslCipher10;
case SSL_PROTOCOL_TLS_12:
default:
return kDefaultSslCipher12;
}
} else if (key_type == KT_ECDSA) {
switch (version) {
case SSL_PROTOCOL_TLS_10:
case SSL_PROTOCOL_TLS_11:
return kDefaultSslEcCipher10;
case SSL_PROTOCOL_TLS_12:
default:
return kDefaultSslEcCipher12;
}
} else {
return std::string();
}
}
} // namespace rtc
#endif // HAVE_NSS_SSL_H