sealedsender

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package sealedsender implements Signal's sealed sender certificates and message content (the UnidentifiedSenderMessageContent, "USMC"), a pure-Go port of rust/protocol/src/sealed_sender.rs validated against upstream libsignal v0.91.0.

A ServerCertificate binds a server signing key to a key id and is signed by the trust root. A SenderCertificate binds a sender's identity key + address to an expiration and is signed by a ServerCertificate's key (the chain: trust-root -> server cert -> sender cert). USMC wraps an inner ciphertext with its sender certificate, message type, content hint, and optional group id for the sealed-sender encryption layer (added in a later task).

Example (SealedSender)

Example_sealedSender shows the sealed-sender v1 flow: a sender holding a certificate chain (sender cert signed by a server cert, which is signed by the trust root) wraps a message so only the recipient learns who sent it, then the recipient decrypts and validates the sender's certificate against the trust root.

package main

import (
	"crypto/rand"
	"fmt"
	"time"

	"github.com/GoCodeAlone/libsignal-go/curve"
	"github.com/GoCodeAlone/libsignal-go/protocol"
	"github.com/GoCodeAlone/libsignal-go/sealedsender"
)

func main() {
	// Long-lived identities. In production these come from key stores; here they
	// are generated for the example. genKey panics on a key-generation error so
	// the example handles every error rather than discarding it.
	genKey := func() curve.KeyPair {
		kp, err := curve.GenerateKeyPair(rand.Reader)
		if err != nil {
			panic(err)
		}
		return kp
	}
	trustRoot := genKey()
	serverKey := genKey()
	senderIdentity := genKey()
	recipientIdentity := genKey()

	// The server certificate is signed by the trust root; the sender certificate
	// is signed by the server, binding the sender's identity + UUID + device.
	server, err := sealedsender.NewServerCertificate(1, serverKey.PublicKey, trustRoot.PrivateKey, rand.Reader)
	if err != nil {
		panic(err)
	}
	expires := time.UnixMilli(2_000_000_000_000).UTC()
	senderCert, err := sealedsender.NewSenderCertificate(
		"sender-uuid", nil, senderIdentity.PublicKey, 1, expires, server, serverKey.PrivateKey, rand.Reader)
	if err != nil {
		panic(err)
	}

	// The unidentified sender message content wraps the actual ciphertext plus
	// routing metadata (message type, sender cert, content hint, optional group).
	usmc, err := sealedsender.NewUnidentifiedSenderMessageContent(
		protocol.MessageTypeWhisper, senderCert, []byte("sealed hello"), sealedsender.ContentHintDefault, nil)
	if err != nil {
		panic(err)
	}

	// The sender seals to the recipient's identity public key.
	sealed, err := sealedsender.SealV1(usmc, senderIdentity, recipientIdentity.PublicKey, rand.Reader)
	if err != nil {
		panic(err)
	}

	// The recipient decrypts with its own identity and validates the sender's
	// certificate chain against the trust root at the current time.
	got, err := sealedsender.DecryptToUSMCAndValidate(sealed, recipientIdentity, trustRoot.PublicKey, expires.Add(-time.Hour))
	if err != nil {
		panic(err)
	}

	fmt.Printf("%s from %s\n", got.Contents(), got.Sender().SenderUUID())
}
Output:
sealed hello from sender-uuid

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	// ErrInvalidCertificate is returned when certificate bytes are structurally
	// invalid: bad protobuf, a missing required field, or unusable key material.
	ErrInvalidCertificate = errors.New("sealedsender: invalid certificate")
	// ErrExpiredCertificate marks an expired-but-well-formed sender certificate.
	// SenderCertificate.Validate reports overall validity via its bool result
	// (returning false when expired, matching upstream), and additionally exposes
	// IsExpired for callers that want to distinguish expiry from a bad signature;
	// this typed error is what IsExpired-aware callers can wrap/match.
	ErrExpiredCertificate = errors.New("sealedsender: certificate expired")
	// ErrUnknownServerCertificateID is returned for a SenderCertificate that
	// references its signing ServerCertificate by id (the space-saving "known
	// certificate" form) rather than embedding it. The known-certificate table is
	// not carried in this package yet (it arrives with the sealed-sender v1/v2
	// encrypt/decrypt layer); only embedded signer certificates are resolvable
	// here. Mirrors upstream's UnknownSealedSenderServerCertificateId.
	ErrUnknownServerCertificateID = errors.New("sealedsender: unknown server certificate id")
)

