saltyim

package module
v0.0.0-...-15a64de Latest Latest
Warning

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

Go to latest
Published: Jan 28, 2023 License: MIT Imports: 40 Imported by: 5

README

Salty IM -- secure, easy, self-hosted messaging

Build Status Go Report Card Go Reference

SaltyIM Logo

Salty IM is a secure, decentrlaised and easy self-hosted instant messaging alternative. Salty IM is fully encrypted using the salty Go library utilising the Saltypack messaging format and parts of keys.pub implemtnation for its cryptography. Encryption, Decryption and Signing of messages is performed with public key cryptography using the Ed25519 algoritms.

For more information on how the protocol works, please refer to the Specification.

This repository also contains a reference client (written in Go), a reference broker (server) as well as a Terminal TUI (TUI) client called salty-chat and command-line tools:

  • saltyd -- Reference broker (server)
  • salty-chat -- Command-line tools and Terminal UI (TUI)
  • saltyim -- Reference client (Go library)

Quick Start CLI

$ go install go.mills.io/saltyim/cmd/salty-chat@latest
$ salty-chat make-user user@domain
# Follow the insturctions
$ salty-chat chat prologic@mills.io

Alternatively follow the full documentation below to setup your own Salty broker and your domain name.

Quick Start (DEV)

Documentation

See also the Old Readme for how this implementation started out as a simple shell script which you can still find at salty-chat.sh.

Roadmap

Please refer to the Roadmap document.

License

saltyd and salty-chat are licensed under the terms of the MIT license.

Documentation

Overview

Package saltyim is a secure, easy, self-hosted messaging

salty.im is an open specification for a new Saltpack based e2e encrypted messaging protocol and platform for secure communications with a focus on privacy, security and being self-hosted.

For more information please refer to https://salty.im/

Index

Constants

View Source
const (
	// DateTimeFormat is the default date and time format used for displaying messages
	DateTimeFormat = "2006-01-02 15:04:05"
)
View Source
const (
	// DefaultEnvPath is the default PATH for pre and post hooks that are shelled out to
	DefaultEnvPath = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
)

Variables

View Source
var (
	// ErrNoMessages is an error returned when there are no further messages found for an inbox from the broker
	ErrNoMessages = errors.New("error: no messages found")

	// ErrNoSender is an error returned when the client is not properly configured with a valid sender
	ErrNoSender = errors.New("error: no sender configured")

	// ErrMissingIdentity is an error returned when the client is not properly configured with a valid identity
	ErrMissingIdentity = errors.New("error: missing identity")
)
View Source
var (
	// Version is the tagged release version in the form <major>.<minor>.<patch>
	// following semantic versioning and is overwritten by the build system.
	Version = defaultVersion

	// Commit is the commit sha of the build (normally from Git) and is overwritten
	// by the build system.
	Commit = defaultCommit

	// Build is the date and time of the build as an RFC3339 formatted string
	// and is overwritten by the build system.
	Build = defaultBuild
)

Functions

func DefaultIdentity

func DefaultIdentity() string

DefaultIdentity returns a default identity file (if one exists) otherwise returns an empty string

func DefaultState

func DefaultState() string

DefaultState returns a default state file

func FixUnixHome

func FixUnixHome(p string) string

FixUnixHome handles paths with a UNIX Home (i.e: ~)

func FormatMessage

func FormatMessage(msg string) string

FormatMessage formats the msg for display on the terminal

func FullVersion

func FullVersion() string

FullVersion display the full version and build

func GenerateULID

func GenerateULID() (string, error)

GenerateULID generates a new unique identifer

func MustGenerateULID

func MustGenerateULID() string

MustGenerateULID generates a new unique identifer or fails

func NewRawRequest

func NewRawRequest(r io.Reader) (out []byte, signer string, err error)

NewRawRequest reads the signed request body from a client, verifies its signature and returns the resulting `[]byte` slice and key used to sign the request on success otherwise an empty object and an error on failure.

func PackMessage

func PackMessage(me Addr, msg string) []byte

PackMessage formats an outgoing message in the Message Format <timestamp>\t(<sender>) <message>

func PackMessageTime

func PackMessageTime(me Addr, msg string, t *lextwt.DateTime) []byte

