Documentation
¶
Overview ¶
Package xochimilco provides an usable API for end-to-end encrypted communication based on the "Signal Protocol".
The "Signal Protocol" refers to the Extended Triple Diffie-Hellman (X3DH) key agreement protocol paired with the Double Ratchet algorithm. Both are implemented and exposed in this repository's subdirectories, parametrized as specified by OMEMO, XEP-0384[0]. For implementation details please refer there.
Following OMEMO, a message's payload is encrypted with a fresh random key, AES-256-CBC with a truncated HMAC-SHA-256. Only this key next to the payload's authentication tag is passed through the Double Ratchet.
Everything above the cryptography is out of this package's scope. Thus, this is no XMPP library; neither the urn:xmpp:omemo:2 namespace nor PEP bundles, device lists or trust management are implemented. Xochimilco's own message framing carries the same key material as an OMEMO bundle resp. an OMEMOKeyExchange would.
[0] https://xmpp.org/extensions/xep-0384.html
This package is based on Alvar Penning's xochimilco[1], which implements the Signal Protocols with their own parameters.
[1] https://github.com/oxzi/xochimilco
Example ¶
// In this example, Alice and Bob can exchange messages over some chat
// protocol. Furthermore, they already know each other's public key.
alicePub, alicePriv, _ := ed25519.GenerateKey(nil)
bobPub, bobPriv, _ := ed25519.GenerateKey(nil)
alice := Session{
IdentityKey: alicePriv,
VerifyPeer: func(peer ed25519.PublicKey) (valid bool) {
return peer.Equal(bobPub)
},
}
bob := Session{
IdentityKey: bobPriv,
VerifyPeer: func(peer ed25519.PublicKey) (valid bool) {
return peer.Equal(alicePub)
},
}
// Alice starts by offering Bob to upgrade the connection.
offerMsg, err := alice.Offer()
if err != nil {
panic(err)
}
fmt.Printf("A->B\tOFFER\t%s\n", offerMsg)
// Bob acknowledges Alice's offer.
ackMsg, err := bob.Acknowledge(offerMsg)
if err != nil {
panic(err)
}
fmt.Printf("B-A\tACK\t%s\n", ackMsg)
// Alice evaluates Bob's acknowledgement. This SHOULD be `isEstablished`.
isEstablished, _, _, err := alice.Receive(ackMsg)
if err != nil {
panic(err)
} else if !isEstablished {
panic("invalid message")
}
// Now we have an established connection.
// Let's exchange some very important messages.
dataMsgAlice1, err := alice.Send([]byte("hello bob"))
if err != nil {
panic(err)
}
dataMsgAlice2, err := alice.Send([]byte("how are you?"))
if err != nil {
panic(err)
}
// Ops, the messages were reorder on the wired.
fmt.Printf("A->B\tDATA\t%s", dataMsgAlice2)
fmt.Printf("A->B\tDATA\t%s", dataMsgAlice1)
_, _, plaintextAlice2, err := bob.Receive(dataMsgAlice2)
if err != nil {
panic(err)
}
fmt.Printf("B\tRECV\t%s", plaintextAlice2)
_, _, plaintextAlice1, err := bob.Receive(dataMsgAlice2)
if err != nil {
panic(err)
}
fmt.Printf("B\tRECV\t%s", plaintextAlice1)
// Bob also sends an answer.
dataMsgBob, err := bob.Send([]byte("hej alice!"))
if err != nil {
panic(err)
}
fmt.Printf("B->A\tDATA\t%s", dataMsgBob)
_, _, plaintextBob, err := alice.Receive(dataMsgBob)
if err != nil {
panic(err)
}
fmt.Printf("A\tRECV\t%s", plaintextBob)
// Finally, Alice closes her Session...
closeMsg, err := alice.Close()
if err != nil {
panic(err)
}
fmt.Printf("A->B\tCLOSE\t%s", closeMsg)
// ...and tells Bob to do the same.
_, isClosed, _, err := bob.Receive(closeMsg)
if err != nil {
panic(err)
} else if !isClosed {
panic("invalid message")
}
_, err = bob.Close()
if err != nil {
panic(err)
}
Index ¶
- Constants
- func DecryptPayload(keyMaterial, ciphertext []byte) (plaintext []byte, err error)
- func DecryptPayloadV1(keyMaterial, iv, ciphertext []byte) (plaintext []byte, err error)
- func EncryptPayload(plaintext []byte) (keyMaterial, ciphertext []byte, err error)
- func EncryptPayloadV1(plaintext []byte) (keyMaterial, iv, ciphertext []byte, err error)
- type Session
- func (sess *Session) Acknowledge(offerMsg string) (ackMsg string, err error)
- func (sess *Session) Close() (closeMsg string, err error)
- func (sess *Session) Offer() (offerMsg string, err error)
- func (sess *Session) Receive(msg string) (isEstablished, isClosed bool, plaintext []byte, err error)
- func (sess *Session) Send(plaintext []byte) (dataMsg string, err error)
Examples ¶
Constants ¶
const ( // Prefix indicates the beginning of an encoded message. Prefix string = "!XO!" // Suffix indicates the end of an encoded message. Suffix string = "!OX!" )
Variables ¶
This section is empty.
Functions ¶
func DecryptPayload ¶
DecryptPayload based on the key material received through the Double Ratchet.
This is the counterpart to EncryptPayload: after a recipient device's own DoubleRatchet.Decrypt has recovered the key material from its per-device wrapped key, DecryptPayload recovers the plaintext from the shared payload ciphertext.
func DecryptPayloadV1 ¶ added in v0.1.0
DecryptPayloadV1 is the counterpart to EncryptPayloadV1: after a recipient device's own DoubleRatchet.Decrypt has recovered the key material and the wire has supplied iv and ciphertext, this recovers the plaintext.
func EncryptPayload ¶
EncryptPayload with a fresh random key, resulting in the ciphertext and the key material to be sent through the Double Ratchet.
The key material is the concatenation of the 32 bytes key and the ciphertext's 16 bytes authentication tag.
This is exported so that callers needing OMEMO's multi-recipient fan-out - one shared payload ciphertext, individually wrapped per recipient device via that device's own Double Ratchet - can encrypt the payload once and pass the resulting key material to each device's DoubleRatchet.Encrypt in turn, instead of going through the single-peer Session type.
func EncryptPayloadV1 ¶ added in v0.1.0
EncryptPayloadV1 encrypts plaintext with a fresh AES-128-GCM key/IV pair, as legacy OMEMO (eu.siacs.conversations.axolotl) demands.
The returned keyMaterial (key || GCM tag) is what must be sent through the Double Ratchet; iv is sent unencrypted in the wire's <iv/> element; ciphertext is the wire's <payload/> content, both base64 encoded by the caller.
Types ¶
type Session ¶
type Session struct {
// IdentityKey is this node's private Ed25519 identity key.
//
// This will only be used within the X3DH key agreement protocol. The other
// party might want to verify this key's public part.
IdentityKey ed25519.PrivateKey
// VerifyPeer is a callback during session initialization to verify the
// other party's public key.
//
// To determine when a key is correct is out of Xochimilco's scope. The key
// might be either exchanged over another secure channel or a trust on first
// use (TOFU) principle might be used.
VerifyPeer func(peer ed25519.PublicKey) (valid bool)
// contains filtered or unexported fields
}
Session between two parties to exchange encrypted messages.
Each party creates a new Session variable configured with their private long time identity key and a function callback to verify the other party's public identity key.
The active party must start by offering to "upgrade" the current channel (Offer). Afterwards, the other party must confirm this step (Acknowledge). Once the first party finally receives the acknowledgement (Receive), the connection is established.
Now both parties can create encrypted messages directed to the other (Send). Furthermore, the Session can be closed again (Close). Incoming messages can be inspected and the payload extracted, if present (Receive).
func (*Session) Acknowledge ¶
Acknowledge to establish an encrypted Session.
This method MUST be called by the passive party (Bob) with the active party's (Alice's) offer message. The created acknowledge message MUST be send back.
At this point, this passive part is able to send and receive messages.
func (*Session) Close ¶
Close this Session and tell the other party to do the same.
This resets the internal state. Thus, the same Session might be reused.
func (*Session) Offer ¶
Offer to establish an encrypted Session.
This method MUST be called initially by the active resp. opening party (Alice) once. The other party will hopefully Acknowledge this message.
func (*Session) Receive ¶
func (sess *Session) Receive(msg string) (isEstablished, isClosed bool, plaintext []byte, err error)
Receive an incoming message.
All messages except the passive party's initial offer message MUST be passed to this method. The multiple return fields indicate this message's kind.
If the active party receives its first (acknowledge) message, this Session will be established; isEstablished. If the other party has signaled to close the Session, isClosed is set. This Session MUST then also be closed down. In case of an incoming encrypted message, the plaintext field holds its decrypted plaintext value. Of course, there might also be an error.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package doubleratchet implements the Double Ratchet Algorithm as profiled by OMEMO, XEP-0384 version 0.9[1].
|
Package doubleratchet implements the Double Ratchet Algorithm as profiled by OMEMO, XEP-0384 version 0.9[1]. |
|
internal
|
|
|
Package legacysignal implements the pre-standardization "legacy OMEMO" wire protocol (eu.siacs.conversations.axolotl), as actually spoken by real clients (Conversations, Dino, Gajim, ChatSecure) - not the conceptually-similar but byte-incompatible XEP-0384 (OMEMO 2) profile implemented by xochimilco's x3dh/doubleratchet packages.
|
Package legacysignal implements the pre-standardization "legacy OMEMO" wire protocol (eu.siacs.conversations.axolotl), as actually spoken by real clients (Conversations, Dino, Gajim, ChatSecure) - not the conceptually-similar but byte-incompatible XEP-0384 (OMEMO 2) profile implemented by xochimilco's x3dh/doubleratchet packages. |
|
Package x3dh implements the Extended Triple Diffie-Hellman (X3DH) key agreement protocol as profiled by OMEMO, XEP-0384 version 0.9[4].
|
Package x3dh implements the Extended Triple Diffie-Hellman (X3DH) key agreement protocol as profiled by OMEMO, XEP-0384 version 0.9[4]. |