Errors returned by certificate parsing and validation. All are %w-wrappable so callers can match with errors.Is.

View Source
var (
	// ErrInvalidSealedSenderMessage is returned for a structurally invalid sealed
	// sender message: empty input, an unknown version, bad protobuf/framing, or
	// unusable embedded key material.
	ErrInvalidSealedSenderMessage = errors.New("sealedsender: invalid sealed sender message")
	// ErrUnknownVersion is returned when the message's version byte names a
	// sealed-sender major version this implementation does not support.
	ErrUnknownVersion = errors.New("sealedsender: unknown sealed sender version")
	// ErrBadCiphertext is returned when authenticated decryption fails: a bad
	// AES-CTR+HMAC tag (v1) or a bad AES-256-GCM-SIV tag (v2), or a truncated
	// ciphertext.
	ErrBadCiphertext = errors.New("sealedsender: ciphertext authentication failed")
)

Errors returned by sealed-sender message decryption.

View Source
var ErrInvalidUSMC = errors.New("sealedsender: invalid unidentified sender message content")

ErrInvalidUSMC is returned when UnidentifiedSenderMessageContent bytes are structurally invalid (bad protobuf, missing required field, unknown message type, or an unparseable embedded sender certificate).

Functions

func SealV1

func SealV1(usmc *UnidentifiedSenderMessageContent, ourIdentity curve.KeyPair, theirIdentity curve.PublicKey, rng io.Reader) ([]byte, error)

SealV1 produces a sealed sender v1 message for a single recipient from a USMC. It generates a fresh ephemeral key, derives the ephemeral keys against the recipient's identity public key, AES-CTR+HMAC-encrypts the sender's identity public key (the "encrypted static"), derives the static keys, AES-CTR+HMAC- encrypts the USMC bytes, and frames the result as 0x11 || proto{ephemeral_public, encrypted_static, encrypted_message}. Mirrors sealed_sender_encrypt_from_usmc.

ourIdentity is the sender's identity key pair; theirIdentity is the recipient's identity public key; rng supplies the ephemeral key and must be a CSPRNG (crypto/rand.Reader) in production.

Types

type ContentHint

type ContentHint uint32

ContentHint advises the recipient how to handle a decryption failure for a sealed-sender message. It mirrors the ContentHint enum in sealed_sender.rs. The zero value is ContentHintDefault, which is omitted on the wire (a sealed sender will not resend; an error should be shown immediately).

const (
	// ContentHintDefault is the wire-absent default: do not resend; show an
	// error immediately. It encodes to a missing contentHint field.
	ContentHintDefault ContentHint = 0
	// ContentHintResendable means the sender will try to resend; the recipient
	// should delay error UI if possible. Proto value 1.
	ContentHintResendable ContentHint = 1
	// ContentHintImplicit means do not show any error UI; the message was sent
	// implicitly (e.g. a typing indicator or receipt). Proto value 2.
	ContentHintImplicit ContentHint = 2
)

func (ContentHint) String

func (h ContentHint) String() string

String renders the known hints by name and any other value as Unknown(n), matching upstream's Default/Resendable/Implicit/Unknown(value) variants.

type SealV2Recipient

type SealV2Recipient struct {
	ServiceID      address.ServiceID
	IdentityKey    curve.PublicKey
	DeviceID       uint32
	RegistrationID uint32
}

SealV2Recipient identifies one recipient of a multi-recipient v2 message: its ServiceId, identity public key, and per-device registration ids. A recipient with multiple devices repeats the same identity key; one PerRecipientData block is emitted per ServiceId with a device list.

type SealV2SentMessage