PackMessageTime formats an incoming message in the Message Format using the existing timestamp <timestamp>\t(<sender>) <message>

func Request

func Request(method, uri string, headers http.Header, body io.Reader) (*http.Response, error)

Request is a generic request handling function for making artbitrary HTTP requests to Salty endpoints for looking up Salty Addresses, Configs and publishing encrypted messages.

func Send

func Send(endpoint, msg string, cap Capabilities) error

Send sends the encrypted message `msg` to the Endpoint `endpoint` using a `POST` request and returns nil on success or an error on failure.

func SignedRequest

func SignedRequest(key *keys.EdX25519Key, method, uri string, headers http.Header, body io.Reader) (*http.Response, error)

SignedRequest is a generic request handling function for making artbitrary HTPT requests to a Salty broker's API endpoints that require authorization.

func SplitInbox

func SplitInbox(endpoint string) (string, string)

SplitInbox splits and endpoint into it's components (inbox, uri) where inbox is a topic queue on the Salty broker uri

Types

type Addr

type Addr interface {
	fmt.Stringer
	json.Marshaler

	// IsZero returns true if the address is empty
	IsZero() bool

	// User returns the user part of the address user@domain
	User() string

	// Domain returns the domain part of the address user@domain
	Domain() string

	// Hash returns the Hex(SHA256Sum()) of the Address
	Hash() string

	// Formatted returns a formatted user used in the Salty Message Format
	// <timestamp>\t(<user>) <message>\n
	Formatted() string

	// Key returns the Public Key of this User (Salty Addr) as discovered
	Key() *keys.EdX25519PublicKey

	// Endpoint returns the discovered Endpoint
	Endpoint() *url.URL

	// Cap returns the discovered Capabilities
	Cap() Capabilities

	// DiscoveredDomain returns the discovered domain (if any) of fallbacks to the Domain
	DiscoveredDomain() string

	// URI returns the Well-Known URI for this Addr
	URI() string

	// HashURI returns the Well-Known HashURI for this Addr
	HashURI() string

	// Refresh forces a lookup and configuration fetch for a Salty Address
	Refresh() error

	// Avatar returns the cached avatar service for a Salty Address or performs a DNS lookup
	// for the avatar service to use and cached it (if found) and returns that. If there is no
	// avatar service found, then a default avatar is used for peers.
	Avatar() string

	// WithEndpoint returns a clone of this address with a different endpoint
	// which is mostly useful for sending messages to ourselves or an outbox
	WithEndpoint(endpoint *url.URL) Addr
}

Addr is a Salty Address in the form of user@domain and is used to discover a user's endpoint, domain, avatar service so we can send messages to.

func LookupAddr

func LookupAddr(addr string) (Addr, error)

LookupAddr looks up a Salty Address for a User by parsing the user's domain and making a request to the user's Well-Known URI expected to be located at https://domain/.well-known/salty/<user>.json

func ParseAddr

func ParseAddr(user string) (Addr, error)

ParseAddr parsers a Salty Address for a user into it's user and domain parts and returns an Addr object with the User and Domain and a method for returning the expected User's Well-Known URI

type AvatarRequest

type AvatarRequest struct {
	Addr    Addr
	Content []byte
}

AvatarRequest is the request used by clients to update avatars stored on a broker's avatar service.

func NewAvatarRequest

func NewAvatarRequest(r io.Reader) (req AvatarRequest, signer string, err error)

NewAvatarRequest reads the signed request body from a client, verifies its signature and returns the resulting `AvatarRequest` and key used to sign the request on success otherwise an empty object and en error on failure.

type Blob

type Blob struct {
	Type       string
	Public     bool
	Filename   string
	Properties map[string]string
	// contains filtered or unexported fields
}

Blob defines the type, filename and whether or not a blob is publicly accessible or not. A Blob also holds zero or more properties as a map of key/value pairs of string interpreted by the client.

func OpenBlob

func OpenBlob(fn string) (*Blob, error)

OpenBlob opens a blob at the given path and returns a Blob object

func (*Blob) Close

func (b *Blob) Close() error

Close closes the blob and the underlying io.ReadSeekCloser

func (*Blob) Read

func (b *Blob) Read(p []byte) (n int, err error)

Read reads data from the blob from the underlying io.ReadSeekCloser

