Remote Call Framework 3.1
Transport Protocols

Transport protocols are used to transform the data passing over a transport. RCF uses transport protocols to provide authentication, encryption and compression for remote calls.

RCF currently supports NTLM, Kerberos, Negotiate and SSL transport protocols. NTLM, Kerberos and Negotiate are only supported on Windows platforms, while SSL is supported on all platforms.

In addition, RCF also supports Zlib-based compression of remote calls.

Transport protocols are configured on a client connection by calling RCF::ClientStub::setTransportProtocol():

RcfClient<I_PrintService> client(( RCF::TcpEndpoint(port) ));
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);

From within the server session of a client connection, you can call RCF::RcfSession::getTransportProtocol() to determine the transport prococol the client is using:

NTLM

To configure NTLM on a client connection:

RcfClient<I_PrintService> client(( RCF::TcpEndpoint(port) ));
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);
client.Print("Hello World");

On the server-side, you can determine the Windows user name of the client, and impersonate them:

std::string clientUsername = session.getClientUserName();
RCF::SspiImpersonator impersonator(session);
// We are now impersonating the client, until impersonator goes out of scope.
// ...

Kerberos

To configure Kerberos on a client connection:

client.getClientStub().setTransportProtocol(RCF::Tp_Kerberos);
client.getClientStub().setKerberosSpn("Domain\\ServerAccount");
client.Print("Hello World");

Notice that the client needs to call ClientStub::setKerberosSpn() to specify the username it expects the server to be running under. This is known as the SPN (Service Principal Name) of the server, and is used in the Kerberos protocol to implement mutual authentication. If the server is not running under this account, the connection will fail.

On the server-side, you can determine the Windows username of the client, and impersonate them:

std::string clientUsername = session.getClientUserName();
RCF::SspiImpersonator impersonator(session);
// We are now impersonating the client, until impersonator goes out of scope.
// ...

Negotiate

Negotiate is a negotiation protocol between the NTLM and Kerberos protocols. It will resolve to Kerberos if possible, and otherwise fall back to NTLM.

To configure Negotiate on a client connection:

client.getClientStub().setTransportProtocol(RCF::Tp_Negotiate);
client.getClientStub().setKerberosSpn("Domain\\ServerAccount");
client.Print("Hello World");

As with the Kerberos transport protocol, you need to supply a SPN for the server.

On the server-side, you can determine the Windows username of the client, and impersonate them:

std::string clientUsername = session.getClientUserName();
RCF::SspiImpersonator impersonator(session);
// We are now impersonating the client, until impersonator goes out of scope.
// ...

SSL

RCF offers two SSL transport protocol implementations. One is based on the cross-platform OpenSSL library, and the other is based on the Windows-only Schannel package.

OpenSSL support is only available if RCF_USE_OPENSSL has been defined.

Schannel support is only available in Windows builds, and does not require any defines.

If you define RCF_USE_OPENSSL in a Windows build, RCF will use OpenSSL rather than Schannel. Should you want to use Schannel, despite defining RCF_USE_OPENSSL, you can set the SSL implementation for individual servers and clients using the RCF::RcfServer::setSslImplementation() and RCF::ClientStub::setSslImplementation() functions, or set it for the entire RCF runtime using the RCF::Globals::setDefaultSslImplementation() function.

The SSL protocol uses certificates to authenticate servers to clients (and optionally clients to servers). Certificate and certificate validation functionality is handled differently by the two SSL transport protocol implementations, as described below.

Schannel

To configure a server to accept SSL connections, you need to provide a server certificate. RCF provides the RCF::PfxCertificate class, to load certificates from .pfx and .p12 files:

"C:\\serverCert.p12",
"password",
"CertificateName") );
server.setCertificate(serverCertPtr);

RCF also provides the RCF::StoreCertificate class, to load certificates from Windows certificate stores:

"CertificateName") );
server.setCertificate(serverCertPtr);