type SealV2SentMessage struct {
	// contains filtered or unexported fields
}

SealV2SentMessage is a sealed sender v2 multi-recipient SENT message: the flat server-bound wire form carrying every recipient's per-recipient block plus the single shared ciphertext. ReceivedMessageForRecipient fans it out to the per-recipient RECEIVED form that DecryptToUSMC consumes.

func SealV2

func SealV2(usmc *UnidentifiedSenderMessageContent, recipients []SealV2Recipient, ourIdentity curve.KeyPair, rng io.Reader) (*SealV2SentMessage, error)

SealV2 produces a sealed sender v2 multi-recipient SentMessage from a USMC for the given recipients. It generates one shared random seed M, derives E and K, AES-256-GCM-SIV-encrypts the USMC once under K, and for each recipient device emits C_i = M ⊕ HKDF(DH(E, R_i)…) and AT_i over the sender/recipient identity ECDH. Recipients are grouped by ServiceId (contiguous) into PerRecipientData blocks with a device list. Mirrors sealed_sender_multi_recipient_encrypt.

ourIdentity is the sender's identity key pair; rng must be a CSPRNG. The result is fanned out per recipient via ReceivedMessageForRecipient.

func (*SealV2SentMessage) ReceivedMessageForRecipient

func (s *SealV2SentMessage) ReceivedMessageForRecipient(i int) ([]byte, error)

ReceivedMessageForRecipient builds the per-recipient v2 ReceivedMessage form (0x22 || C_i || AT_i || E_pub || ciphertext) for the recipient at index i in the recipients slice passed to SealV2. This is what the server would route to that recipient and what DecryptToUSMC consumes.

func (*SealV2SentMessage) Serialized

func (s *SealV2SentMessage) Serialized() []byte

Serialized returns the full SentMessage wire form (server-bound).

type SenderCertificate

type SenderCertificate struct {
	// contains filtered or unexported fields
}

SenderCertificate binds a sender's identity key and address (uuid, optional e164, device id) to an expiration, signed by a ServerCertificate's key. Mirrors SenderCertificate in sealed_sender.rs.

func DeserializeSenderCertificate

func DeserializeSenderCertificate(data []byte) (*SenderCertificate, error)

DeserializeSenderCertificate parses a serialized SenderCertificate: the outer wrapper, then the inner Certificate (sender address, identity key, expiration, and the signer — an embedded ServerCertificate or a reference id). A uuid is accepted as a string or as 16 raw bytes (rendered to canonical string form). No signature/expiry check is done — call Validate. Fails with ErrInvalidCertificate on any missing field or bad material.

func NewSenderCertificate

func NewSenderCertificate(
	senderUUID string,
	senderE164 *string,
	key curve.PublicKey,
	senderDeviceID uint32,
	expiration time.Time,
	signer *ServerCertificate,
	signerKey curve.PrivateKey,
	rng io.Reader,
) (*SenderCertificate, error)

NewSenderCertificate builds and signs a SenderCertificate with an embedded signer ServerCertificate: it encodes the inner Certificate, signs it with signerKey (the server's private key matching signer.PublicKey, XEdDSA nonce from rng), and assembles the wire form. expiration is stored to millisecond granularity (the proto's fixed64). Mirrors SenderCertificate::new.

func (*SenderCertificate) Certificate

func (c *SenderCertificate) Certificate() []byte

Certificate returns the serialized inner Certificate (the signed bytes).

func (*SenderCertificate) Expiration

func (c *SenderCertificate) Expiration() time.Time

Expiration returns the certificate's expiration time (millisecond precision).

func (*SenderCertificate) IsExpired

func (c *SenderCertificate) IsExpired(now time.Time) (bool, error)

IsExpired reports whether the certificate is expired at now and, when it is, returns a wrapped ErrExpiredCertificate describing the times. now is strictly compared against the expiration, mirroring Validate's expiry check (`validation_time > expiration`). Validate already folds expiry into its bool result; IsExpired lets a caller tell expiry apart from a signature failure.

func (*SenderCertificate) Key

Key returns the sender's identity public key.