func (*Blob) SetHeaders

func (b *Blob) SetHeaders(r *http.Request)

SetHeaders sets HTTP headers on the net/http.Request object based on the blob's type, filename and various other properties (if any).

type BlobOption

type BlobOption func(opts *BlobOptions) error

BlobOption is a function type that is used to configure blob options

func WithKey

func WithKey(key string) BlobOption

WithKey sets an explicit key (location) for the blob

type BlobOptions

type BlobOptions struct {
	// Key is the locaiton of the blob option, if omitted a hash of the contents is used as the key
	Key string

	// Type is the mimetype of the blob, if omitted the type is auto detected
	Type string

	// Public sets whether the blob is public or pviate (default)
	Public bool

	// Filename sets the blob's friendly filename (if omitted, no filename is used)
	Filename string

	// Properties sets optional properties of a blob which are up to clients to interpet
	Properties map[string]string
}

BlobOptions are various options that define a blob such its type, filename, whether it is public or not (private by default) and any optional properties that clients can interpret.

type BlobRequest

type BlobRequest struct {
	Blob
}

BlobRequest is the request used by clients to update blob metadata for blobs stored on a broker.

func NewBlobRequest

func NewBlobRequest(r io.Reader) (req BlobRequest, signer string, err error)

NewBlobRequest reads the signed request body from a client, verifies its signature and returns the resulting `BlobRequest` and key used to sign the request on success otherwise an empty object and an error on failure.

type BlobService

type BlobService interface {
	Write(r io.Reader, opts ...BlobOption) error
	Read(key string) ([]byte, error)
}

BlobService allows a client to store, retrieve and delete blobs of data as well as store properites for a blob that are useful to other clients or users receiving the blob.

type Capabilities

type Capabilities struct {
	AcceptEncoding string
}

Capabilities defines optional capabilities of a client

func (Capabilities) String

func (c Capabilities) String() string

String implements the fmt.Stringer interface and formats capabilities as HTTP headers

type Client

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

Client is a Salty IM client that handles talking to a Salty IM Broker and Sedngina and Receiving messages to/from Salty IM Users.

func NewClient

func NewClient(options ...ClientOption) (*Client, error)

NewClient reeturns a new Salty IM client for sending and receiving encrypted messages to other Salty IM users as well as decrypting and displaying messages of the user's own inbox.

func (*Client) Blobs

func (cli *Client) Blobs() BlobService

Blobs returns an object that implements the BlobService which uses this client's identity and the broker this client is connected to (normally an instance of `saltyd`).

func (*Client) Env

func (cli *Client) Env(extraenvs string) []string

Env sets up a sensible (and hopefully secure) environment for running pre and post hooks Extra environment variables are parsed from extraenvs and some default variables injected into the new environment such as PATH, PWD and HOME as well as the current user's Salty address (SALTY_USER) and their public key (SALTY_IDENTITY).

func (*Client) Key

func (cli *Client) Key() *keys.EdX25519PublicKey

Key returns our (self) public key

func (*Client) Lookup

func (cli *Client) Lookup(user string) (Addr, error)

Lookup performs a lookup for a user's address and config If the user has an address already cached, the cached addr is returned, otherwise a full lookup is done.

func (*Client) Me

func (cli *Client) Me() Addr

Me returns our (self) address

func (*Client) Outbox

func (cli *Client) Outbox() *url.URL

Outbox returns the URL of our (self) outbox for sending copies of our outgoing messages to which is later used by the client as a way to track messages sent.

func (*Client) OutboxAddr

func (cli *Client) OutboxAddr(to Addr) Addr

OutboxAddr returns the address of our (self) outbox

func (*Client) OutboxClient

func (cli *Client) OutboxClient(to Addr) *Client

OutboxClient returns a modified client for our (self) outbox

func (*Client) Read

func (cli *Client) Read(options ...ReadOption) (Message, error)

Read reads a single message from this user's inbox

func (*Client) Register

func (cli *Client) Register(brokerURI string) error

Register sends a registration request to the service user of a Salty Broker

func (*Client) Request

func (cli *Client) Request(method, endpoint string, body []byte) ([]byte, error)

Request makes a signed request to a broker's API.

func (*Client) Send