On the client, you need to provide a means of validating the certificate presented by the server. There are several ways of doing this. You can let the Schannel package apply its own internal validation logic, which will defer to the locally installed certificate authorities:

client.getClientStub().setTransportProtocol(RCF::Tp_Ssl);
client.getClientStub().setEnableSchannelCertificateValidation("CertificateName");
client.Print("Hello World");

You can also provide a particular certificate authority yourself, which will be used to validate the server certificate:

"C:\\clientCaCertificate.p12",
"password",
"CaCertificatename"));
client.getClientStub().setCaCertificate(caCertPtr);

You can also write your own custom certificate validation logic:

bool schannelValidateCert(RCF::Certificate * pCert)
{
RCF::Win32Certificate * pWin32Cert = static_cast<RCF::Win32Certificate *>(pCert);
if (pWin32Cert)
{
RCF::tstring certName = pWin32Cert->getCertificateName();
RCF::tstring issuerName = pWin32Cert->getIssuerName();
PCCERT_CONTEXT pContext = pWin32Cert->getWin32Context();
// Custom code to inspect and validate certificate.
// ...
}
// Return true if the certificate is considered valid. Otherwise, return false,
// or throw an exception.
return true;
}
client.getClientStub().setCertificateValidationCallback(&schannelValidateCert);

RCF clients can also be configured to present a certificate to the server:

"C:\\clientCert.p12",
"password",
"CertificateName") );
client.getClientStub().setCertificate(clientCertPtr);

Server-side certificate validation is done in the same way as client-side certificate validation, but using the RCF::RcfServer::setEnableSchannelCertificateValidation(), RCF::RcfServer::setCertificateValidationCallback(), and RCF::RcfServer::setCaCertificate() functions.

OpenSSL

When using the OpenSSL-based SSL transport protocol, certificates need to be loaded from .pem files, using the RCF::PemCertificate class. Here is an example of providing a server certificate:

"C:\\serverCert.pem",
"password") );
server.setCertificate(serverCertPtr);

The client can validate the server certificate in two ways. It can provide a certificate authority certificate:

"C:\\clientCaCertificate.pem",
"password"));
client.getClientStub().setCaCertificate(caCertPtr);

, or it can provide custom validation logic in a callback function:

bool opensslValidateCert(RCF::Certificate * pCert)
{
RCF::X509Certificate * pX509Cert = static_cast<RCF::X509Certificate *>(pCert);
if (pX509Cert)
{
std::string certName = pX509Cert->getCertificateName();
std::string issuerName = pX509Cert->getIssuerName();
X509 * pX509 = pX509Cert->getX509();
// Custom code to inspect and validate certificate.
// ...
}
// Return true if valid, false if not.
return true;
}
client.getClientStub().setCertificateValidationCallback(&opensslValidateCert);

The client can also provide a certificate of its own, to present to the server:

"C:\\clientCert.pem",
"password") );
client.getClientStub().setCertificate(clientCertPtr);

Server-side certificate validation is done in the same way as client-side certificate validation, but using the RCF::RcfServer::setCertificateValidationCallback(), and RCF::RcfServer::setCaCertificate() functions.

Compression

RCF supports transport-level compression of remote calls. To build RCF with support for compression, define RCF_USE_ZLIB (see Building RCF).

Compression is configured independently of other transport protocols, using RCF::ClientStub::setEnableCompression():

RcfClient<I_PrintService> client(( RCF::TcpEndpoint(port) ));
client.getClientStub().setEnableCompression(true);
client.Print("Hello World");

RCF applies the compression stage before the transport protocol stage. For example, if you configure compression and NTLM on the same connection:

RcfClient<I_PrintService> client(( RCF::TcpEndpoint(port) ));
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);
client.getClientStub().setEnableCompression(true);
client.Print("Hello World");

, the remote call data will be compressed first, before being encrypted and sent across the wire.