Programming IceSSL in C++
This page describes the C++ API for the IceSSL plug-in.
On this page:
The IceSSL Plugin Interface in C++
The IceSSL C++ plug-in has several implementations listed in the table below. Its API consists of implementation-independent classes directly in the IceSSL
namespace plus implementation-dependent classes in nested namespaces such as IceSSL::OpenSSL.
Underlying SSL/TLS Implementation | IceSSL Plug-in Class | IceSSL Certificate Class | Platform Availability |
---|---|---|---|
OpenSSL |
|
| Linux (default), Windows |
SecureTransport | IceSSL::Plugin |
| macOS (default) |
SChannel |
|
| Windows (default) |
UWP | IceSSL::Plugin |
| UWP (default) |
Each platform has a default implementation, indicated with (default) in the table above. Static functions on the IceSSL::Certificate
class are supplied by the default platform-dependent implementation.
Applications can interact directly with the IceSSL plug-in using the C++ class IceSSL::Plugin
. You can retrieve this Plugin
object as shown below:
auto communicator = ... auto pluginMgr = communicator->getPluginManager(); auto plugin = pluginMgr ->getPlugin("IceSSL"); auto sslPlugin = std::dynamic_pointer_cast<IceSSL::Plugin>(plugin);
Ice::CommunicatorPtr communicator = ... Ice::PluginManagerPtr pluginMgr = communicator->getPluginManager(); Ice::PluginPtr plugin = pluginMgr->getPlugin("IceSSL"); IceSSL::PluginPtr sslPlugin = IceSSL::PluginPtr::dynamicCast(plugin);
The Plugin
class provides the following functions:
namespace IceSSL { class Plugin : public Ice::Plugin { public: virtual void setCertificateVerifier(std::function<bool(const std::shared_ptr<IceSSL::ConnectionInfo>&)>) = 0; virtual void setPasswordPrompt(std::function<std::string()>) = 0; virtual std::shared_ptr<Certificate> load(const std::string&) const = 0; virtual std::shared_ptr<Certificate> decode(const std::string&) const = 0; }; }
// Only with OpenSSL implementation // namespace Ice { namespace OpenSSL { class Plugin : public IceSSL::Plugin { public: virtual Ice::Long getOpenSSLVersion() const = 0; virtual void setContext(SSL_CTX*) = 0; virtual SSL_CTX* getContext() = 0; }; } }
namespace IceSSL { class Plugin : public Ice::Plugin { public: virtual void setCertificateVerifier(const CertificateVerifierPtr&) = 0; virtual void setPasswordPrompt(const PasswordPromptPtr&) = 0; virtual CertificatePtr load(const std::string&) const = 0; virtual CertificatePtr decode(const std::string&) const = 0; }; }
// Only with OpenSSL implementation // namespace Ice { namespace OpenSSL { class Plugin : public IceSSL::Plugin { public: virtual Ice::Long getOpenSSLVersion() const = 0; virtual void setContext(SSL_CTX*) = 0; virtual SSL_CTX* getContext() = 0; }; } }
The setCertificateVerifier
function installs a custom certificate verifier object that the plug-in invokes for each new connection. The setPasswordPrompt
function provides an alternate way to supply IceSSL with passwords. We discuss certificate verifiers below and revisit the other functions in our discussion of advanced IceSSL programming.
The load
function creates a Certificate
object from a file in PEM encoded format. Likewise, the decode
function creates a certificate from a string in the PEM encoding format. The most-derived type of the certificate object depends on the plug-in implementation you're using:
Plug-in | Certificate |
---|---|
OpenSSL | IceSSL::OpenSSL::Certificate |
SChannel | IceSSL::SChannel::Certificiate |
SecureTransport | IceSSL::SecureTransport::Certificate |
UWP | IceSSL::UWP::Certificate |
IceSSL::OpenSSL::Plugin
extends the base IceSSL::Plugin
class and provides the getOpenSSLVersion
, setContext
and getContext
functions that are specific to the OpenSSL implementation and are rarely used in practice. The IceSSL.InitOpenSSL
property is typically used in conjunction with setContext
.
Obtaining SSL Connection Information in C++
You can obtain information about any SSL connection using the getInfo
operation on a Connection
object. IceSSL defines the following type in Slice:
// from Ice/Connection.ice module Ice { local class ConnectionInfo { ConnectionInfo underlying; bool incoming; string adapterName; string connectionId; } } // from IceSSL/ConnectionInfo.ice module IceSSL { local class ConnectionInfo extends Ice::ConnectionInfo { string cipher; ["cpp:type:std::vector<CertificatePtr>", "java:type:java.security.cert.Certificate[]", "cs:type:System.Security.Cryptography.X509Certificates.X509Certificate2[]"] Ice::StringSeq certs; bool verified; } }
The certs
member contains the peer's certificate chain as a sequence of IceSSL::CertificatePtr
objects. The cpp:type
metadata changes the default mapping of the certs
member to that native type. The cipher
member is a description of the ciphersuite that SSL negotiated for this connection. The verified
member is always true if IceSSL.VerifyPeer > 0
. Otherwise, it is false if one of these checks fail:
- the underlying SSL engine certificate verification fails,
- if
IceSSL.CheckCertName > 0
, the host name verification check failed.
The inherited underlying
data member contains the connection information of the underlying transport (if SSL is based on TCP, this member will contain an instance of Ice::TCPEndpointInfo
which you can use to retrieve the remote and local addresses). The incoming
member indicates whether the connection is inbound (a server connection) or outbound (a client connection). The connectionId
data member matches the connection identifier set on the proxy. Finally, if incoming
is true, the adapterName
member supplies the name of the object adapter that hosts the endpoint.
Installing a Certificate Verifier in C++
A new connection undergoes a series of verification steps before an application is allowed to use it. The low-level SSL engine executes certificate validation procedures and, assuming the certificate chain is successfully validated, IceSSL performs additional verification as directed by its configuration properties. If a certificate verifier is installed, IceSSL invokes it to provide the application with an opportunity to decide whether to accept or reject the connection. The value of the IceSSL.VerifyPeer
property also plays an important role here. We've summarized the process in the following flow chart:
With the C++11 mapping, the certificate verifier is a std::function
provided to the IceSSL plug-in's setCertificateVerifier
member function:
virtual void setCertificateVerifier(std::function<bool(const std::shared_ptr<IceSSL::ConnectionInfo>&)>) = 0;
IceSSL rejects the connection if the certificate verifier function returns false
, and allows it to proceed if the function returns true
. The verify
function receives a IceSSL::ConnectionInfo
object that describes the connection's attributes.
The following code demonstrates a simple implementation of a certificate verifier:
auto verifier = [](const shared_ptr<IceSSL::ConnectionInfo>& info) { if(!info->certs.empty()) { auto cert = IceSSL::Certificate::decode(info->certs[0]); auto dn = cert->getIssuerDN(); transform(dn.begin(), dn.end(), dn.begin(), ::tolower); if(dn.find("zeroc") != string::npos) { return true; } } return false; };
In this example, the verifier rejects the connection unless the string zeroc
is present in the issuer's distinguished name of the peer's certificate. In a more realistic implementation, the application is likely to perform detailed inspection of the certificate chain.
Installing the verifier is a simple matter of calling setCertificateVerifier
on the plug-in interface:
shared_ptr<IceSSL::Plugin> sslPlugin = ... sslPlugin->setCertificateVerifier(verifier);
With the C++98 mapping, you must provide an object that implements the CertificateVerifier
abstract base class:
namespace IceSSL { class CertificateVerifier : public IceUtil::Shared { public: virtual bool verify(const IceSSL::ConnectionInfoPtr&) = 0; }; typedef IceUtil::Handle<CertificateVerifier> CertificateVerifierPtr; }
IceSSL rejects the connection if the certificate verifier function returns false
, and allows it to proceed if the function returns true
. The verify
function receives a IceSSL::ConnectionInfo
object that describes the connection's attributes.
The following class is a simple implementation of a certificate verifier:
class Verifier : public IceSSL::CertificateVerifier { public: bool verify(const IceSSL::ConnectionInfoPtr& info) { if(!info->certs.empty()) { IceSSL::CertificatePtr cert = IceSSL::Certificate::decode(info->certs[0]); string dn = cert->getIssuerDN(); transform(dn.begin(), dn.end(), dn.begin(), ::tolower); if(dn.find("zeroc") != string::npos) { return true; } } return false; } };
In this example, the verifier rejects the connection unless the string zeroc
is present in the issuer's distinguished name of the peer's certificate. In a more realistic implementation, the application is likely to perform detailed inspection of the certificate chain.
Installing the verifier is a simple matter of calling setCertificateVerifier
on the plug-in interface:
IceSSL::PluginPtr sslPlugin = ... sslPlugin->setCertificateVerifier(verifier);
You should install the verifier before any SSL connections are established.
You can also install a certificate verifier using a custom plug-in to avoid making changes to the code of an existing application.
The Ice run time calls the certificate verifier during the connection-establishment process, therefore delays in the certificate verifier implementation have a direct impact on the performance of the application. Do not make remote invocations from your implementation of the certificate verifier.
Using Certificates in C++
The IceSSL::ConnectionInfo
class contains a vector of IceSSL::Certificate
objects representing the peer's certificate chain. IceSSL::Certificate
is a reference-counted convenience class that hides the complexity of the platform's native SSL API inspired by the Java class X509Certificate
. The IceSSL::Certificate
class defines the common API for all IceSSL implementations, and APIs that use platform-dependent types are provided by extensions of this class. When a common method is not supported by a SSL implementation it raises Ice::FeatureNotSupportedException
.
namespace IceSSL { class Certificate : public std::enable_shared_from_this<Certificate> { public: virtual bool verify(const std::shared_ptr<Certificate>&) const = 0; virtual std::string encode() const = 0; virtual bool checkValidity() const = 0; virtual bool checkValidity(const std::chrono::system_clock::time_point&) const = 0; virtual std::chrono::system_clock::time_point getNotAfter() const = 0; virtual std::chrono::system_clock::time_point getNotBefore() const = 0; virtual std::string getSerialNumber() const = 0; virtual DistinguishedName getIssuerDN() const = 0; virtual std::vector<std::pair<int, std::string> > getIssuerAlternativeNames() const = 0; virtual DistinguishedName getSubjectDN() const = 0; virtual std::vector<std::pair<int, std::string> > getSubjectAlternativeNames() const = 0; virtual int getVersion() const = 0; virtual std::string toString() const = 0; virtual std::vector<std::shared_ptr<X509Extension>> getX509Extensions() const = 0; virtual std::shared_ptr<X509Extension> getX509Extension(const std::string&) const = 0; virtual std::vector<Ice::Byte> getAuthorityKeyIdentifier() const = 0; virtual std::vector<Ice::Byte> getSubjectKeyIdentifier() const = 0; static std::shared_ptr<Certificate> load(const std::string&); static std::shared_ptr<Certificate> decode(const std::string&); }; }
namespace IceSSL { class Certificate : public virtual IceUtil::Shared { public: virtual bool verify(const CertificatePtr&) const = 0; virtual std::string encode() const = 0; virtual bool checkValidity() const = 0; virtual bool checkValidity(const IceUtil::Time&) const = 0; virtual IceUtil::Time getNotAfter() const = 0; virtual IceUtil::Time getNotBefore() const = 0; virtual std::string getSerialNumber() const = 0; virtual DistinguishedName getIssuerDN() const = 0; virtual std::vector<std::pair<int, std::string> > getIssuerAlternativeNames() const = 0; virtual DistinguishedName getSubjectDN() const = 0; virtual std::vector<std::pair<int, std::string> > getSubjectAlternativeNames() const = 0; virtual int getVersion() const = 0; virtual std::string toString() const = 0; virtual std::vector<X509ExtensionPtr> getX509Extensions() const = 0; virtual X509ExtensionPtr getX509Extension(const std::string&) const = 0; virtual std::vector<Ice::Byte> getAuthorityKeyIdentifier() const = 0; virtual std::vector<Ice::Byte> getSubjectKeyIdentifier() const = 0; static CertificatePtr load(const std::string&); static CertificatePtr decode(const std::string&); }; }
The more commonly-used functions are described below; refer to the documentation in IceSSL/Plugin.h
for information on the functions that are not covered.
The static function load
creates a certificate from the contents of a certificate file PEM-encoded. If an error occurs, the function raises IceSSL::CertificateReadException
; the reason member provides a description of the problem.
UWP
The UWP implementation of load uses GetFileFromApplicationUriAsync and the file path must use an appropriate URI format.
Use decode
to obtain a certificate from a PEM-encoded string representing a certificate. The caller must be prepared to catch IceSSL::CertificateEncodingException
if decode
fails; the reason member provides a description of the problem.
Both load
and decode
return a certificate using the default IceSSL::Certificate
implementation:
IceSSL::SChannel::Certificate
on WindowsIceSSL::SecureTransport::Certificate
on macOSIceSSL::UWP::Certificate
for UWPIceSSL::OpenSSL::Certificate
for OpenSSL
All of these classes derive from IceSSL::Certificate
to provide platform-dependent methods.
The encode
function creates a PEM-encoded string that represents the certificate. The return value can later be passed to decode
to recreate the certificate.
The checkValidity
functions determine whether the certificate is valid. The overloading with no arguments returns true if the certificate is valid at the current time; the other overloading accepts a system_clock::time_point
with the C++11 mapping (IceUtil::Time
with the C++98 mapping) and returns true if the certificate is valid at the given time.
The getNotAfter
and getNotBefore
functions return the times that define the certificate's valid period, as a system_clock::time_point
with the C++11 mapping and as a IceUtil::Time
with the C++98 mapping.
The functions getIssuerDN
and getSubjectDN
supply the distinguished names of the certificate's issuer (i.e., the CA that signed the certificate) and subject (i.e., the person or entity to which the certificate was issued). The functions return instances of the class IceSSL::DistinguishedName
, another convenience class that is described below.
The getX509Extensions
and getX509Extension
functions can be used to retrieve a list of the X509 extensions included in the certificate or a specific X509 extension by providing its OID
, respectively. These methods are only supported by the OpenSSL and SChannel implementations. We discuss extensions further below.
The getAuthorityKeyIdentifier
and getSubjectKeyIdentifier
functions return the authority key identifier and subject key identifier respectively as a sequence of bytes. These methods are not supported with UWP or with SecureTransport on iOS.
Finally, the toString
function returns a human-readable string describing the certificate.
Interacting with the Native Certificates
It is possible to interact with the SSL implementation's native certificates. Tthese APIs are platform-dependent and are defined in extensions of the IceSSL::Certificate
class. Refer to the header file for each implementation to get more details. The following table summarizes the header files specific to each implementation:
SSL Implementation | Header File |
---|---|
OpenSSL | IceSSL/OpenSSL.h |
SecureTransport | IceSSL/SecureTransport.h |
SChannel | IceSSL/SChannel.h |
UWP | IceSSL/UWP.h |
Usually you don't need to include these header files in your application; you'll just include IceSSL/IceSSL.h
which includes the appropriate header files for your platform's default implementation. If you are using the OpenSSL implementation on Windows and need to access OpenSSL-specific APIs, you must include IceSSL/OpenSSL.h
.
Using Distinguished Names in C++
X.509 certificates use a distinguished name to identify a person or entity. The name is an ordered sequence of relative distinguished names that supply values for fields such as common name, organization, state, and country. Distinguished names are commonly displayed in stringified form according to the rules specified by RFC 2253, as shown in the following example:
C=US, ST=Florida, L=Palm Beach Gardens, O="ZeroC, Inc.", OU=Servers, CN=Quote Server
DistinguishedName
is a convenience class provided by IceSSL to simplify the tasks of parsing, formatting and comparing distinguished names.
namespace IceSSL { class DistinguishedName { public: explicit DistinguishedName(const std::string&); explicit DistinguishedName(const std::list<std::pair<std::string, std::string>>&); bool match(const DistinguishedName&) const; bool match(const std::string&) const; operator std::string() const; friend bool operator==(const DistinguishedName&, const DistinguishedName&); friend bool operator<(const DistinguishedName&, const DistinguishedName&); }; inline bool operator!=(const DistinguishedName&, const DistinguishedName&) { ... } // etc., provides all comparison operators. }
The first overloaded constructor accepts a string argument representing a distinguished name encoded using the rules set forth in RFC 2253. The new DistinguishedName
instance preserves the order of the relative distinguished names in the string. The caller must be prepared to catch IceSSL::ParseException
if an error occurs during parsing.
The second overloaded constructor requires a list of type-value pairs representing the relative distinguished names. The new DistinguishedName
instance preserves the order of the relative distinguished names in the list.
The match
functions perform a partial comparison that does not consider the order of relative distinguished names. Given two instances N1 and N2 of DistinguishedName
, N1.match(N2)
returns true if all of the relative distinguished names in N2
are present in N1
.
Finally, the string conversion operator encodes the distinguished name in the format described by RFC 2253.
The comparison operators perform an exact match of distinguished names in which the order of the relative distinguished names is important. For two distinguished names to be equal, they must have the same relative distinguished names in the same order.
Using Certificate Extensions in C++
The Certificate
class provides the methods getX509Extensions
and getX509Extension
for obtaining information about its X509 extensions.
These methods are only supported when using the OpenSSL or Schannel implementations.
The methods return instances of IceSSL::X509Extension
:
namespace IceSSL { class X509Extension { public: virtual bool isCritical() const = 0; virtual std::string getOID() const = 0; virtual std::vector<Ice::Byte> getData() const = 0; }; }
namespace IceSSL { class X509Extension : public virtual IceUtil::Shared { public: virtual bool isCritical() const = 0; virtual std::string getOID() const = 0; virtual std::vector<Ice::Byte> getData() const = 0; }; }
An OID is represented as a string. For example, the OID for the subject alternative name extension is "
2.5.29.17"
. The data associated with the extension is provided as a vector of bytes.
The following code demonstrates how to retrieve an extension:
shared_ptr<IceSSL::Certificate> cert = ...; shared_ptr<IceSSL::X509Extension> subjectAltName = cert->getX509Extension("2.5.29.17"); if(subjectAltName) { vector<Ice::Byte> data = subjectAltName->getData(); ... }
IceSSL::CertificatePtr cert = ...; IceSSL::X509ExtensionPtr subjectAltName = cert->getX509Extension("2.5.29.17"); if(subjectAltName) { vector<Ice::Byte> data = subjectAltName->getData(); ... }