Figure 1.1 libZRTP Architecture.
LibZRTP consists of two basic elements: the library itself and a set of application-specific helper functions. These comprise a set of frameworks for typical applications using libZRTP, where a developer has two options: use pure libZRTP and implement all necessary helper functions for a particular environment or base the development on the libZRTP helper functions that we provide. The supplied helper functions demonstrate how libZRTP is used in different environments.
System interface : taking into account the fact that LibZRTP was developed as a cross-platform library; could be used inside OS kernels and on different mobile platforms, several system-dependent functions were embedded into the library source code and the remaining ones were implemented as interfaces. The intention was to have as few system-dependent functions as possible, but they still exist. A full list of such functions with respective parameters will be provided in the following sections. All default implementations are already included in the Enterprise version of the library
Shared secrets cache: an important part of the encryption system requiring special care, managed by a set of interface functions for storing retained shared secrets and other additional information. The shared secrets cache is implemented as an abstract interface because there are many different ways to store this data in different applications, such as a simple binary file, in a windows registry, as database, etc. The Enterprise version of the library contains an example of the cache implementation as a binary file.
Setup and initialization module: a set of functions for configuring and initializing the library with five main parameters in LibZRTP: cipher type, authentication scheme, hash type, public keys exchange scheme, and length of the srtp authentication tag. The library provides a set of functions and interfaces for flexibility in configuring these parameters. If the developer does not want to use additional configuration options, s/he can initialize the library with the default settings. More detailed information about the library configuration is available in the corresponding section of "Developers Guide" 6. Setup, initialization and deinitialization
Crypto components management: libZRTP provides an interface for expanding the crypto capabilities by adding crypto components. Please note that this facility should not be abused. The built-in components are capable of providing the necessary functionality in 99% of all cases. Each built-in crypto component is logically based, balanced and fully tested, so use your own components judiciously.
Protocol: the library kernel implements ZRTP protocol logic. As soon as the kernel gets control of the RTP stream it begins exchanging protocol packets and informs the user about transitions from one state to another through a system of callbacks. When the key exchange is finished and a secure channel is set up, the RTP traffic gets encrypted. The library kernel also has functions for protecting the client codec against crashing in case of incorrect packet input. A more detailed description of the connection setup and methods of its management is provided in the 7 Connection life-cycle and management.
bnLib and Brian Gladman's AES Libzrtp uses AES ciphers for the encryption of RTP packets and bnlib for mathematical operations with large integers. Slight changes were introduced to support cross-platform and Win32 kernel-mode operation in bnlib and aes libraries. For this reason, libZRTP is distributed with compiled bnlib and Brian Gladman's aes libraries, where these components can be updated only by the libzrtp development team and will be upgraded in future libzrtp versions. Independent updating is not recommended.
Configuration of libzrtp sysyem-dependent and protocol behavior options available through several configuration files: zrtp_config_xxx. h zrtp_config_user.h. The first one is a set of predefined for every platform options and adjustments. libzrtp includes default zrtp_config_XXX.h for Linux, OS X, Windows platforms and Symbian OS. In most cases the developer doesn't need to modify this file. The second file zrtp_config_user.h contains set of parameters for protocol related adjustments. See documentation in zrtp_config_user.h for more information.
Further instructions must be followed in order to build and set up the library in any Unix-like operation system (Linux, FreeBSD, MacOS):
For library configuration and installation on Windows platform project files from /projects directory can be used.
If you want to build libzrtp in Windows kernel mode you mast use MAKEFILE.WIN32, MAKEFILE.WIN64
Note for Symbian developers: The Libzrtp project supplies both a Symbian OS .mmp project file and a bldmake component description file, bld.inf. The bld.inf file simply specifies the name of the .mmp project file to be built. Using these files the library can be built from the Metrowerks CodeWarrior for Symbian Professional IDE, the Microsoft's Visual C++ IDE, or from the command-line. To build libzrtp itself use the bld.inf and libzrtp.mmp files from the root directory. To test libzrtp on Stmbian we have converted our test-unit from C to C++. Source files can be found in "libzrtp\test\Symbian" folder. This folder also contains the ./bld.inf and test.mmp project files.
By default the library and test-units are built with struct member alignment = 1Byte. If the application uses another value of structure alignment it may cause errors. To avoid problems use the same value of structure alignment while compiling the library that will be used in the application (most tests were implemented with the value of alignment equal to 1byte, so that value is preferable).
For components other than the library itself, tests are provided to determine whether they work on a given platform. The libzrtp tests runs test-vectors and test-cases for all crypto components, creates several parallel ZRTP sessions, initiates a transfer to the protected mode, and displays statistics. If the application test is successfully completed, then the library is configured correctly and all components work correctly.
Possible problems and how to solve them:
zrtp_system.h following the comments.If you are faced with some problems during configuration or installing of the library, send a report to the Support Service. If you installed the library on a platform not described here, please contact the Support Service. We are interested in knowing the results of the testing on new platforms. We will carefully examine all proposals and will do our best to implement them in the new library versions.
Figure 1.2 - libzrtp structure.
The library kernel consists of the srtp encryption module and the ZRTP protocol logic module. The details of the srtp functions are hidden from the user behind the libZRTP interface and are not directly accessible.As was stated earlier, the library uses several system-dependent functions to make it easy to migrate across platforms. All interfaces are developed as external functions and only after their implementation can you build and use libzrtp. (Most of these interfaces are already implemented)
Preferences: the second auxiliary block is a configuration module. It lists instructions for library initialization. LibZRTP also gives a set interfaces for integrating new components to make the library more functional, flexible, and multi-purpose. All functions of this block are further described in sections 6. Setup, initialization and deinitialization and Library crypto-components.
As soon as the system-dependent functions are implemented and all crypto-components are configured, the library is ready for use. The interface for connections and RTP stream management is basic and easy to use. It can be divided into three subgroups:
[doc] - documentation and schemes files; [include] - header files: zrtp_config_user.h - user defined ZRTP configuration options; zrtp_config_win.h - Windows Family related adjustments; zrtp_config.h - libzrtp automatic configuration routine. zrtp_crypto.h - contains definitions of the data types and functions necessary to strengthen the crypto-segment of the library. These functions are used only by advanced users and inside of libzrtp kernel. Typical projects based on libzrtp do not use these functions; zrtp_engine.h - contains types and functions needed by the ZRTP state-machine For internal use only; zrtp_error.h - contains error codes returned by the libzrtp functions; zrtp_iface_system.h - contains a set of OS-related interface functions which must be implemented in order to use the library; zrtp_iface.h - contains a set of interface functions which must be implemented in order to use the library; zrtp_legal.h - libzrtp license agreement; zrtp_list.h - contains functions and macros for safe operations with linked lists. All lists in libzrtp are based on these functions. They can be used to avoid mistakes in list operations; zrtp_log.h - contains functions to track bugs and store the error log.; zrtp_pbx.h - conatins declarations of the main PBX related functions. Use this header if you are the implementor of some VoIP-server solutions; zrtp_srtp.h - SRTP crypto types and interfaces. Used to integrate libzrtp with third party SRTP implementations; zrtp_srtp_builtin.h - data structures for built-in realization of SRTP. zrtp_string.h - contains functions for the use of the special, safe strings, zrtp_string_t, used by libzrtp. zrtp_types.h - contains the definitions of the internal data types which are used by libzrtp developers and experienced users. zrtp.h - conatins declarations of the main dataypes and function functions necessary to operate libzrtp. This file header is only must to be included in each module using the libzrt functions;[projects] [gnu] - make files for Unix-like systems using autotools; [symbian] - configuration and make files for Symbian platform; [win] - Set of Microsoft Visual Studio project files for Windows and Windows CE. [win_kernel] - makefiles for Windows Kernel mode. [xcode] - project files for Apple XCode.[src] - libzrtp source files;[test] - test suite for libZRTP kernel logic. Includes versions for Unix, Windows, Windows CE and Symbian.
[third_party]
[bnlib] - libbn files which are not intended for external use; [bgaes] - AES encryption library and hash functions by Brian Gladman; Most callbacks are initiated by the library in response to state-machine changes in that result from processing incoming ZRTP messages. While incoming ZRTP messages are being processed, the context data is protected from other asynchronous calls by a mutex. Therefore this mutex is blocked at callback initiation. This must be taken into account when system designing. Deadlocks or other recursive locks can be easily avoided by preventing other libzrtp calls from callbacks. In some systems the blocking time is limited, so the stream should not be deferred in the callback handler.
Figure 1.3 libzrtp data structure
Every session context contains all of the necessary information about the ZRTP session and the stream contexts. ZRTP Session contains profile defined crypto options and behavior of every stream within current session. Besides streams, session context contains SAS for the entire call. The number of streams is defined with the types::ZRTP_MAX_STREAMS_PER_SESSION variable. An array of streams is included in the static session context, where the streams can be used with the main::zrtp_attach_stream() function call. The stream context contains all possible information about each ZRTP stream. Since the ZRTP protocol is stream-oriented, it is the stream context which is the main operant for most of the libzrtp functions.
The context is a key parameter in the operation of all library functions. One should abide by the following rules when working with the context:
Nevertheless, each platform has its own specific definition, and for maximum efficiency and convenience, its own set of functions should be implemented.This can be accomplished either by starting from the zero-based leaf or by optimizing the built-in implementation.
This section gives an overview of the component functions, and provides a description of their main algorithms. See the API description and the source code of the default components for further implementation details. ("./src/iface/ folfer").
Implementation of the cache should not include any processing of the stored values. All business-logic is implemented in libZRTP. The cache should provide only enough storage for the necessary information. The user application should not call any of the cache functions directly.
The shared secrets' cache should be permanent and constantly updated using data storage with a relatively short response time.
Each application which uses libzrtp may have a different format for the shared secrets' cache. The only prerequisite is an interface with the minimum required number of parameters to allow the operation of libzrtp, such as:
"src/iface/zrtp_cache.c". To build default cache use DBUILD_DEFAULT_CACHE compilation flag.Audio alerts are of major importance on systems without graphical interfaces. For instance, alerting a transition from protected mode to non-protected mode is the only way to notify the responder ZRTP endpoint about the interruption in traffic encryption. The events to be alerted are:
The packet resending scheme is as follows: libzrtp constructs a packet, to be sent to the remote party. The constructed packet is cached and sent to the network through the zrtp_send_rtp () function. If the packet is lost it must be resent after the time period. Therefore libzrtp calculates the time interval and enqueues the packet for resending zrtp_send_packet_later(). When the required interval has passed the user's process calls a handler function (zrtp_retry_task::callback) and the packet is resent, a new interval is calculated and the task is enqueued for resending again. When the ZRTP remote party's response is received, the zrtp_cancel_send_packet_later() call is initiated and resending is overridden.
The "Send packet later" module should function in the following way: each ZRTP stream should be provided with a tasks pool. The send task is created by calling zrtp_send_packet_later(). If the pool already contains the adding task for that stream it should be replaced with the current one. If don't - new task should be added to the pool. When the determined time-out is reached, the user's stream makes a callback and removes the task from the pool. The call of the cancel_send_packet_later() removes the task from the communications queue. If cancel_send_packet_later() called with NULL tasks - all tasks should be removed from the pool. Thus all logic is integrated to the library. The only thing the module does is make the callbacks after a defined time period.
Special features of the implementation: as the library is designed to be able to work in multi-threaded environments, special attention should be paid to synchronization. Each callback called by the "Send packet later" module is implemented in parallel to the main stream. The synchronization of this libzrtp operation is provided by the zrtp_stream_protector mutex. Cancel/add task synchronization is implemented on the libzrtp kernel level as well. The only thing the user should be careful about when implementing the "Send packet later" module is the synchronization of operations with the tasks list for several contexts. The callback's recursive nature should be taken into account (a callback may initiate the adding of a new task or canceling of an existing one).
The library is comes with standard default "Send packet later" module implementation. To install the library with the default "Send packet later" module implementation use the flag DBUILD_DEFAULT_TIMER. See implementation details in "./src/iface/zrtp_scheduler.c" or "src/iface/symbian/ZrtpIScheduler.cpp" for Symbian. To use the default scheduler on Symbian - just add C++ class to your project.
Failure to use true entropy from the physical environment as a basis for generating random cryptographic key material would lead to a disastrous loss of security.
When a random number is required by the ZRTP protocol, the library kernel calls the Deterministic Random Bit Generator interface function zrtp_randstr(). That function requires the existance of an entropy pool that has already been seeded with sufficient entropy. This entropy pool must be seeded by calling zrtp_add_entropy().
The zrtp_add_entropy() function takes a buffer of raw unprocessed entropy provided by the caller and adds it to the entropy pool via the SHA-512 hash function. But the zrtp_add_entropy() function also adds in some other entropy from the operating system by calling the zrtp_add_system_state(), which is discussed in the next section.
zrtp_add_system_state() description.
To add entropy to the entropy pool maintained by the libzrtp random number generator, the application calls the zrtp_add_entropy() function. This entropy accumulation function may be called whenever new entropy becomes available.
The entropy pool builds up more precious entropy each time you call zrtp_add_entropy(). Once in a while, it is a good idea to save the entropy in nonvolatile storage, by calling zrtp_randstr() and writing the output to a file, or to flash memory, or to some nonvolatile system storage area. This can be done whenever the VoIP application shuts down, or perhaps at the end of each secure VoIP call. A minimum of 512 bits (64 bytes) of output from zrtp_randstr() should be stored this way, but there is no need to store more than 256 bytes. When the VoIP application starts back up again, the contents of this nonvolatile entropy file should be added back into the active entropy pool by passing it to the zrtp_add_entropy() function.
The zrtp_add_entropy() function augments the application-supplied entropy by adding in some entropy from the operating system, by calling the zrtp_add_system_state() function. The application may call the zrtp_add_entropy() function, not the zrtp_add_system_state() function, which is called only by zrtp_add_entropy(). However, the developer may have to implement the zrtp_add_system_state() function for his own platform if the default implementation is not sufficient for that platform.
The SDK library provides a default implementation of zrtp_add_system_state() for Windows Kernel and Unix based platforms. For the Windows kernel mode zrtp_add_system_state() gathers current system state information as an entropy source. Among them are the performance counter, the current value of the system interrupt-time count, the count of the interval timer interrupts, and the values of some CPU registers. For Unix platforms, zrtp_add_system_state() calls /dev/urandom.
On a desktop or laptop PC running Linux, FreeBSD, NetBSD, or OpenBSD, a good source of entropy may be found by reading from /dev/random or /dev/urandom. This is because /dev/random is seeded by entropy from keyboard timings, mouse movements, disk latency measurements, or other physical noise sources, some of them involving unpredictable human interaction. However, some low cost embedded Linux systems have no keyboard, no mouse or trackpad, no disk drive, and are starving for high quality entropy. There are some low cost Asterisk PBX boxes that are built this way. Or hardware Analog Telephone Adapters. Or low cost consumer routers. Many of them have no /dev/random implemented, or worse, have only a stub for /dev/random that does not actually collect any environmental entropy. This creates a dangerous illusion that entropy is available, because /dev/urandom appears to work, but is not backed by true entropy. This is bad, and not only for ZRTP. Platforms like these might not be able to generate strong cryptographic key material for SSH or SSL.
If you are an OEM that builds hardware like this, and you wish to implement the ZRTP protocol with our libzrtp SDK, you really should provide a properly implemented /dev/random and /dev/urandom, properly supplied with true environmental entropy. If you are building a telephone, you can easily collect entropy from raw audio samples from the microphone. If the phone includes a video camera, you can collect entropy by sampling a few raw uncompressed video frames. If it's a mobile phone or a cordless phone, you can collect entropy from the RF noise in your wireless circuitry. If it's an embedded box like a router or low cost PBX, you can do high resolution timings of packet arrivals and use the timer readings as entropy sources. A PBX might include an analog interface to PSTN phone lines, and those interfaces usually include registers that measure analog voltage levels, which can serve as a source of entropy. The entropy sources do not need to produce much entropy, just a few bits at a time, but it can build up slowly until you have accumulated a few hundred bits of entropy. That's enough to generate cryptographically useful keys. Even if it takes some seconds or even minutes to accumulate this much entropy the first time your product is activated, it can be stored in nonvolatile storage so that it will be ready to reseed the entropy pool instantly the next time your product is powered up.
In the ideal case, if you are designing the embedded hardware yourself, you could provide a good source of entropy by including a simple ring oscillator in the hardware. A ring oscillator is a circular chain (a ring) of NOT gates, and has nothing whatsoever to do with a telephone ring generator. The oscillation frequency drifts from thermal noise, and sampling the output at some low sampling rate is a good way to get some entropy. However, most designers have to work with existing hardware designs, and don't have the luxury of adding special hardware to generate entropy, which means you have to improvise with whatever you can collect from the environment, using any of the methods described above.
If the library is used on another platform, the potential entropy sources should be thoroughly analyzed and a custom implementation of zrtp_add_system_state() must be developed for that platform. You can get your entropy collection ideas by looking at the default implementation of zrtp_add_system_state() provided in zrtp_rng.c. Again, microphone noise can be a good entropy source for VoIP clients. Raw, uncompressed, unfiltered audio samples should be used.
If you have entropy gathering schemes for platforms not already supported in libzrtp, or if you doubt the correctness of your entropy collection approach, contact us to discuss how it may be done. We will do our best to provide you with technical assistance.
zrtp_add_system_state() description.
ZRTP is a stream-oriented protocol. The session concept has been included merely to provide an analogy to the SIP connection. All library functions operate only with streams. Each stream can be supplied with its own settings and is processed by the library with respect to them.
"Behavior" settings are represented by boolean flags with "true" and "false" values. They include:
"Crypto-settings": the protocol defines 5 crypto-component types used in calculations:
The "crypto-settings" configuration contributes to the choice of each component type by providing a priority ranking. It serves a a request, in that the component chosen for the first priority is not necessarily used in the connection installation. Rather, the resulting component type depends also on the opposite side's settings. The component choosing mechanism is as follows: both sides communicated their supported components during the "discovery phase". After that the initiator chooses the optimal intersection of components. In this way it is possible to set condition such as: "use component type A; if A is not supported by the other side, then use B; never use C".
If, when the discovery phase is finished, no common components of at least one type have been discovered, the connection installation will be aborted.
For components identification the numerical values of the following types are used: zrtp_hash_id_t, zrtp_cipher_id_t, zrtp_atl_id_t, and zrtp_sas_id_t, which are defined in zrtp_crypto.h. The profile field responsible for components of a particular type setting is an integer-valued array where component identifiers should be placed in order of priority. 0-element is of the first priority. The list should end with ZRTP_COMP_UNKN=0. Return to the previous example for configuration of the public data exchange scheme: zrtp_profile_t::pk_schemes={ZRTP_PKTYPE_DH4096, ZRTP_PKTYPE_DH3072, 0,...0} means that the library will try to set the connection in DH4K mode or DH3K mode if the first is not supported. Attempts to use "Preshared" will be discarded.
As described above the profile is used in the zrtp_init_session_ctx(). To simplify the library use, the default configuration is available. The default profile contains the set of components defined by the ZRTP protocol standard. For detailed information see the description of zrtp_init_session_ctx(). The following functions of the protocol usage are available as well:
Figure 1.4 libzrtp crypto-components structure
Figure 1.5 libzrtp state-machine diagram
session creation ( zrtp_init_session_ctx() ). This call connects the session with the side's ZRTP identifier and prepares it for further use. The creation and start of ZRTP streams are possible only after this function call. After initialization the session is placed in the list of sessions, which is stored in zrtp_global_ctx. The maximum number of sessions is limited only by the operating memory size. It is also possible to create several sessions between two ZRTP endpoints. Such sessions will be interpreted by the scheduler as a single one with many streams, since the synchronization of the stream work is realized not in the frames of a session but between two ZRTP endpoints. After this session's stream has completed, the resources allocated in initialization should be released by the call zrtp_done_session_ctx().
session deletion ( zrtp_done_session_ctx() ). This call removes a session from the list of active sessions and releases all allocated resources. Deinitialization of the session leads to deinitialization of all attached streams and further use of these streams will have unpredictable results.
stream adding ( zrtp_attach_stream() ). This function separates the ZRTP stream in the frames of the session indicated and prepares it for further use. When a stream is created it is bound to the appropriate RTP media by SSRC and this SSRC value can be used for traffic assignment between ZRTP streams. The stream is ready for use and transition to the protected mode immediately after creation.
stream stopping ( zrtp_stop_stream() ). This function will stop the protocol at any stage: all delayed tasks are canceled, and the protocol packet exchange and encryption is stopped. After this function call it is necessary to stop traffic processing using the zrtp_process_xxx() function. When the stream is stopped the remote side is not informed in any way about the encryption break (don't confuse with the transition to non-protected mode) so the stream should be stopped right before closing the communication channel (RTP steam/whole SIP session).
Figure 1.6 libzrtp extended
Figure 1.7 libzrtp extended
Start-Clear. After the protocol starts, both sides start exchanging Hello packets and the ZRTP sides exchange ZIDs and lists of supported crypto-components. If one side uses the "autosecure" option, then right after exchanging Hello packets, transition to the protected mode (clear-secure) will be initiated. Otherwise the state-machine moves to the Clear state and informs the user's application about that through a zrtp_event_callback() of type ZRTP_EVENT_IS_CLEAR. The transition to the protected mode can be initiated manually by calling zrtp_secure_stream()
Clear-Secure. Regardless of which initiation typeis used, autosecure or zrtp_start_stream(), one side becomes the "initiator" and moves to the "Initiating Secure" state (ZRTP_EVENT_IS_INITIATINGSECURE event). The other side , having received a Commit packet becomes rthe esponder (ZRTP_EVENT_IS_PENDINGSECURE). The Commit contains the crypto-components list, chosen by the initiator for use at connection initiation and for further traffic encryption. Further along the stream, the keys calculation is performed using the Diffie-Hellman algorithm and then confirmed. After the keys calculation and the srtp encryption ibzrtp moves to the Secure state (ZRTP_EVENT_IS_SECURE) and is ready for traffic encryption. Having moved to Secure mode the user gets access to all information about the connection parameter settings and all generated keys for verifying data: SAS values. 8.2 SAS values and secrets options describes how these values are obtained and used.
Secure-Clear. Transition to the non-protected mode: let side A initiate the transition by means of zrtp_clear_stream() call. This function call initiates the sending of GoClear packets. Side B, having received a GoClear packet, moves to "Pending Clear" (ZRTP_EVENT_IS_PENDINGCLEAR) and informs the user about the remote side's intention to break the protected communication session. In the Pending Clear state, the responder's libzrtp blocks outgoing RTP traffic until the transition to the non-protected mode is confirmed by a zrtp_clear_stream() call. After that, side B moves to the Clear state, renews the RTP media and notifies side A that it accepts a protected connection break.
Here are the basic algorithms of the ZRTP Session life-cycle. For single stream peer sessions the algorithm looks like this:
When the user wants to attach another stream to an already existing ZRTP session he calls zrtp_attach_stream() and starts it with zrtp_start_stream(). zrtp_start_stream() checks whether the ZRTP session is already established between two endpoints. If so, the stream goes into the Secure state using Multistream mode, eliminating a full DH exchange.
It is possible to start several streams within a single session simultaneously. Imagine that we have a ZRTP session with two streams: Audio and Video. In order to go secure we start both of them: zrtp_start_stream(Audio) and zrtp_start_stream(Video). To handle this case correctly libzrtp starts one stream in Full, DH mode and keeps the other one. When the first, Full stream has switched to the Secure state and the Session key is computed, the second one will be resumed in Multistream mode.
To switch to Clear and then go Secure again, use zrtp_clear_stream() and zrtp_secure_stream(). The library will destroy the Session key and repeat the algorithm outlined above.
a) zrtp_process_rtp/rtcp(): processes the outgoing RTP/RTCP media traffic. In accordance with the state of the protocol state-machine, the following activitions are performed:
To conclude, if the error has occurred on the local side the library makes a zrtp_event_callback() of the ZRTP_EVENT_ERROR event type. The error code is contained in the zrtp_stream_ctx_t::last_error context variable and may be analyzed by the user's application. The list of possible errors is defined in the zrtp_protocol_error_t definition. If the error has occurred on the remote side, the transition to Clear mode will be initiated and the zrtp_stream_ctx_t::last_error will contain the code of the remote error.
The protocol and the library are developed such that when an error occurs there is no threat of secret information disclosure. The error handlers are necessary for debugging and provide detailed information about the reason for the error.
The list of errors and a detailed description of the terms of their occurrence can be found at 7.4. Error handling zrtp_error.h.
The library is designed such that the user has access to all context data at all stages of establishing a connection. While this adds flexibility to the designed application, it does demand extra attention when working with context fields. No context field should be changed by the user's application directly. Only public fields and methods which do not begin with the "_" character are accessible for reading.
As the libzrtp works in a multi-threaded environment, the call for context fields should be performed in definite time intervals, otherwise the wrong value could be read or non-existing structure could be addressed. As a rule, necessary information from the context structure should be read with the zrtp_event_callback() call.
The following describes the main data available to users and recommendations for its use.
The protocol state-machine stores its current state in the zrtp_stream_ctx::_state context variable. The advanced user can use this variable for stream state analyses. The previous state is stored in zrtp_stream_ctx::_prev_state. Access to these variables is allowed in all zrtp_event_callback() and zrtp_packet_callback() callback types.
It works as follows: The SAS is calculated for each of the two sides separately on the basis of the session key. A symbol/string SAS value is displayed to each user. Then both users have to tell each other their SAS's orally. If they are identical it guarantees the key's authenticity and absence of a MitM. When both users have been assured of the key's authenticity, they can verify the remote side's key. In the ZRTP protocol each new stream key is calculated on the basis of the previous one, so it is sufficient to perform the verification procedure only once (at the first call). If the first call was MitM free, the next one will also be MitM free. If it is not, the protocol engine will detect it. The Verification Flag is stored in the cache and is restored automatically at the next call.
Implementation: after the transition into protected mode (ZRTP_EVENT_IS_SECURE) the developer gets access to the SAS and flag values. Various SAS forms are stored in zrtp_conn_ctx_t::sas_values, and flag values are accessible in the zrtp_conn_ctx_t::secrets structure. The GUI of the designed application has to display the SAS in at least one of the forms (sas_values::str1 and/or sas_values::str2) and the current common verification flag value (verifieds & ZRTP_BIT_RS0). If the user wants to change the verification flag (check/uncheck), the zrtp_cache_set_verified() function should be used.
RTP packets can be encrypted and authenticated (using the zrtp_srtp_protect() function), turning them into SRTP packets. Similarly, SRTP packets can be decrypted and have their authentication verified (using the zrtp_srtp_unprotect() function), turning them into RTP packets. To work with RTCP packets use zrtp_srtp_protect_rtcp() and zrtp_srtp_unprotect_rtcp() analogously.
In the implementation of the SRTP interface, the following datatypes are used:
The SRTP context defines two streams, incoming and outgoing. When a stream is initialized, its structure is defined by the srtp profile type, zrtp_srtp_profile_t. The profile specifies the crypto policies for RTP and RTCP, and the values of SSRC, the master key and the master salt.
A crypto policy is defined by the zrtp_srtp_policy_t type. It specifies the following packet processing options:
Here we assume that the ssrc value is already set to describe the SSRC of the stream that we are sending, and that the functions zrtp_get_rtp_packet() and zrtp_send_srtp_packet() are available to us. The former puts an RTP packet into the buffer. The latter sends the RTP packet in the buffer.
zrtp_srtp_profile_t inc_profile, out_profile; if(zrtp_status_ok != zrtp_srtp_init(zrtp_global, zrtp_srtp_global)) { //handle error code } //initialize profiles // // TODO: code for profiles init // //allocate and initialize SRTP session context zrtp_srtp_ctx *srtp_ctx = zrtp_srtp_create(zrtp_srtp_global, &inc_profile, &out_profile); if(NULL == srtp_ctx) { //handle error code } //main loop: get rtp packets, send srtp packets while(1) { zrtp_rtp_info_t packet; zrtp_get_rtp_packet(&packet); if(zrtp_status_ok != zrtp_srtp_protect(zrtp_srtp_global, srtp_ctx, &packet)){ //handle error code } zrtp_send_srtp_packet(&packet); }
The implementation of the replay protection mechanism is based on a sliding window defined by the zrtp_srtp_rp_t type. It consists of a bit string, where each bit corresponds to a packet sequence number. The position of the corresponding bit in the window is determined by the value of zrtp_srtp_rp_t::seq and the sequence number.
+-----------------------+
-------------------------| |-----------------
A | B | C
-------------------------| |-----------------
+-----------------------+
<---window moving direction---
The position of a packet relative to the sliding window is determined during its processing by the replay protection mechanism, and depending on which interval that result falls in, the following actions occur:
Consider the following tree:
SSRC
/ \
/ \
/ \
in stream out stream
/ \ / \
/ \ / \
RTP RTCP RTP RTCP
The uniqueness of the sequence number of a packet is preserved only within a leaf of this tree.
Two lists of nodes (zrtp_rp_node_t), are maintained by the library. One of these contains replay protection nodes for incoming packets, and the other for outgoing ones. The list is searched by SSRC value.
Additional steps to use an alternative implementation of SRTP:
This interface connects libzrtp and libsrtp.
As described in the 3. Data structures, the library data structure may be compared with a tree, where the global context zrtp_global_ctx is at the root. The following items are among the shared data of this structure:
The next structure in the hierarchy, zrtp_conn_ctx_t sessions context, is shared among all of its streams. By convention, they may be divided into two groups:
ZRTP sessions protocol data. The protocol data is encapsulated by a great number of fields: the flags set, the set of shared secrets, the SAS strings, DH related data, etc. All of this data is modified by the DH streams, and PRESHARED streams are used for reading only. The presence of only one DH stream between two ZRTP endpoints at the same time is required by the protocol. This implementation helps protect all session data automatically as follows:during the user's DH stream initialization attempt, the presence of other activated DH streams is checked across all sessions between the ZRTP endpoints. If such a stream is detected, the initialization attempt is delayed until the end of the current DH stream. If the DH stream is initialized by the remote ZRTP side and the DH stream that is processing the initialization query was declined, the active DH streams search process uses the mutex _dh_mult_protector for synchronization;
Streams list _streams. Operations with streams are protected with the item zrtp_conn_ctx_t::_streams_protector.
Lastly, the stream contexts are represented by the tree leaves. All operations of the upper level structures have been synchronized already. For encryption/decryption the context data is not used. Libzrtp synchronizes only the protocol packet processing: zrtp_process_srtp() takes protocol packets and zrtp_secure_stream()/zrtp_clear_stream() is synchronized. For instance, if the main processing stream calls zrtp_process_srtp() for each incoming packet and at same time the control stream calls zrtp_secure_stream(), it is processed correctly.
Mutexes are used for synchronization in libzrtp. The mutex type is defined by zrtp_mutex_t type in the zrtp_types.h file. Like all of the system dependent functions, they are implemented as a set of interfaces:
locks the object referred to the mutex; mutex.The process from the client side consists of calling the PBX number, confirming the SAS, and then tagging the current call as "registration" using the zrtp_register_with_trusted_mitm() function. To separate MiTM registration calls from general VoIP calls, the server-side ZRTP endpoint should mark a registration call using a special flag in the Confirm packet sent to the client-side. The user-space application is informed of the registration ritual by a zrtp_event_t::ZRTP_EVENT_IS_CLIENT_ENROLLMENT event.
The server, having received a call to the registration number, authenticates the client using a password scheme, and then tags the call as a registration. To do this, the server ZRTP peer should start the ZRTP exchange in a special way, using one of the functions zrtp_start_registration_stream() or zrtp_secure_registration_stream(). On calling one of these functions, the PBX then generates a shared secret and associates it with that client for subsequent authentication and SAS transfer. In registration stream handling the following different scenarios are possible:
The user may also cancel registration to the PBX by removing the other MiTM shared secret with the PBX. If this occurs, libzrtp raises the zrtp_event_t::ZRTP_EVENT_USER_UNENROLLED event on the PBX side and it may remove it's own cache with this user (but this isn't recomended because it may create en platform for DOS attacks)
It would be inefficient to store the corresponding PBX shared secrets of every pair of ZID's in the cache used for regular calls, so PBX shared secrets are stored separately. The two functions in libzrtp for reading and writing the PBX cache are zrtp_mitmcache_get(), and zrtp_mitmcache_put(), respectively. The function interfaces are analogous to those of the functions zrtp_cache_put() and zrtp_cache_get() (see the API for further details). The cache is implemented as a single file with the list of PBX shared secrets at the beginning, and the list of regular shared secrets at the end.
The two known problems in the PBX MitM model are synchronizing the SAS value and synchronizing its rendering scheme. The ZRTP protocol solves both of these using SAS Relaying. After all three sides have switched to Secure, in common for ZRTP endpoint way, the SAS value or its rendering scheme may be initiated and/or renewed from either side using the function zrtp_update_remote_options().
To use the new SAS Relaying Scheme, in the first stage of setting up a PBX connection, there is nothing different to do; having received an incoming call, the PBX establishes a secure channel with both sides using the zrtp_start_stream() function. Only after the transition to the Secure state does the PBX have enough information to synchronize the rendering scheme and SAS value to the registered user.
Obviously, the SAS may be transferred only to a registered user. Whether the user can receive an transferred SAS can be determined by the zrtp_is_user_enrolled() function. In this case, the function zrtp_update_remote_options() is used. These functions move the stream::mitm_m ode into the ZRTP_MITM_MODE_RECONFIRM_SERVER mode and allow the SAS value to be sent. Otherwise, a regular call is running without SAS transferring. When the PBX runs a call in MiTM mode it's possible that both sides are registered. In this case the PBX should choose one to resolve the ambiguity. This may be done with function zrtp_schoose_one_enrolled().
In order to maximize the simplicity of setting up a secure channel in MitM mode, libzrtp provides the function zrtp_resolve_mitm_call(). Upon the transition of both sides into the secure state this function is necessary and sufficient to synchronize the SAS value. After completing the process, libzrtp generates the zrtp_event_t::ZRTP_EVENT_REMOTE_SAS_UPDATED event.
Having received the SAS Relay packet, the client looks up the shared secret for the given PBX. If the shared secret matches, the client's ZRTP machine accepts the SAS and goes into zrtp_stream_t::mitm_mode ZRTP_MITM_MODE_RECONFIRM_CLIENT and generates the zrtp_event_t::ZRTP_EVENT_LOCAL_SAS_UPDATED event. The user-space application should handle this event and update the SAS value according to the specified rendering scheme. If the client receives a request for tranferring an SAS from an unverified user (the MitM shared secret is not found, or doesn't match), this results in the error zrtp_protocol_error_t::zrtp_error_possible_mitm3. Then the process of establishing a secure channel is terminated. The client's ZRTP machine cannot request any further action from the user except a temporary registration.