func (cli *Client) Send(user, msg string) error

Send sends an encrypted message to the specified user

func (*Client) SendToAddr

func (cli *Client) SendToAddr(addr Addr, msg string) error

SendToAddr encrypts and sends the message to a specified address

func (*Client) SetAvatar

func (cli *Client) SetAvatar(content []byte) error

SetAvatar creates or updates an avatar for a user with a broker

func (*Client) SetLookup

func (cli *Client) SetLookup(lookup Lookuper)

SetLookup sets the internal lookup interface to use (Lookuper) for looking up Salty Addresses. By default the DirectLookup implementation is used.

func (*Client) SetSend

func (cli *Client) SetSend(send Sender)

SetSend sets the internal send interface to use (Sender) for sending messages to endpoints. By default the DirectSend implementation is used.

func (*Client) State

func (cli *Client) State() *State

State returns the current state of the client

func (*Client) String

func (cli *Client) String() string

String implements the fmt.Stringer interface and outputs who we (self) are, what our endpoint is we're connected to (broker), our outbox and our public key.

func (*Client) Subscribe

func (cli *Client) Subscribe(ctx context.Context, options ...ReadOption) chan Message

Subscribe subscribers to this user's inbox for new messages

type ClientOption

type ClientOption func(cli *Client) error

ClientOption is a function that configures a client

func WithAddr

func WithAddr(me Addr) ClientOption

WithAddr sets the client's `me` Salty Address

func WithClientIdentity

func WithClientIdentity(options ...IdentityOption) ClientOption

WithClientIdentity sets the client's identity

func WithIdentity

func WithIdentity(id *Identity) ClientOption

WithIdentity sets the client's identity from an identity object

func WithState

func WithState(state *State) ClientOption

WithState sets the client's state from a state object

func WithStateFromBytes

func WithStateFromBytes(data []byte) ClientOption

WithStateFromBytes sets the client's state from a byte array

func WithStateFromFile

func WithStateFromFile(fn string) ClientOption

WithStateFromFile sets the client's state from a file on disk

func WithUser

func WithUser(user string) ClientOption

WithUser sets the client's `me` Salty Address if a non-nil or non-empty and valid Salty Address for `user` is supplifed, otherwise the user in the client's identity is used.

type Config

type Config struct {
	Endpoint string `json:"endpoint"`
	Key      string `json:"key"`
}

Config represents a Salty Config for a User which at a minimum is required to have an Endpoint and Key (Public Key)

type DNSOverHTTPResolver

type DNSOverHTTPResolver struct{}

DNSOverHTTPResolver is a resolver that performs DNS queries using a DNS Over HTTP service where direct DNS queries are not possible (standard resolver).

func (*DNSOverHTTPResolver) LookupSRV

func (r *DNSOverHTTPResolver) LookupSRV(service, proto, domain string) (string, error)

LookupSRV implements the Resolver interface

type DirectLookup

type DirectLookup struct{}

DirectLookup performs a direct lookup request

func (*DirectLookup) LookupAddr

func (l *DirectLookup) LookupAddr(user string) (Addr, error)

LookupAddr performs a lookup of a Salty Addr directly

type DirectSend

type DirectSend struct{}

DirectSend performs a direct send request

func (*DirectSend) Send

func (s *DirectSend) Send(key *keys.EdX25519Key, endpoint, msg string, cap Capabilities) error

Send posts a message to an endpoint directly

type Identity

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

Identity allows interaction with CreateIdentity, GetIdentity, and NewClient

func CreateIdentity

func CreateIdentity(options ...IdentityOption) (*Identity, error)

CreateIdentity ...

func GetIdentity

func GetIdentity(options ...IdentityOption) (*Identity, error)

GetIdentity ...

func GetOrCreateIdentity

func GetOrCreateIdentity(options ...IdentityOption) (*Identity, error)

GetOrCreateIdentity ...

func (*Identity) Addr

func (i *Identity) Addr() Addr

Addr returns the Salty Address for this identity

func (*Identity) Contents

func (i *Identity) Contents() []byte

Contents returns the contents of the identity as a byte slice

func (*Identity) Key

func (i *Identity) Key() *keys.EdX25519Key

Key returns the full private and public Ed25519 key object for this identity

