A Jami account is defined by a cryptographic Jami Identity based of RSA asymmetric key-pair and managed with x.509 certificates as defined by RFC 5280.
Jami uses the gnutls library to generate and manage RSA keys and certificates.
This represents the identify of a Jami user.
Generated at account creation
Contains the Jami account public key.
The SHA-1 fingerprint (160-bits) of this public certificate is the JamiId.
Signed by a CA (from an organization or self-signed).
The subject UID field must be the hexadecimal form of the JamiId.
The issuer UID field must be the hexadecimal form of the issuer public key fingerprint (CA).
Random RSA key-pair of at least 4096-bits long.
This is the identity of one specific device used to run Jami.
One per device.
Random and 4096-bits long.
The SHA-1 fingerprint of the public key becomes the DeviceId.
Must be signed by the private key that created the Jami certificate.
The subject UID field must be the hexadecimal form of the DeviceId.
The issuer UID field must be the hexadecimal form of the issuer public key fingerprint (JamiId).
It’s the DHT key where the list of account devices are published and where all devices listen to synchronize on account changes (i.e. adding or revoke a device).
The Jami certificate RSA keys are used as long-term keys to sign/encrypt/decrypt messages sent over the DHT:
private key to sign-off and decrypt incoming messages and device certificates.
public key to encrypt messages (this is done by the message issuer using the receiver public key).
A device can be “removed” from a Jami account through revocation of the device certificate:
Revoked device certificates are added to one or more standard x509 Certificate Revocation List (CRL).
CRLs for revoked device must be valid and signed with the corresponding CA key, which is the Jami account private key.
Why storing data?
Jami needs to load certificates and key-pairs each time the application is started.
When Jami creates a new device, these information are also needed, shared from another trusted device in a secure way.
All platforms doesn’t provide secure way to store data, Jami supports this fact by encrypting data stored outside the memory (i.e. on a file-system) using a user defined password during the account creation.
These files are stored on user device (see below for details):
a compressed and encrypted archive with private account data.
the public certificates chain as a CRT file
the device private key.
Jami archive (export.gz)
Contains private account data.
Currently transmitted over the DHT network when device is created or revoked.
It’s a JSON compressed and encrypted file.
The current format is (could change at any time):
ringCaKey: <base64 serialized PEM-encoded CA private key>,
ringAccountKey: <base64 serialized PEM-encoded Jami private key>
ringAccountCert: <base64 serialized PEM-encoded Jami certificate (public key)>,
ethKey: <base64 serialized of the optional 160-bits Etherium key used to register the JamiId on a public blockchain>,
ringAccountCRL: <base64 serialized of packet list of revoked device certificates PEM-encoded>,
ringAccountContacts: <JSON dictionary to export trusted user contacts>
The JSON byte-stream is compressed using *gzip* algorithm.
Then the gzip-stream is encrypted using AES-GCM-256 symmetric cipher with a 256-bits key.
This key is derived from the user provided password, a PIN and a timestamp, using Argon2 (a password stretching and normalizer) as follow:
salt = PIN + timestamp
key = argon2(password, salt)
argon2 is the argon2i algorithm using t_cost = 16, m_cost = 2^16 (64 MiB), mono-threaded, to generate a 512-bits key and then hashed with SHA-256 to generate a 256-bits key.
PIN is a random 32bits number in hexadecimal form.
+ is string concatenation operator.
timestamp is the current UNIX timestamp divided by 1200 (20 minutes).
password is a user-chosen password (given at account creation).
The PIN should be shown to the user to be copied manually on the new physical device along with the password to finish the device creation process.
NOTE: when exporting a file on the DHT or anywhere else, the daemon update the archive first, to write latest contacts. This is the reason why the password is needed when exporting (it’s not just a copy of the archive somewhere else)
Jami device certificate chain (ring_device.crt)
Includes the Device certificate and parent certificates (Jami device certificate and parents)
Device private key (ring_device.key)
not encrypted, we let the device file-system protect this file
The DHT network
Dedicated Jami distributed network page.
Deprecated in favor of “Conversation requests”
Max 64k (a DHT value)
Contains a conversation Id
Contains the sender URI
Can contains optional metadatas (avatar/profile)
Mostly used to initiate connections with ICE candidates
Can transmit some SIP messages, however SIP channel is preferred
SIP messages can be read status, location sharing, messages notifications.
Outgoing and Incoming calls
Contactable addresses (i.e. IP addresses) of the user are given to peer only:
When we call a peer (outgoing call).
When a trusted peer is calling (incoming call).
All combination forms of how a specific device can be contacted is summarized by a ICE message:
RFC 5245 defines ICE (Interactive Connectivity Establishment), a protocol for NAT traversal.
ICE is used in Jami to establish a peer-to-peer communication between two devices.
Making an outgoing call
The calling device gathers candidates and build an Initial Offer according to the ICE specifications.
The calling device puts the encrypted ICE offer (the Initial Offer) on the DHT at:
)where h is SHA1, + is the string concatenation, DeviceID is in hexadecimal form.
The calling device waits on the peer answer, with its own ICE candidates lists.
At peer answer reception, the calling device starts the ICE negotiation.
If the negotiation succeed, the process continues on a client-side DTLS session establishment over the created ICE socket (see below).
Listening for incoming calls
A device listens for incoming calls by performing a listen OpenDHT operation on
)where h is SHA1, + is the string concatenation and DeviceID is in hexadecimal form.
At ICE Initial Offer reception, the called device must do a security validation of the peer (see below).
If the security validation succeed, the called device starts the ICE negotiation.
If the negotiation succeed, the process continues on a server-side DTLS session establishment over the created ICE socket (see below).
Note: OpenDHT drops values that are not properly encrypted or signed, as specified by OpenDHT protocol.
ICE serialization format
ICE messages exchanged between peers during a call set up use following format.
An ICE message is a chunk of binary data, following msgpack data format.
<8-bits unsigned integer> giving the version of ICE message format protocol used for the rest of the data,
<C++ std::pair of string> of the ICE local session ufrag and the ICE local session password,
<8-bits unsigned integer> giving the number of components in the ICE session,
<array of string> of the previous number entries, where each string describe the ICE candidate, formated as an "a=" line (without the "a=" header) described in [rfc5245, section 4.3](https://tools.ietf.org/html/rfc5245#page-26)
Current defined protocol is 1:
Security Validation of the Peer
Upon reception of the encrypted and signed Initial ICE Offer (through the listen operation), a called device should perform authorization checks of the calling device, identified as the Initial Offer signer.
Authorization rules are implementation defined, but a typical implementation would authorize known or trusted contacts.
If the calling device is not authorized or if for any implementation defined reason the called device refuses the incoming connection request, the called device must ignore the Initial Offer and may log the event.
If the called device authorizes the caller and wish to accept the connection it must build an ICE answer, start the ICE negotiation process and send the encrypted and signed ICE answer at the same DHT key.
Once a peer-to-peer communication channel has been established by ICE protocol, the called device starts a server-side DTLS session on the ICE socket, while the caller starts a client-side DTLS session on the other side of the ICE socket.
The DTLS communication is RFC6347 compliant using gnutls library.
To prevent peer certificates to be displayed in plain-text for the call anonymity, the session handshake is done twice:
A first handshake in anonymous mode to create a secure but anonymous transport.
A second handshake in certificate mode, over the first one, to prove the identity of peers.
Only PFS cipher suites are supported:
The set of supported cipher suites is implementation defined but should include at least ECDHE-AES-GCM.
The actual cipher suites (in gnutls form) is:
Used over the DTLS session to signaling the call (vcard, media negotiation, hangup, instant messaging, …)
Once an encrypted and authenticated peer-to-peer communication channel is available, the SIP protocol must be used to place a call and send messages.
The caller might send a SIP INVITE as soon as the DTLS channel is established.
The SIP implementation must support ICE and SRTP.
Supported codecs are implementation defined, but Jami clients should support the Opus audio coded and the H264 video codec.
SRTP must be used when negotiating media with SIP, using a new random key for each media and each negotiation. ICE should be used when negotiating media with SIP.
Sent on the DHT
DeviceAnnouncement (contains device hash + public key)
Security / Privacy
Jami provides perfect forward secrecy for calls and in call text messages with different Eliptic Curve Diffie-Hellman key negociation at every call. For out of call messaging single RSA-4096 is used. The cryptography library used is GNUTLS
Technical overview of concepts and protocols inside Jami