mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 996238 - ALPN support for WebRTC. r=ekr
This commit is contained in:
parent
c792a525fe
commit
7b12c6b26b
@ -82,5 +82,10 @@
|
||||
EXPECT_TRUE(res); \
|
||||
} while(0)
|
||||
|
||||
#define ASSERT_EQ_WAIT(expected, actual, timeout) \
|
||||
do { \
|
||||
WAIT(expected == actual, timeout); \
|
||||
ASSERT_EQ(expected, actual); \
|
||||
} while(0)
|
||||
|
||||
#endif
|
||||
|
@ -501,6 +501,20 @@ class TransportTestPeer : public sigslot::has_slots<> {
|
||||
ASSERT_TRUE(NS_SUCCEEDED(res));
|
||||
}
|
||||
|
||||
void SetAlpn(std::string str, bool withDefault, std::string extra = "") {
|
||||
std::set<std::string> alpn;
|
||||
alpn.insert(str); // the one we want to select
|
||||
if (!extra.empty()) {
|
||||
alpn.insert(extra);
|
||||
}
|
||||
nsresult res = dtls_->SetAlpn(alpn, withDefault ? str : "");
|
||||
ASSERT_EQ(NS_OK, res);
|
||||
}
|
||||
|
||||
const std::string& GetAlpn() const {
|
||||
return dtls_->GetNegotiatedAlpn();
|
||||
}
|
||||
|
||||
void SetDtlsPeer(TransportTestPeer *peer, int digests, unsigned int damage) {
|
||||
unsigned int mask = 1;
|
||||
|
||||
@ -854,14 +868,23 @@ class TransportTest : public ::testing::Test {
|
||||
p2_->SetDtlsAllowAll();
|
||||
}
|
||||
|
||||
void ConnectSocket() {
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(p1_, &TransportTestPeer::ConnectSocket, p2_),
|
||||
NS_DISPATCH_SYNC);
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(p2_, &TransportTestPeer::ConnectSocket, p1_),
|
||||
NS_DISPATCH_SYNC);
|
||||
void SetAlpn(std::string first, std::string second,
|
||||
bool withDefaults = true) {
|
||||
if (!first.empty()) {
|
||||
p1_->SetAlpn(first, withDefaults, "bogus");
|
||||
}
|
||||
if (!second.empty()) {
|
||||
p2_->SetAlpn(second, withDefaults);
|
||||
}
|
||||
}
|
||||
|
||||
void CheckAlpn(std::string first, std::string second) {
|
||||
ASSERT_EQ(first, p1_->GetAlpn());
|
||||
ASSERT_EQ(second, p2_->GetAlpn());
|
||||
}
|
||||
|
||||
void ConnectSocket() {
|
||||
ConnectSocketInternal();
|
||||
ASSERT_TRUE_WAIT(p1_->connected(), 10000);
|
||||
ASSERT_TRUE_WAIT(p2_->connected(), 10000);
|
||||
|
||||
@ -870,16 +893,18 @@ class TransportTest : public ::testing::Test {
|
||||
}
|
||||
|
||||
void ConnectSocketExpectFail() {
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(p1_, &TransportTestPeer::ConnectSocket, p2_),
|
||||
NS_DISPATCH_SYNC);
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(p2_, &TransportTestPeer::ConnectSocket, p1_),
|
||||
NS_DISPATCH_SYNC);
|
||||
ConnectSocketInternal();
|
||||
ASSERT_TRUE_WAIT(p1_->failed(), 10000);
|
||||
ASSERT_TRUE_WAIT(p2_->failed(), 10000);
|
||||
}
|
||||
|
||||
void ConnectSocketExpectState(TransportLayer::State s1,
|
||||
TransportLayer::State s2) {
|
||||
ConnectSocketInternal();
|
||||
ASSERT_EQ_WAIT(s1, p1_->state(), 10000);
|
||||
ASSERT_EQ_WAIT(s2, p2_->state(), 10000);
|
||||
}
|
||||
|
||||
void InitIce() {
|
||||
p1_->InitIce();
|
||||
p2_->InitIce();
|
||||
@ -908,6 +933,15 @@ class TransportTest : public ::testing::Test {
|
||||
}
|
||||
|
||||
protected:
|
||||
void ConnectSocketInternal() {
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(p1_, &TransportTestPeer::ConnectSocket, p2_),
|
||||
NS_DISPATCH_SYNC);
|
||||
test_utils->sts_target()->Dispatch(
|
||||
WrapRunnable(p2_, &TransportTestPeer::ConnectSocket, p1_),
|
||||
NS_DISPATCH_SYNC);
|
||||
}
|
||||
|
||||
PRFileDesc *fds_[2];
|
||||
TransportTestPeer *p1_;
|
||||
TransportTestPeer *p2_;
|
||||
@ -955,6 +989,56 @@ TEST_F(TransportTest, TestConnectAllowAll) {
|
||||
ConnectSocket();
|
||||
}
|
||||
|
||||
TEST_F(TransportTest, TestConnectAlpn) {
|
||||
SetDtlsPeer();
|
||||
SetAlpn("a", "a");
|
||||
ConnectSocket();
|
||||
CheckAlpn("a", "a");
|
||||
}
|
||||
|
||||
TEST_F(TransportTest, TestConnectAlpnMismatch) {
|
||||
SetDtlsPeer();
|
||||
SetAlpn("something", "different");
|
||||
ConnectSocketExpectFail();
|
||||
}
|
||||
|
||||
TEST_F(TransportTest, TestConnectAlpnServerDefault) {
|
||||
SetDtlsPeer();
|
||||
SetAlpn("def", "");
|
||||
// server allows default, client doesn't support
|
||||
ConnectSocket();
|
||||
CheckAlpn("def", "");
|
||||
}
|
||||
|
||||
TEST_F(TransportTest, TestConnectAlpnClientDefault) {
|
||||
SetDtlsPeer();
|
||||
SetAlpn("", "clientdef");
|
||||
// client allows default, but server will ignore the extension
|
||||
ConnectSocket();
|
||||
CheckAlpn("", "clientdef");
|
||||
}
|
||||
|
||||
TEST_F(TransportTest, TestConnectClientNoAlpn) {
|
||||
SetDtlsPeer();
|
||||
// Here the server has ALPN, but no default is allowed.
|
||||
// Reminder: p1 == server, p2 == client
|
||||
SetAlpn("server-nodefault", "", false);
|
||||
// The server doesn't see the extension, so negotiates without it.
|
||||
// But then the server is forced to close when it discovers that ALPN wasn't
|
||||
// negotiated; the client sees a close.
|
||||
ConnectSocketExpectState(TransportLayer::TS_ERROR,
|
||||
TransportLayer::TS_CLOSED);
|
||||
}
|
||||
|
||||
TEST_F(TransportTest, TestConnectServerNoAlpn) {
|
||||
SetDtlsPeer();
|
||||
SetAlpn("", "client-nodefault", false);
|
||||
// The client aborts; the server doesn't realize this is a problem and just
|
||||
// sees the close.
|
||||
ConnectSocketExpectState(TransportLayer::TS_CLOSED,
|
||||
TransportLayer::TS_ERROR);
|
||||
}
|
||||
|
||||
TEST_F(TransportTest, TestConnectNoDigest) {
|
||||
SetDtlsPeer(0, 0);
|
||||
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
#include "mozilla/UniquePtr.h"
|
||||
|
||||
@ -44,6 +45,7 @@ static PRDescIdentity transport_layer_identity = PR_INVALID_IO_LAYER;
|
||||
MOZ_ASSERT(false); \
|
||||
PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0)
|
||||
|
||||
#define MAX_ALPN_LENGTH 255
|
||||
|
||||
// We need to adapt the NSPR/libssl model to the TransportFlow model.
|
||||
// The former wants pull semantics and TransportFlow wants push.
|
||||
@ -388,6 +390,24 @@ void TransportLayerDtls::WasInserted() {
|
||||
}
|
||||
|
||||
|
||||
// Set the permitted and default ALPN identifiers.
|
||||
// The default is here to allow for peers that don't want to negotiate ALPN
|
||||
// in that case, the default string will be reported from GetNegotiatedAlpn().
|
||||
// Setting the default to the empty string causes the transport layer to fail
|
||||
// if ALPN is not negotiated.
|
||||
// Note: we only support Unicode strings here, which are encoded into UTF-8,
|
||||
// even though ALPN ostensibly allows arbitrary octet sequences.
|
||||
nsresult TransportLayerDtls::SetAlpn(
|
||||
const std::set<std::string>& alpn_allowed,
|
||||
const std::string& alpn_default) {
|
||||
|
||||
alpn_allowed_ = alpn_allowed;
|
||||
alpn_default_ = alpn_default;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsresult TransportLayerDtls::SetVerificationAllowAll() {
|
||||
// Defensive programming
|
||||
if (verification_mode_ != VERIFY_UNSET)
|
||||
@ -564,6 +584,10 @@ bool TransportLayerDtls::Setup() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!SetupAlpn(ssl_fd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now start the handshake
|
||||
rv = SSL_ResetHandshake(ssl_fd, role_ == SERVER ? PR_TRUE : PR_FALSE);
|
||||
if (rv != SECSuccess) {
|
||||
@ -584,6 +608,43 @@ bool TransportLayerDtls::Setup() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TransportLayerDtls::SetupAlpn(PRFileDesc* ssl_fd) const {
|
||||
if (alpn_allowed_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SECStatus rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_NPN, PR_FALSE);
|
||||
if (rv != SECSuccess) {
|
||||
MOZ_MTLOG(ML_ERROR, "Couldn't disable NPN");
|
||||
return false;
|
||||
}
|
||||
|
||||
rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_ALPN, PR_TRUE);
|
||||
if (rv != SECSuccess) {
|
||||
MOZ_MTLOG(ML_ERROR, "Couldn't enable ALPN");
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned char buf[MAX_ALPN_LENGTH];
|
||||
size_t offset = 0;
|
||||
for (auto tag = alpn_allowed_.begin();
|
||||
tag != alpn_allowed_.end(); ++tag) {
|
||||
if ((offset + 1 + tag->length()) >= sizeof(buf)) {
|
||||
MOZ_MTLOG(ML_ERROR, "ALPN too long");
|
||||
return false;
|
||||
}
|
||||
buf[offset++] = tag->length();
|
||||
memcpy(buf + offset, tag->c_str(), tag->length());
|
||||
offset += tag->length();
|
||||
}
|
||||
rv = SSL_SetNextProtoNego(ssl_fd, buf, offset);
|
||||
if (rv != SECSuccess) {
|
||||
MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN string");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ciphers we need to enable. These are on by default in standard firefox
|
||||
// builds, but can be disabled with prefs and they aren't on in our unit tests
|
||||
// since that uses NSS default configuration.
|
||||
@ -777,6 +838,15 @@ void TransportLayerDtls::Handshake() {
|
||||
TL_SET_STATE(TS_ERROR);
|
||||
return;
|
||||
}
|
||||
if (!CheckAlpn()) {
|
||||
// Despite connecting, the connection doesn't have a valid ALPN label.
|
||||
// Forcibly close the connection so that the peer isn't left hanging
|
||||
// (assuming the close_notify isn't dropped).
|
||||
ssl_fd_ = nullptr;
|
||||
TL_SET_STATE(TS_ERROR);
|
||||
return;
|
||||
}
|
||||
|
||||
TL_SET_STATE(TS_OPEN);
|
||||
} else {
|
||||
int32_t err = PR_GetError();
|
||||
@ -808,6 +878,61 @@ void TransportLayerDtls::Handshake() {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if ALPN was negotiated correctly and returns false if it wasn't.
|
||||
// After this returns successfully, alpn_ will be set to the negotiated
|
||||
// protocol.
|
||||
bool TransportLayerDtls::CheckAlpn() {
|
||||
if (alpn_allowed_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SSLNextProtoState alpnState;
|
||||
char chosenAlpn[MAX_ALPN_LENGTH];
|
||||
unsigned int chosenAlpnLen;
|
||||
SECStatus rv = SSL_GetNextProto(ssl_fd_, &alpnState,
|
||||
reinterpret_cast<unsigned char*>(chosenAlpn),
|
||||
&chosenAlpnLen, sizeof(chosenAlpn));
|
||||
if (rv != SECSuccess) {
|
||||
MOZ_MTLOG(ML_ERROR, LAYER_INFO << "ALPN error");
|
||||
return false;
|
||||
}
|
||||
switch (alpnState) {
|
||||
case SSL_NEXT_PROTO_SELECTED:
|
||||
case SSL_NEXT_PROTO_NEGOTIATED:
|
||||
break; // OK
|
||||
|
||||
case SSL_NEXT_PROTO_NO_SUPPORT:
|
||||
MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "ALPN not negotiated, "
|
||||
<< (alpn_default_.empty() ? "failing" : "selecting default"));
|
||||
alpn_ = alpn_default_;
|
||||
return !alpn_.empty();
|
||||
|
||||
case SSL_NEXT_PROTO_NO_OVERLAP:
|
||||
// This only happens if there is a custom NPN/ALPN callback installed and
|
||||
// that callback doesn't properly handle ALPN.
|
||||
MOZ_MTLOG(ML_ERROR, LAYER_INFO << "error in ALPN selection callback");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Warning: NSS won't null terminate the ALPN string for us.
|
||||
std::string chosen(chosenAlpn, chosenAlpnLen);
|
||||
MOZ_MTLOG(ML_NOTICE, LAYER_INFO << "Selected ALPN string: " << chosen);
|
||||
if (alpn_allowed_.find(chosen) == alpn_allowed_.end()) {
|
||||
// Maybe our peer chose a protocol we didn't offer (when we are client), or
|
||||
// something is seriously wrong.
|
||||
std::ostringstream ss;
|
||||
for (auto i = alpn_allowed_.begin(); i != alpn_allowed_.end(); ++i) {
|
||||
ss << (i == alpn_allowed_.begin() ? " '" : ", '") << *i << "'";
|
||||
}
|
||||
MOZ_MTLOG(ML_ERROR, LAYER_INFO << "Bad ALPN string: '" << chosen
|
||||
<< "'; permitted:" << ss.str());
|
||||
return false;
|
||||
}
|
||||
alpn_ = chosen;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void TransportLayerDtls::PacketReceived(TransportLayer* layer,
|
||||
const unsigned char *data,
|
||||
size_t len) {
|
||||
@ -928,6 +1053,9 @@ nsresult TransportLayerDtls::SetSrtpCiphers(std::vector<uint16_t> ciphers) {
|
||||
|
||||
nsresult TransportLayerDtls::GetSrtpCipher(uint16_t *cipher) const {
|
||||
CheckThread();
|
||||
if (state_ != TS_OPEN) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, cipher);
|
||||
if (rv != SECSuccess) {
|
||||
MOZ_MTLOG(ML_DEBUG, "No SRTP cipher negotiated");
|
||||
@ -943,6 +1071,10 @@ nsresult TransportLayerDtls::ExportKeyingMaterial(const std::string& label,
|
||||
unsigned char *out,
|
||||
unsigned int outlen) {
|
||||
CheckThread();
|
||||
if (state_ != TS_OPEN) {
|
||||
MOZ_ASSERT(false, "Transport must be open for ExportKeyingMaterial");
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
SECStatus rv = SSL_ExportKeyingMaterial(ssl_fd_,
|
||||
label.c_str(),
|
||||
label.size(),
|
||||
|
@ -10,6 +10,7 @@
|
||||
#define transportlayerdtls_h__
|
||||
|
||||
#include <queue>
|
||||
#include <set>
|
||||
|
||||
#include "sigslot.h"
|
||||
|
||||
@ -67,6 +68,10 @@ class TransportLayerDtls final : public TransportLayer {
|
||||
void SetIdentity(const RefPtr<DtlsIdentity>& identity) {
|
||||
identity_ = identity;
|
||||
}
|
||||
nsresult SetAlpn(const std::set<std::string>& allowedAlpn,
|
||||
const std::string& alpnDefault);
|
||||
const std::string& GetNegotiatedAlpn() const { return alpn_; }
|
||||
|
||||
nsresult SetVerificationAllowAll();
|
||||
nsresult SetVerificationDigest(const std::string digest_algorithm,
|
||||
const unsigned char *digest_value,
|
||||
@ -131,8 +136,11 @@ class TransportLayerDtls final : public TransportLayer {
|
||||
|
||||
bool Setup();
|
||||
bool SetupCipherSuites(PRFileDesc* ssl_fd) const;
|
||||
bool SetupAlpn(PRFileDesc* ssl_fd) const;
|
||||
void Handshake();
|
||||
|
||||
bool CheckAlpn();
|
||||
|
||||
static SECStatus GetClientAuthDataHook(void *arg, PRFileDesc *fd,
|
||||
CERTDistNames *caNames,
|
||||
CERTCertificate **pRetCert,
|
||||
@ -151,6 +159,13 @@ class TransportLayerDtls final : public TransportLayer {
|
||||
CERTCertificate *cert);
|
||||
|
||||
RefPtr<DtlsIdentity> identity_;
|
||||
// What ALPN identifiers are permitted.
|
||||
std::set<std::string> alpn_allowed_;
|
||||
// What ALPN identifier is used if ALPN is not supported.
|
||||
// The empty string indicates that ALPN is required.
|
||||
std::string alpn_default_;
|
||||
// What ALPN string was negotiated.
|
||||
std::string alpn_;
|
||||
std::vector<uint16_t> srtp_ciphers_;
|
||||
|
||||
Role role_;
|
||||
|
@ -199,6 +199,22 @@ MediaPipelineFactory::CreateOrGetTransportFlow(
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Always permits negotiation of the confidential mode.
|
||||
// Only allow non-confidential (which is an allowed default),
|
||||
// if we aren't confidential.
|
||||
std::set<std::string> alpn;
|
||||
std::string alpnDefault = "";
|
||||
alpn.insert("c-webrtc");
|
||||
if (!mPC->PrivacyRequested()) {
|
||||
alpnDefault = "webrtc";
|
||||
alpn.insert(alpnDefault);
|
||||
}
|
||||
rv = dtls->SetAlpn(alpn, alpnDefault);
|
||||
if (NS_FAILED(rv)) {
|
||||
MOZ_MTLOG(ML_ERROR, "Couldn't set ALPN");
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsAutoPtr<PtrVector<TransportLayer> > layers(new PtrVector<TransportLayer>);
|
||||
layers->values.push_back(ice.release());
|
||||
layers->values.push_back(dtls.release());
|
||||
|
@ -956,14 +956,14 @@ PeerConnectionMedia::EndOfLocalCandidates_m(const std::string& aDefaultAddr,
|
||||
}
|
||||
|
||||
void
|
||||
PeerConnectionMedia::DtlsConnected_s(TransportLayer *dtlsLayer,
|
||||
PeerConnectionMedia::DtlsConnected_s(TransportLayer *layer,
|
||||
TransportLayer::State state)
|
||||
{
|
||||
MOZ_ASSERT(layer->id() == "dtls");
|
||||
TransportLayerDtls* dtlsLayer = static_cast<TransportLayerDtls*>(layer);
|
||||
dtlsLayer->SignalStateChange.disconnect(this);
|
||||
|
||||
bool privacyRequested = false;
|
||||
// TODO (Bug 952678) set privacy mode, ask the DTLS layer about that
|
||||
// This has to be a dispatch to a static method, we could be going away
|
||||
bool privacyRequested = (dtlsLayer->GetNegotiatedAlpn() == "c-webrtc");
|
||||
GetMainThread()->Dispatch(
|
||||
WrapRunnableNM(&PeerConnectionMedia::DtlsConnected_m,
|
||||
mParentHandle, privacyRequested),
|
||||
|
Loading…
Reference in New Issue
Block a user