func (*SenderCertificate) SenderDeviceID

func (c *SenderCertificate) SenderDeviceID() uint32

SenderDeviceID returns the sender's device id.

func (*SenderCertificate) SenderE164

func (c *SenderCertificate) SenderE164() (string, bool)

SenderE164 returns the sender's optional e164 phone number, or (\"\", false).

func (*SenderCertificate) SenderUUID

func (c *SenderCertificate) SenderUUID() string

SenderUUID returns the sender's uuid (canonical string form).

func (*SenderCertificate) Serialized

func (c *SenderCertificate) Serialized() []byte

Serialized returns the full serialized SenderCertificate wire form.

func (*SenderCertificate) Signature

func (c *SenderCertificate) Signature() []byte

Signature returns the server's signature over the inner certificate.

func (*SenderCertificate) Signer

func (c *SenderCertificate) Signer() *ServerCertificate

Signer returns the embedded signing ServerCertificate, or nil when the signer is referenced by id (see SignerID).

func (*SenderCertificate) SignerID

func (c *SenderCertificate) SignerID() (uint32, bool)

SignerID returns the referenced signer key id and true when the signer is referenced rather than embedded, else (0, false).

func (*SenderCertificate) Validate

func (c *SenderCertificate) Validate(trustRoot curve.PublicKey, opts ...ValidateOption) (bool, error)

Validate reports whether the certificate chain is valid against trustRoot:

  1. the signer ServerCertificate validates under trustRoot (and is embedded — a reference-by-id signer returns ErrUnknownServerCertificateID, since the known-certificate table is not carried here yet),
  2. the sender certificate's signature verifies under the signer's key, and
  3. the validation time is not past the expiration.

A well-formed but invalid chain (bad signature, expired) returns (false, nil); only an unresolvable signer reference returns a non-nil error. Mirrors SenderCertificate::validate. The validation clock is time.Now() unless overridden with WithClock.

type ServerCertificate

type ServerCertificate struct {
	// contains filtered or unexported fields
}

ServerCertificate is a server signing key (with its key id) signed by the trust root. Mirrors ServerCertificate in sealed_sender.rs.

func DeserializeServerCertificate

func DeserializeServerCertificate(data []byte) (*ServerCertificate, error)

DeserializeServerCertificate parses a serialized ServerCertificate. It decodes the outer wrapper, then the inner Certificate (key id + key), and fails with ErrInvalidCertificate on any missing field or bad key material. It performs no signature check — call Validate for that.

func NewServerCertificate

func NewServerCertificate(keyID uint32, key curve.PublicKey, trustRoot curve.PrivateKey, rng io.Reader) (*ServerCertificate, error)

NewServerCertificate builds and signs a ServerCertificate: it encodes the inner Certificate (keyID + key), signs it with the trust-root private key (XEdDSA, nonce drawn from rng), and assembles the serialized wrapper. Mirrors ServerCertificate::new.

func (*ServerCertificate) Certificate

func (c *ServerCertificate) Certificate() []byte

Certificate returns the serialized inner Certificate (the signed bytes).

func (*ServerCertificate) KeyID

func (c *ServerCertificate) KeyID() uint32

KeyID returns the server certificate's key id.

func (*ServerCertificate) PublicKey

func (c *ServerCertificate) PublicKey() curve.PublicKey

PublicKey returns the server's signing public key.

func (*ServerCertificate) Serialized

func (c *ServerCertificate) Serialized() []byte

Serialized returns the full serialized ServerCertificate wire form.

func (*ServerCertificate) Signature

func (c *ServerCertificate) Signature() []byte

Signature returns the trust-root signature over the inner certificate.

func (*ServerCertificate) Validate

func (c *ServerCertificate) Validate(trustRoot curve.PublicKey) bool

Validate reports whether the certificate is signed by trustRoot. A revoked key id is rejected (returns false) regardless of signature, mirroring ServerCertificate::validate. Signature failure also returns false (not an error); a malformed certificate cannot reach here (deserialize validates).

type UnidentifiedSenderMessageContent