func (*Identity) Source

func (i *Identity) Source() string

Source returns the path of an identity or []byte it if was loaded from a byte string

type IdentityOption

type IdentityOption func(*Identity)

IdentityOption represents functional options for various identity operations

func WithIdentityAddr

func WithIdentityAddr(addr Addr) IdentityOption

WithIdentityAddr sets the identity Addr option

func WithIdentityBytes

func WithIdentityBytes(contents []byte) IdentityOption

WithIdentityBytes indicates that the identity should be read from a byte array

func WithIdentityPath

func WithIdentityPath(path string) IdentityOption

WithIdentityPath indicates that an identity should be read / written from a file path

type Lookuper

type Lookuper interface {
	LookupAddr(user string) (Addr, error)
}

Lookuper defines an interface for looking up Salty Addresses, primarily used by the PWA and possibly other clients, as a way to either perform direct lookups or to have lookups proxied through a broker.

type Message

type Message struct {
	Text string
	Key  *keys.EdX25519PublicKey
}

Message contains the plaintext (decrypted) message and the sender's public key

type MessageEventHandlerFunc

type MessageEventHandlerFunc func(context.Context, *Service, *keys.EdX25519PublicKey, *lextwt.SaltyEvent) error

MessageEventHandlerFunc defines a function type to handle an inbound event to a service

type MessageTextHandlerFunc

type MessageTextHandlerFunc func(context.Context, *Service, *keys.EdX25519PublicKey, *lextwt.SaltyText) error

MessageTextHandlerFunc defines a function type to handle an inbound message to a service

type ProxyLookup

type ProxyLookup struct {
	// LookupEndpoint is the uri of the lookup endpoint of a broker
	LookupEndpoint string
}

ProxyLookup proxies lookup requests through a Salty Broker's /api/v1/lookup endpoint

func (*ProxyLookup) LookupAddr

func (l *ProxyLookup) LookupAddr(user string) (Addr, error)

LookupAddr performs a lookup of a Salty Addr directly and if the request fails for whatever reason (usuaully due to Cross-Orogin-Resource-Sharing policies / CORS) it uses the Salty Broker the PWA was served from initially to perform the lookup by proxying the lookup through the broker. Why? CORS sucks.

type ProxySend

type ProxySend struct {
	// SendEndpoint is the uri of the send endpoint of a broker
	SendEndpoint string
}

ProxySend proxies send requests through a Salty Broker's /api/v1/send endpoint

func (*ProxySend) Send

func (s *ProxySend) Send(key *keys.EdX25519Key, endpoint, msg string, cap Capabilities) error

Send posts a message to an endpoint directly directly and if the request fails for whatever reason (usuaully due to Cross-Orogin-Resource-Sharing policies / CORS) it uses the Salty Broker the PWA was served from initially to perform the send by proxying the send request through the broker. Why? CORS sucks.

type ReadOption

type ReadOption func(opts *ReadOptions) error

ReadOption is a function that configures a client

func WithExtraEnvs

func WithExtraEnvs(extraenvs string) ReadOption

WithExtraEnvs sets extra environment variables for use by pre/post hooks

func WithPostHook

func WithPostHook(posthook string) ReadOption

WithPostHook sets the posthook used for processing incoming messages which is the path to a script that is passed the decrypted message as its standard input.

func WithPreHook

func WithPreHook(prehook string) ReadOption

WithPreHook sets the prehook used for processing incoming messages which is the path to a script that is passed the encrypted message payload as its standard input.

type ReadOptions

type ReadOptions struct {
	ExtraEnvs string
	PreHook   string
	PostHook  string
}

ReadOptions allows a client to read from its inbox and provide additional options for processing messages such as extra environment variables for pre/post hooks.

type RegisterRequest

type RegisterRequest struct {
	Hash string
	Key  string
}

RegisterRequest is the request used by clients to register to a broker

func NewRegisterRequest

func NewRegisterRequest(r io.Reader) (req RegisterRequest, signer string, err error)

NewRegisterRequest reads the signed request body from a client, verifies its signature and returns the resulting `RegisterRequest` and key used to sign the request on success otherwise an empty object and en error on failure.

type Resolver

type Resolver interface {
	LookupSRV(service, proto, domain string) (string, error)
}

Resolver is an interface for performing DNS lookups and is primarily used by the PWA and possibly other clients where direct DNS queries are not always possible and instead uses DNS over HTTP to eprform the same functionality.

var (

	// ErrSRVRecordNotFound is an error returned by a resolver when there is no SRV record found for the domain of a Salty Address
	ErrSRVRecordNotFound = errors.New("error: No SRV records found")

	// DefaultResolver is the default resolver which defaults to the StandardResolver
	DefaultResolver Resolver = &StandardResolver{}
)

type SendRequest

type SendRequest struct {
	Endpoint     string
	Message      string
	Capabilities Capabilities
}

SendRequest is the request used by clients to send messages via a broker

func NewSendRequest

func NewSendRequest(r io.Reader) (req SendRequest, signer string, err error)

NewSendRequest reads the signed request body from a client, verifies its signature and returns the resulting `SendRequest` and key used to sign the request on success otherwise an empty object and en error on failure.

type Sender

type Sender interface {
	Send(key *keys.EdX25519Key, endpoint, msg string, cap Capabilities) error
}

Sender defines an interface for sending messages to another Salty Address (user) and is primarily used by the PWA and possibly other clients to send outbound messages where it may not always be possible to send the message directly and instead proxy the message thorugh a broker. Note that even if proxying through a broker, the message is already encrypted at the point of proxying, so there needs not be any trust between the client and broker as the broker is treated as a "dumb" relay.

type Service

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

Service is an object that implements an async responder (bot) that responds to textual callbacks (commands) or events. This can be used to implement automated users, bots or services.

func NewService

func NewService(me Addr, id *Identity, state string) (*Service, error)

NewService constructs a new service with the provided address, identity and state

func (*Service) EventFunc

func (svc *Service) EventFunc(name string, fn MessageEventHandlerFunc)

EventFunc registers a handler for processing event callbacks (events)

func (*Service) Respond

func (svc *Service) Respond(user, msg string) error

Respond is a convenitne method to respond to a user with the provided message

func (*Service) Run

func (svc *Service) Run(ctx context.Context) error

Run runs the service tunil the context is done, if an error occurred at any point an error is returned.

func (*Service) SetClient

func (svc *Service) SetClient(cli *Client)

SetClient sets the client instance to used for this service

func (*Service) String

func (svc *Service) String() string

func (*Service) TextFunc

func (svc *Service) TextFunc(name string, fn MessageTextHandlerFunc)

TextFunc registers a handler for processing textual callbacks (commands)

type StandardResolver

type StandardResolver struct{}

StandardResolver is a standard resolver that performs direct DNS queries over the standard networking protocol using port tcp/53 or udp/53

func (*StandardResolver) LookupSRV

func (r *StandardResolver) LookupSRV(service, proto, domain string) (string, error)

LookupSRV implements the Resolver interface

type State

type State struct {
	sync.RWMutex

	Indicies map[string]int64
}

State represents the state of a client and the indices of its inbox(es)

func LoadState

func LoadState(r io.Reader) (*State, error)

LoadState loads a state from a file on disk

func NewState

func NewState() *State

NewState returns a new empty state

func (*State) Bytes

func (s *State) Bytes() ([]byte, error)

Bytes serialises the state into a byte slice

func (*State) GetIndex

func (s *State) GetIndex(name string) int64

GetIndex returns the current index for the name inbox

func (*State) Save

func (s *State) Save(fn string) error

Save persists the state to a file on disk

func (*State) SetIndex

func (s *State) SetIndex(name string, index int64)

SetIndex sets the index for the named inbox

Directories

Path Synopsis
cmd
salty-chat
Package main implements the `salty-chat` command-line (CLI) tool and a terminal user interface (TUI)
Package main implements the `salty-chat` command-line (CLI) tool and a terminal user interface (TUI)
app
Package app implements a terminal user interface (tui)
Package app implements a terminal user interface (tui)
authreq
Package authreq signa and verifies HTTP requests using Ed25519 private/public keys
Package authreq signa and verifies HTTP requests using Ed25519 private/public keys
pwa
Package main implements the PWA (progressive web app) using the go-app framework
Package main implements the PWA (progressive web app) using the go-app framework

Jump to

Keyboard shortcuts

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