package libsignal /* #include #include #include #include */ import "C" import ( "runtime" "unsafe" ) // Protocol version constants (protocol.h's CIPHERTEXT_CURRENT_VERSION / // CIPHERTEXT_OMEMO_VERSION), selecting legacy siacs OMEMO vs modern OMEMO. // Modern is shared by OMEMO 1 (urn:xmpp:omemo:1) and OMEMO 2 // (urn:xmpp:omemo:2) - those two differ only in outer SCE stanza framing, // not in this underlying Double Ratchet/X3DH wire format. const ( ProtocolVersionV3 = 3 ProtocolVersionV4 = 4 ) // RemoteBundle is the decoded material from a peer's XEP-0384 device // bundle, as needed to establish a session with one of their devices via // X3DH. type RemoteBundle struct { RegistrationID uint32 DeviceID uint32 PreKeyID uint32 // 0 if the bundle carried no one-time prekey PreKeyPublic []byte // nil if PreKeyID == 0 SignedPreKeyID uint32 SignedPreKeyPublic []byte SignedPreKeySignature []byte // see SignedPreKeyInfo: pick Signature or SignatureOMEMO to match the version this bundle is processed with IdentityKeyPublic []byte } // SessionBuilder establishes (or refreshes) a Double Ratchet session with // one remote device. type SessionBuilder struct { raw *C.session_builder addr *cAddress } // NewSessionBuilder creates a session builder for storeCtx's local identity // talking to remote. version controls the protocol version new sessions // built by ProcessPreKeyBundle will use for encryption going forward // (ProtocolVersionV3 or ProtocolVersionV4). func NewSessionBuilder(ctx *Context, storeCtx *StoreContext, remote Address, version int) (*SessionBuilder, error) { addr := newCAddress(remote) var raw *C.session_builder if code := C.session_builder_create(&raw, storeCtx.raw, addr.ptr, ctx.raw); code != C.SG_SUCCESS { addr.free() return nil, newError("session_builder_create", int(code)) } if err := setBuilderVersion(raw, version); err != nil { C.session_builder_free(raw) addr.free() return nil, err } return &SessionBuilder{raw: raw, addr: addr}, nil } // Close frees the session builder. Do not use it afterward. func (b *SessionBuilder) Close() { if b.raw != nil { C.session_builder_free(b.raw) b.raw = nil } b.addr.free() } // ProcessPreKeyBundle establishes a session from a remote peer's fetched // device bundle (X3DH). // // IsUntrustedIdentity(err) reports the case where the remote's identity key // does not match a previously trusted one for this device id - under a // TOFU trust policy this is the one case that must NOT be silently // re-trusted (it is what TOFU exists to catch: either genuine key rotation // or an impersonation attempt), unlike a first-ever sighting of a device, // which the identity store's IsTrustedIdentity should accept. func (b *SessionBuilder) ProcessPreKeyBundle(ctx *Context, bundle RemoteBundle) error { identityKey, err := decodePoint(ctx, bundle.IdentityKeyPublic) if err != nil { return err } defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(identityKey))) signedPreKeyPublic, err := decodePoint(ctx, bundle.SignedPreKeyPublic) if err != nil { return err } defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(signedPreKeyPublic))) var preKeyPublic *C.ec_public_key if len(bundle.PreKeyPublic) > 0 { preKeyPublic, err = decodePoint(ctx, bundle.PreKeyPublic) if err != nil { return err } defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(preKeyPublic))) } sigPtr, sigLen := cBytesPtrLen(bundle.SignedPreKeySignature) var cBundle *C.session_pre_key_bundle code := C.session_pre_key_bundle_create(&cBundle, C.uint32_t(bundle.RegistrationID), C.int(bundle.DeviceID), C.uint32_t(bundle.PreKeyID), preKeyPublic, C.uint32_t(bundle.SignedPreKeyID), signedPreKeyPublic, sigPtr, sigLen, identityKey) runtime.KeepAlive(bundle.SignedPreKeySignature) if code != C.SG_SUCCESS { return newError("session_pre_key_bundle_create", int(code)) } defer C.signal_type_unref((*C.signal_type_base)(unsafe.Pointer(cBundle))) if code := C.session_builder_process_pre_key_bundle(b.raw, cBundle); code != C.SG_SUCCESS { return newError("session_builder_process_pre_key_bundle", int(code)) } return nil } func decodePoint(ctx *Context, data []byte) (*C.ec_public_key, error) { var key *C.ec_public_key ptr, length := cBytesPtrLen(data) code := C.curve_decode_point(&key, ptr, length, ctx.raw) runtime.KeepAlive(data) if code != C.SG_SUCCESS { return nil, newError("curve_decode_point", int(code)) } return key, nil } func cBytesPtrLen(b []byte) (*C.uint8_t, C.size_t) { if len(b) == 0 { return nil, 0 } return (*C.uint8_t)(unsafe.Pointer(&b[0])), C.size_t(len(b)) }