type UnidentifiedSenderMessageContent struct {
	// contains filtered or unexported fields
}

UnidentifiedSenderMessageContent (USMC) is the inner payload of a sealed sender message: the wrapped ciphertext (contents) plus the metadata needed to route and handle it — the message type, the sender's certificate, a content hint, and an optional group id. Mirrors UnidentifiedSenderMessageContent in sealed_sender.rs. The sealed-sender encryption that wraps this (v1/v2) is a later task; this type is the serialize/deserialize boundary.

func DecryptToUSMC

func DecryptToUSMC(message []byte, ourIdentity curve.KeyPair) (*UnidentifiedSenderMessageContent, error)

DecryptToUSMC decrypts a sealed sender message (v1 or v2 received form) with the recipient's identity key pair and returns the recovered UnidentifiedSenderMessageContent WITHOUT validating its sender certificate — the caller must validate Sender() against a trust root (see DecryptToUSMCAndValidate). The version is taken from the high nibble of the leading byte; v0 is accepted as v1, matching upstream's lenient v1 path. Mirrors sealed_sender_decrypt_to_usmc.

func DecryptToUSMCAndValidate

func DecryptToUSMCAndValidate(message []byte, ourIdentity curve.KeyPair, trustRoot curve.PublicKey, validationTime time.Time) (*UnidentifiedSenderMessageContent, error)

DecryptToUSMCAndValidate decrypts a sealed sender message and then validates the recovered sender certificate against trustRoot (chain + expiry), returning the USMC only when the certificate is valid. validationTime is the clock used for the expiry check. This is the typical recipient entry point: decryption proves the message was sealed to us; certificate validation proves the claimed sender is authorized.

func DeserializeUnidentifiedSenderMessageContent

func DeserializeUnidentifiedSenderMessageContent(data []byte) (*UnidentifiedSenderMessageContent, error)

DeserializeUnidentifiedSenderMessageContent parses a serialized USMC: it decodes the proto, resolves the message type, parses the embedded sender certificate, and reads the content hint (absent => Default) and optional group id. Fails with ErrInvalidUSMC on a missing required field or unknown type, and surfaces certificate-parse failures. No signature/expiry validation is done here — validate the returned Sender() against a trust root separately.

func NewUnidentifiedSenderMessageContent

func NewUnidentifiedSenderMessageContent(
	msgType uint8,
	sender *SenderCertificate,
	contents []byte,
	contentHint ContentHint,
	groupID []byte,
) (*UnidentifiedSenderMessageContent, error)

NewUnidentifiedSenderMessageContent assembles a USMC and serializes it. An empty (or nil) groupID is omitted from the wire form, matching upstream (a zero-length group id encodes to a missing field). msgType must be one of the protocol.MessageType* tags.

func (*UnidentifiedSenderMessageContent) ContentHint

ContentHint returns the content hint (ContentHintDefault when none was set).

func (*UnidentifiedSenderMessageContent) Contents

func (u *UnidentifiedSenderMessageContent) Contents() []byte

Contents returns the wrapped ciphertext bytes.

func (*UnidentifiedSenderMessageContent) GroupID

func (u *UnidentifiedSenderMessageContent) GroupID() ([]byte, bool)

GroupID returns the optional group id and whether one is present (an empty group id is treated as absent).

func (*UnidentifiedSenderMessageContent) MessageType

func (u *UnidentifiedSenderMessageContent) MessageType() uint8

MessageType returns the wrapped ciphertext's protocol.MessageType* tag.

func (*UnidentifiedSenderMessageContent) Sender

Sender returns the sender's certificate.

func (*UnidentifiedSenderMessageContent) Serialized

func (u *UnidentifiedSenderMessageContent) Serialized() []byte

Serialized returns the full serialized USMC wire form.

type ValidateOption

type ValidateOption func(*validateConfig)

ValidateOption configures Validate (currently only the validation clock).

func WithClock

func WithClock(now time.Time) ValidateOption

WithClock overrides the validation time used for the expiration check. When unset, Validate uses time.Now(). Injecting a clock makes expiry deterministic in tests.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL