secureio

package module
v0.0.0-...-9ffe99b Latest Latest
Warning

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

Go to latest
Published: Jun 28, 2020 License: LGPL-3.0 Imports: 36 Imported by: 2

README

GoDoc go report Build Status Coverage Status

Quick start

Prepare keys (on both sides):

[ -f ~/.ssh/id_ed25519 ] && [ -f ~/.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519
scp ~/.ssh/id_ed25519.pub remote:from_remote_side/

Encrypted io.ReadWriteCloser, easy:

// Generate (if not exists) and read ED25519 keys 
identity, err := secureio.NewIdentity(`/home/user/.ssh`)

// Read remote identity 
remoteIdentity, err := secureio.NewRemoteIdentityFromPublicKey(`/home/user/from_remote_side/id_ed25519.pub`)

// Create a connection
conn, err := net.Dial("udp", "10.0.0.2:1234")

// Create an encrypted connection (and exchange keys using ECDH and verify remote side by ED25519 signature).
session := identity.NewSession(remoteIdentity, conn, nil, nil)
session.Start(context.Background())

// Use it!

// Write to it
_, err = session.Write(someData)

// Or/and read from it
_, err = session.Read(someData)

It's also a multiplexer

Receive

Setup the receiver:

session.SetHandlerFuncs(secureio.MessageTypeChannel(0), func(payload []byte) {
    fmt.Println("I received a payload:", payload)
}, func(err error) {
    panic(err)
})
Send

Send a message synchronously:

_, err := session.WriteMessage(secureio.MessageTypeChannel(0), payload)

OR

Send a message asynchronously:

// Schedule the sending of the payload
sendInfo := session.WriteMessageAsync(secureio.MessageTypeChannel(0), payload)

[.. your another stuff here if you want ..]

// Wait until the real sending
<-sendInfo.Done()

// Here you get the error if any:
err := sendInfo.Err

// It's not necessary, but helps to reduce the pressure on GC (so to optimize CPU and RAM utilization)
sendInfo.Release()
MessageTypes

A MessageType for a custom channel may be created via function MessageTypeChannel(channelID uint32). channelID is a custom number to identify which flow is it (to connect a sender with appropriate receiver on the remote side). In the examples above it was used 0 as the channelID value, but it could be any value in the range: 0 <= x <= 2**31.

Also there's a special MessageType MessageTypeReadWrite is used for default Read()/Write(). But you may redirect this flow to a custom handler.

Limitations and hints

  • Does not support traffic fragmentation. If it's required to make it work over UDP then it's required to disable the fragmentation, see an example for UDP.
  • If the underlying writer cannot handle big messages then it's required to adjust SessionOptions.MaxPayloadSize.
  • If you don't have multiple writers and you don't need to aggregate messages (see below) then you may set SessionOptions.SendDelay to &[]time.Duration{0}[0].

Benchmark

The benchmark was performed with communication via an UNIX-socket.

BenchmarkSessionWriteRead1-8                          	   10000	    118153 ns/op	   0.01 MB/s	     468 B/op	       8 allocs/op
BenchmarkSessionWriteRead16-8                         	   10000	    118019 ns/op	   0.14 MB/s	     455 B/op	       8 allocs/op
BenchmarkSessionWriteRead1024-8                       	    9710	    119238 ns/op	   8.59 MB/s	     441 B/op	       8 allocs/op
BenchmarkSessionWriteRead32000-8                      	    6980	    173441 ns/op	 184.50 MB/s	     488 B/op	       9 allocs/op
BenchmarkSessionWriteRead64000-8                      	    3994	    310038 ns/op	 206.43 MB/s	     629 B/op	       9 allocs/op
BenchmarkSessionWriteMessageAsyncRead1-8              	 2285032	       539 ns/op	   1.86 MB/s	       0 B/op	       0 allocs/op
BenchmarkSessionWriteMessageAsyncRead16-8             	 2109264	       572 ns/op	  27.99 MB/s	       2 B/op	       0 allocs/op
BenchmarkSessionWriteMessageAsyncRead1024-8           	  480385	      2404 ns/op	 425.87 MB/s	      15 B/op	       0 allocs/op
BenchmarkSessionWriteMessageAsyncRead32000-8          	   30163	     39131 ns/op	 817.76 MB/s	     162 B/op	       5 allocs/op
BenchmarkSessionWriteMessageAsyncRead64000-8          	   15435	     77898 ns/op	 821.59 MB/s	     317 B/op	      10 allocs/op

This package is designed to be asynchronous, so basically Write is an stupid wrapper around code of WriteMessageAsync. To get more throughput it merges all your messages collected in 50 microseconds into a one, sends, and then splits them back. It allows to reduce amount of syscalls and other overheads. So to achieve like 1.86MiB/s on 1-byte messages you need to send a lot of them asynchronously (from each other), so they will be merged while sending/receiving through the backend connection.

Also this 800MiB/s is more about the localhost-case. And more realistic network case (if we have MTU ~= 1400) is:

BenchmarkSessionWriteMessageAsyncRead1300_max1400-8   	  117862	     10277 ns/op	 126.49 MB/s	     267 B/op	      10 allocs/op

Security design

Key exchange

The remote side is authenticated by a ED25519 signature (of the key exchange message).

Key exchange is performed via ECDH with X25519. If a PSK is set, then it is PSK concatenated with a constant salt-value, hashed with blake3.Sum256 and sha3.Sum256 and used to XOR the (exchanged) key.

The resulting value is used as the encryption key for XChaCha20. This key is called cipherKey within the code.

The key (received via ECDH) is updated every minute. So in turn the cipherKey is updated every minute as well.

Encryption

A SessionID is exchanged by the very first key-exchange messages. SessionID is a combination of UnixNano (of when the Session was initialized) and random 64-bit integer.

Also each packet starts with an unique (for a session) plain-text PacketID (actually if PSK is set then PacketID encrypted with a key derived as a hash of a salted PSK).

The combination of PacketID and SessionID is used as IV/NONCE for XChaCha20 and cipherKey is used as the key.

Worth to mention that PacketID is intended to be unique only within a Session. So it just starts with zero and then increases by 1 for each next message. Therefore if the system clock is broken then uniqueness of NONCE is guaranteed by 64-bit random value of SessionID.

Message authentication

Message authentication is done using Poly1305. As key for Poly1305 used a blake3.Sum256 hash of:

PacketID is supposed to be increasing only. Received PacketID are remembered in a limited window of values. If it was received a packet with the same PacketID (as it already was) or with a lesser PackerID (than the minimal possible value in the window) then the packet is just ignored.

Session states

A successful session with the default settings goes through states/phases/stages:

  • Initialization
  • Key exchanging
  • Negotiation
  • Established
  • Closing
  • Closed
Initialization

This is the stage where all the options are parsed and all the required goroutines are being initialized.

After that *Session will be switched to the "Key Exchanging" state.

Key exchanging

At this stage both parties (the local one and the remote one) are exchanging with ECDH public keys to get a symmetrical shared key (see "Security Design").

Also if a remote identity is set then we verify if it matches, and ignores any key-exchange messages with any other ED25519 public keys (don't confuse with ECDH public key): ED25519 keypairs are static for each party (and usually pre-defined), while ECDH keypairs are generated for each key exchange.

Also each key-exchanging key is verified by the public key (passed with the message).

With the default settings, each party also sends acknowledgement messages to verify if the messages was received and percieved. And (with the default settings) each party waits for a acknowledgment message from the remote side (see also SessionOptions.AnswersMode).

If everything is successful here then the *Session goes to the "Negotiation" phase. But the key exchanging process is still periodically performed in background.

Negotiation

At this stage we try to determine which size of packets the underlying io.Writer can handle. So we try to send packets of 3 different sizes and look which of them will be able to make a round trip. Then repeat the procedure in a shorter interval. And so on up to 4 times.

This behavior could be enabled or disabled through SessionOptions.NegotiatorOptions.Enable.

When this procedure will be finished (or if it is disabled), then the *Session is switched to the state "Established".

Established

This is the state in which the *Session is operating normally, so you may send and receive messages through it. And messages attempted to be sent before reaching this stage will be sent as soon as *Session will reach this stage.

Closing / Closed

Closing is a transitional state before becoming Closed. If state Closed is reached it means the session is dead and nothing will ever happen to it anymore.

TODO

  • support of fragmented/merged (by backend) traffic.
  • check keyCreatedAt
  • error if key hasn't changed
  • verify TS difference sanity
  • don't use Async for sync-writes.
  • route messenger-related errors to the messenger's handler.
  • more comments (in the code)
  • consider notewakeup instead of Cond.Wait

I'm not sure if we need

  • download key-files by URLs

Documentation

Index

Constants

View Source
const (
	// DefaultKeyExchangeInterval defines how ofter the cipher key is renewed.
	DefaultKeyExchangeInterval = time.Minute

	// DefaultKeyExchangeTimeout defines how long it will wait (after sending
	// the request) for a response to request to exchange keys before
	// consider the situation erroneous.
	DefaultKeyExchangeTimeout = time.Minute

	// DefaultKeyExchangeRetryInterval defines the default value of
	// KeyExchangerOptions.RetryInterval.
	DefaultKeyExchangeRetryInterval = time.Second
)
View Source
const (
	// PublicKeySize the size of a identity public key in bytes.
	PublicKeySize = ed25519.PublicKeySize

	// PrivateKeySize the size of a identity private key in bytes
	PrivateKeySize = ed25519.PrivateKeySize
)
View Source
const (
	// KeyExchangeAnswersModeDefault means use the default value of AnswersMode
	KeyExchangeAnswersModeDefault = KeyExchangeAnswersMode(iota)

	// KeyExchangeAnswersModeAnswerAndWait makes the key exchanger to send
	// answers and wait for answers from the remote side before consider
	// a key exchange to be successful.
	KeyExchangeAnswersModeAnswerAndWait

	// KeyExchangeAnswersModeAnswer makes the key exchanger to send
	// answers, but don't wait for them from the remote side.
	KeyExchangeAnswersModeAnswer

	// KeyExchangeAnswersModeDisable makes the key exchanger to do not
	// send answers and to do not wait for them from the remote side.
	KeyExchangeAnswersModeDisable
)
View Source
const (
	// DefaultNegotiatorTotalTimeout is the default value for
	// `NegotiatorOptions.TotalTimeout`.
	DefaultNegotiatorTotalTimeout = time.Minute * 5

	// DefaultNegotiatorReadTimeout is the default value for
	// `NegotiatorOptions.ReadTimeout`.
	DefaultNegotiatorReadTimeout = time.Minute

	// DefaultNegotiatorMaxIterations is the default value for
	// `NegotiatorOptions.MaxIterations`.
	DefaultNegotiatorMaxIterations = 4
)
View Source
const (
	// NegotiatorEnableAuto enables negotiations only if detects an UDP
	// connection.
	NegotiatorEnableAuto = NegotiatorEnable(iota)

	// NegotiatorEnableFalse disables negotiations.
	NegotiatorEnableFalse

	// NegotiatorEnableTrue enables negotiations
	NegotiatorEnableTrue
)
View Source
const (
	// DefaultErrorOnSequentialDecryptFailsCount defines the default value
	// of how many sequential messages is required be failed to be decrypted
	// to consider this fails to be erroneous situation.
	DefaultErrorOnSequentialDecryptFailsCount = 3

	// DefaultSendDelay defines the default messages aggregation delay.
	// It is used to merge small messages into one while sending it
	// through the backend to reduce overheads.
	DefaultSendDelay = time.Microsecond * 50

	// DefaultPacketIDStorageSize defines the default value for
	// SessionOptions.PacketIDStorageSize.
	//
	// Due to internal-implementation-specifics the value will
	// be automatically round-up to be aligned to 64.
	//
	// Don't use big values here (e.g. >4096):
	// T: O(n)
	// S: O(n)
	//
	// It seems unlikely to get a legitimate misordered packet
	// with misplacement more than 256 packets, while
	// the performance penalty is almost absent (the value is
	// small enough).
	DefaultPacketIDStorageSize = 256

	// DefaultMaxChainIDDiff is the default value for
	// SessionOptions.MaxChainIDDiff.
	DefaultMaxChainIDDiff = 8

	// DefaultMaxFragmentedMessageSize is the default value for
	// SessionOptions.MaxFragmentedMessageSize.
	DefaultMaxFragmentedMessageSize = 1 << 16
)
View Source
const (
	// SessionStateNew means the Session was just created and even
	// did not Start it's routines.
	SessionStateNew = SessionState(iota)

	// SessionStateClosed means the session is already deattached from
	// the backend io.ReadWriteCloser, closed and cannot be used anymore.
	SessionStateClosed

	// SessionStateKeyExchanging is the state which follows after
	// SessionStateNew. It means the Session started it's routines
	// (including the key exchanger routine), but not yet successfully
	// exchanged with keys (at least once).
	SessionStateKeyExchanging

	// SessionStateNegotiating is the state which follows after
	// SessionStateKeyExchanging. It means the Session is performing
	// experiments to find the optimal settings for further
	// communications.
	SessionStateNegotiating

	// SessionStateEstablished means the Session successfully exchanged
	// with keys and currently operational.
	SessionStateEstablished

	// SessionStatePaused means the Session was temporary detached from
	// the backend io.ReadWriteCloser by method `(*Session).SetPause`.
	SessionStatePaused

	// SessionStateClosing is a transition state to SessionStateClosed
	SessionStateClosing
)
View Source
const (

	// MessageTypeReadWrite is the default MessageType for
	// the in-band data. It used by default for (*Session).Read and
	// (*Session).Write.
	MessageTypeReadWrite
)

Variables

View Source
var (

	// Salt is used to append PSKs. If you change this value then
	// it is required to change it on both sides.
	Salt = []byte(`xaionaro-go/secureio.KeyExchanger`)
)

Functions

func IsLossyWriter

func IsLossyWriter(w io.Writer) bool

IsLossyWriter returns true if writer `w` is a known type of a writer which can loose traffic (currently it only looks for UDP connections).

func SetPayloadLossySizeLimit

func SetPayloadLossySizeLimit(newSize uint32)

SetPayloadLossySizeLimit sets the default PayloadSizeLimit is the backend is considered lossy (see IsLossyWriter).

func SetPayloadSizeLimit

func SetPayloadSizeLimit(newSize uint32)

SetPayloadSizeLimit sets the default PayloadSizeLimit is the backend is not considered lossy (see IsLossyWriter).

Types

type DebugOutputEntry

type DebugOutputEntry struct {
	// Format has the same meaning as the first argument to `fmt.Printf`
	Format string

	// Args has the same meaning as the rest arguments (except the first one)
	// to `fmt.Printf`
	Args []interface{}
}

DebugOutputEntry is a structure of data which is being passed to a debugger

See `(*Session).DebugOutputChan()` and `(*Session).InfoOutputChan()`.

type ErrAlreadyClosed

type ErrAlreadyClosed struct{}

ErrAlreadyClosed is an error indicates there was an attempt to use a resource which is already marked as closed. For example, it could mean a try to use a closed session or connection.

func (ErrAlreadyClosed) Error

func (err ErrAlreadyClosed) Error() string

type ErrAnswersModeMismatch

type ErrAnswersModeMismatch struct {
	AnswersModeLocal  KeyExchangeAnswersMode
	AnswersModeRemote KeyExchangeAnswersMode
}

ErrAnswersModeMismatch is reported when local and remote side has different settings of KeyExchangerOptions.AnswersMode

func (ErrAnswersModeMismatch) Error

func (err ErrAnswersModeMismatch) Error() string

type ErrCanceled

type ErrCanceled struct{}

ErrCanceled is an error indicates that the action was canceled. It happens when there're active async-requests while session is already closing.

func (ErrCanceled) Error

func (err ErrCanceled) Error() string

type ErrCannotDecrypt

type ErrCannotDecrypt struct{}

ErrCannotDecrypt is an error indicates it was unable to decrypt a message. So all three attempts failed: * Try to decrypt using current cipher key. * Try to decrypt using previous cipher key. * Try to interpret it as already a non-encrypted message.

func (ErrCannotDecrypt) Error

func (err ErrCannotDecrypt) Error() string

type ErrCannotLoadKeys

type ErrCannotLoadKeys struct {
	OriginalError error
}

ErrCannotLoadKeys is an error indicates if it was unable to read or/and parse crypto keys.

func (ErrCannotLoadKeys) Error

func (err ErrCannotLoadKeys) Error() string

type ErrCannotPauseOrUnpauseFromThisState

type ErrCannotPauseOrUnpauseFromThisState struct{}

ErrCannotPauseOrUnpauseFromThisState is returned by SetPause() if the session is not in a required state.

To pause a session it must be in state SessionStateEstablished. To unpause a session it must be in state SessionStatePaused.

func (ErrCannotPauseOrUnpauseFromThisState) Error

type ErrCannotSetReadDeadline

type ErrCannotSetReadDeadline struct {
	Backend io.ReadWriter
}

ErrCannotSetReadDeadline is returned if it was an attempt to use "Detach" (see SessionOptions) functions or "SetPause" on a session created over io.ReadWriteCloser which does not implement any of methods: `SetReadDeadline` and `SetDeadline`.

func (ErrCannotSetReadDeadline) Error

func (err ErrCannotSetReadDeadline) Error() string

type ErrInvalidChecksum

type ErrInvalidChecksum struct {
	ExpectedChecksum [poly1305.TagSize]byte
	RealChecksum     [poly1305.TagSize]byte
}

ErrInvalidChecksum is an error indicates if decrypted checksum does not match checksum of the decrypted data with any currently available cipher key.

func (ErrInvalidChecksum) Error

func (err ErrInvalidChecksum) Error() string

type ErrInvalidSignature

type ErrInvalidSignature struct{}

ErrInvalidSignature is an error indicates if the remote side have sent a signature which fails to be verified by the known public key (of the remote side).

func (ErrInvalidSignature) Error

func (err ErrInvalidSignature) Error() string

type ErrKeyExchangeTimeout

type ErrKeyExchangeTimeout struct{}

ErrKeyExchangeTimeout is an error indicates that there was no successful key exchange too long. So this session does not work properly or/and cannot be trusted and therefore considered erroneous.

func (ErrKeyExchangeTimeout) Error

func (err ErrKeyExchangeTimeout) Error() string

type ErrPartialWrite

type ErrPartialWrite struct{}

ErrPartialWrite is an error indicates if a Write() call returned "n" less than expected. Could be a connection-related problem.

func (ErrPartialWrite) Error

func (err ErrPartialWrite) Error() string

type ErrPayloadTooBig

type ErrPayloadTooBig struct {
	MaxSize  uint
	RealSize uint
}

ErrPayloadTooBig means there was an attempt to use more buffer space than it was reserved. The size of a message should not exceed (*Session).GetPayloadSizeLimit() bytes.

func (ErrPayloadTooBig) Error

func (err ErrPayloadTooBig) Error() string

type ErrTooShort

type ErrTooShort struct {
	ExpectedLength uint
	RealLength     uint
}

ErrTooShort is an error used when it was unable to parse something because the data (in the binary representation) is too short. For example if there was received only one byte while it was expecting for message headers (which are a structure of a static size larger than one byte).

func (ErrTooShort) Error

func (err ErrTooShort) Error() string

type ErrWrongKeyLength

type ErrWrongKeyLength struct {
	ExpectedLength uint
	RealLength     uint
}

ErrWrongKeyLength is an error indicates when a crypto key is of a wrong size. For example ED25519 key is expected to be 256 bits (no more, no less).

func (ErrWrongKeyLength) Error

func (err ErrWrongKeyLength) Error() string

type EventHandler

type EventHandler interface {
	// OnConnect is called right after the first successful key exchange
	// with the remote side.
	OnConnect(*Session)

	// Error is called when there's an error occurred which is nowhere
	// else to return to.
	Error(*Session, error) bool
}

EventHandler is a collection of callbacks.

type Handler

type Handler interface {
	// Handler is the function called each time to Handler an incoming message
	Handle([]byte) error
}

Handler is an interface defines a custom Handler of receiving traffic for a Messenger.

type Identity

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

Identity is a subject of a (secure) communication. Identity is used to be identified as the subject that is expected to be communicating with.

If the "Private" key is set then the identity could be used as a "local identity", so it could be used to represent the communication participant to a remote side.

If the "Public" key is set then the identity could be used as a "remote identity", so it could be used to verify the communication participant of the remote side.

func NewIdentity

func NewIdentity(keysDir string) (*Identity, error)

NewIdentity is a constructor for `Identity` based on the path to ED25519 keys. It:

* Parses ED25519 keys from directory `keysDir` if they exists and creates a new instance of `*Identity`. * Creates ED25519 keys and saves them to the directory `keysDir` if they does not exist there and creates a new instance of `*Identity`.

File names in the directory are `id_ed25519` and `id_ed25519.pub`.

The returned identity (if it is not `nil`) could be used as both: local and remote (see `Identity`).

func NewIdentityFromPrivateKey

func NewIdentityFromPrivateKey(privKey ed25519.PrivateKey) (*Identity, error)

NewIdentityFromPrivateKey is a constructor for `Identity` based on private ED25519 key.

The returned identity could be used as both: local and remote (see `Identity`).

func NewRemoteIdentity

func NewRemoteIdentity(keyPath string) (*Identity, error)

NewRemoteIdentity is a constructor for `Identity` based on the path to the ED25519 public key. It parses the public key from the directory `keysDir`. The file name is `id_ed25519.pub`.

The returned identity (if it is not `nil`) could be used only as a remote identity (see `Identity`).

func NewRemoteIdentityFromPublicKey

func NewRemoteIdentityFromPublicKey(pubKey ed25519.PublicKey) (*Identity, error)

NewRemoteIdentityFromPublicKey is a constructor for `Identity` based on the ED25519 public key.

The returned identity could be used only as a remote identity (see `Identity`).

func (*Identity) MutualConfirmationOfIdentity

func (i *Identity) MutualConfirmationOfIdentity(
	ctx context.Context,
	remoteIdentity *Identity,
	backend io.ReadWriteCloser,
	eventHandler EventHandler,
	options *SessionOptions,
	onStartFunc func(sess *Session) error,
) (ephemeralKeys [][]byte, returnError error)

MutualConfirmationOfIdentity is a helper which creates a temporary session to verify the remote side and (securely) exchange with an ephemeral key. If this method is used then it should be used on the both sides. While an execution of the method nothing else should write-to or/and read-from the `backend`. By the end of execution of this method the temporary session will be closed and then the backend will be free to be used for other purposes.

This method could be used for example to: * Verify if the expected participant is on the remote side. * Easy and securely create a new shared key with the remote side.

func (*Identity) NewSession

func (i *Identity) NewSession(
	remoteIdentity *Identity,
	backend io.ReadWriteCloser,
	eventHandler EventHandler,
	opts *SessionOptions,
) *Session

NewSession creates a secure session over (unsecure) `backend`.

The session verifies the remote side using `remoteIdentity`, securely exchanges with encryption keys and allows to communicate through it. Nothing else should write-to or/and read-from the `backend` while the session is active.

The session will not work until method Start() will be called.

See `Session`.

func (*Identity) Sign

func (i *Identity) Sign(signature, data []byte)

Sign just fills `signature` with an ED25519 signature of `data`.

func (*Identity) VerifySignature

func (i *Identity) VerifySignature(signature, data []byte) error

VerifySignature just verifies an ED25519 signature `signature` over `data`.

type KeyExchangeAnswersMode

type KeyExchangeAnswersMode uint8

KeyExchangeAnswersMode is the variable type for KeyExchangeOptions.AnswersMode

type KeyExchangerOptions

type KeyExchangerOptions struct {
	// KeyUpdateInterval defines delay between generating a new cipher key.
	//
	// Generating a key is an expensive operation. Moreover
	// secureio remembers only the current key and the previous one. So
	// if you generate keys with interval less than required for stable
	// round-trip between peers, then the session will be very unstable.
	//
	// If a zero-value then DefaultKeyExchangeInterval is used.
	KeyUpdateInterval time.Duration

	// RetryInterval defines the maximal delay between sending a key exchange
	// packet and success key exchange before resending the key exchange
	// packet.
	//
	// If a zero-value then DefaultKeyExchangeRetryInterval is used instead.
	RetryInterval time.Duration

	// Timeout defines how long it can wait after sending a request
	// to exchange keys and before the successful key exchange. If
	// it waits more than the timeout then an error is returned.
	//
	// If a zero-value then DefaultKeyExchangeTimeout is used.
	Timeout time.Duration

	// PSK is a Pre-Shared Key. If it is set then it is used as
	// an additional source for ephemeral key ("cipher key") generation.
	// So if it is set then to initiate a working session it's required to
	// satisfy both conditions: valid (and expected) identities and the same PSK.
	PSK []byte

	// AnswersMode set the behavior of key-exchange message acknowledgments.
	//
	// When the local side receives a packet from the remote side it _may_
	// send a key exchange packet even if it was already sent before. This
	// packet is called "answer". An answer packet is marked with a special
	// flag to prevent answers on answers (to prevent loops).
	//
	// If two parties has different AnswersModes then an error will be
	// reported.
	//
	// See KeyExchangeAnswersMode values.
	AnswersMode KeyExchangeAnswersMode
	// contains filtered or unexported fields
}

KeyExchangerOptions is used to configure the key exchanging options. It's passed to a session via SessionOptions.

type Keys

type Keys struct {
	Public  ed25519.PublicKey
	Private ed25519.PrivateKey
}

Keys is a key pair used to generate signatures (to be verified on the remote side) and to verify the remote side.

If "Private" key is defined than it could be used for a local identity If "Public" key is defined than it could be used for a remote identity.

type MessageType

type MessageType uint32

MessageType is the identifier of the type of the message. It is used to determine how to interpret the message (which Handler to use).

func MessageTypeChannel

func MessageTypeChannel(channelID uint32) MessageType

MessageTypeChannel returns MessageType for multiplexer. See (*Session).SetHandlerFuncs.

func (MessageType) String

func (t MessageType) String() string

type Messenger

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

Messenger is a Handler for a specific MessageType and for a specific Session.

func (*Messenger) Close

func (messenger *Messenger) Close() error

Close closes the defined Handler.

See `(*Messenger).SetHandler`

func (*Messenger) SetHandler

func (messenger *Messenger) SetHandler(handler Handler)

SetHandler sets the handler for incoming traffic.

func (*Messenger) WaitForClosure

func (messenger *Messenger) WaitForClosure()

WaitForClosure waits until the Messenger will be closed and will finish everything.

func (*Messenger) Write

func (messenger *Messenger) Write(p []byte) (int, error)

Write sends the message of MessageType assigned to the Messenger through the Session of this Messenger in the synchronous way.

See also (*Session).WriteMessage

func (*Messenger) WriteAsync

func (messenger *Messenger) WriteAsync(p []byte) *SendInfo

WriteAsync sends a message of MessageType assigned to the Messenger through the Session of this Messenger in the asynchronous way.

See also (*Session).WriteMessageAsync

func (*Messenger) WriteSingle

func (messenger *Messenger) WriteSingle(p []byte) (int, error)

WriteSingle sends the message of MessageType assigned to the Messenger (see `(*Session).NewMessenger`) through the Session of this Messenger in the synchronous way and without messages aggregation (with effectively disabled send-delay, see `SessionOptions.SendDelay`).

See also (*Session).WriteMessageSingle

type NegotiatorEnable

type NegotiatorEnable uint8

NegotiatorEnable controls if negotiations should be enabled.

type NegotiatorOptions

type NegotiatorOptions struct {
	// Enable controls if negotiations should be enabled.
	Enable NegotiatorEnable

	// ReadTimeout defines how long the negotiator is allowed to wait
	// without any incoming message before consider the connection
	// unreliable and return an error.
	//
	// The default value is DefaultNegotiatorReadTimeout
	//
	// Use a negative value to disable this behavior.
	ReadTimeout time.Duration

	// TotalTimeout defines how long the negotiator is allowed to wait
	// in total before give up and just try to use the best-yet-found
	// settings. And if the timeout was reached and there was no
	// pong-responses at all (there is no of any found settings) then
	// return an error
	//
	// The default value is DefaultNegotiatorTotalTimeout
	//
	// Use a negative value to disable this behavior.
	TotalTimeout time.Duration

	// MaxIterations define how many iterations of probing for packet
	// size is permitted. The more this value the more time will
	// be required to negotiate, but the value of the optimal packet
	// size will be more precise
	//
	// The default value is DefaultNegotiatorMaxIterations
	MaxIterations uint32
}

NegotiatorOptions is the structure with options related only to the negotiation process. The negotiation process follows after key-exchanging and called upon to find optimal settings to communicate through given underlying io.ReadWriteCloser.

type SendInfo

type SendInfo struct {
	// Err contains the resulting error
	Err error

	// N contains the number of bytes were written to send the merged
	// messaged through the backend.
	N int
	// contains filtered or unexported fields
}

SendInfo contains information about the scheduled sending request. It's values should be read only after "<-(*SendInfo).Done()" will finish.

func (*SendInfo) Done

func (sendInfo *SendInfo) Done() <-chan struct{}

Done returns a channel which should be used to wait until a real sending will be performed. After that values `SendInfo.Err` and `SendInfo.N` could be read and method `(*SendInfo).Release()` could be called.

func (*SendInfo) Release

func (sendInfo *SendInfo) Release()

Release just puts the `*SendInfo` back to the memory pool to be re-used in future. It could be used to reduce the pressure on GC. It's **NOT** necessary to call this function. It is supposed to be used only high-performant applications.

func (*SendInfo) SendID

func (sendInfo *SendInfo) SendID() uint64

SendID returns the unique ID of the sending request. It could be called at any moment before `(*SendInfo).Release()`.

func (*SendInfo) SendNowAndWait

func (sendInfo *SendInfo) SendNowAndWait()

SendNowAndWait belays the rest part of the send delay of the remaining send iteration and forces to send the data ASAP and wait until it will be done.

func (*SendInfo) String

func (sendInfo *SendInfo) String() string

func (*SendInfo) Wait

func (sendInfo *SendInfo) Wait()

Wait waits until message is send or until the context is cancelled (for example if session is closed).

type Session

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

Session is an encrypted communication session which: * Verifies the remote side. * Uses ephemeral encryption keys ("cipher key") to encrypt/decrypt the traffic.

When a Session is already in work, nothing else should Read-from/Write-to the backend io.ReadWriteCloser of the session.

Session also implements io.ReadWriteCloser.

func (*Session) Close

func (sess *Session) Close() error

Close implements io.Closer. It will send a signal to close the session, but it will return immediately (without waiting until everything will finish).

func (*Session) CloseAndWait

func (sess *Session) CloseAndWait() error

CloseAndWait sends the signal to close to the Session and waits until it will be done.

func (*Session) DebugOutputChan

func (sess *Session) DebugOutputChan() <-chan DebugOutputEntry

DebugOutputChan returns the channel to receive the DEBUG messages from.

func (*Session) GetCipherKeys

func (sess *Session) GetCipherKeys() [][]byte

GetCipherKeys returns the currently active cipher keys. Do not modify it, it's not a copy.

It returns nil if there was no successful key exchange, yet.

func (*Session) GetCipherKeysWait

func (sess *Session) GetCipherKeysWait() [][]byte

GetCipherKeysWait waits until the first successful key exchange and returns the latest cipher key. Do not modify it, it's not a copy.

func (*Session) GetEphemeralKeys

func (sess *Session) GetEphemeralKeys() [][]byte

GetEphemeralKeys just returns the last generated shared keys

It's not a copy, don't modify.

func (*Session) GetEstablishedPacketSize

func (sess *Session) GetEstablishedPacketSize() uint32

GetEstablishedPacketSize returns the packet size limit received as result of negotiations. This is the real packet size used for communications.

The value is calculated based on GetEstablishedPayloadSize() with addition of sizes of headers and paddings.

func (*Session) GetEstablishedPayloadSize

func (sess *Session) GetEstablishedPayloadSize() uint32

GetEstablishedPayloadSize returns the payload size limit received as result of negotiations. This is the real payload size used for communications.

func (*Session) GetPacketSizeLimit

func (sess *Session) GetPacketSizeLimit() uint32

GetPacketSizeLimit returns the currently configured maximal packet size that could be sent through the backend io.ReadWriteCloser.

The value is calculated based on SessionOptions.PayloadSizeLimit with addition of sizes of headers and paddings.

See also GetEstablishedPacketSize

func (*Session) GetPayloadSizeLimit

func (sess *Session) GetPayloadSizeLimit() uint32

GetPayloadSizeLimit returns the currently configured MaxPayLoadSize of this Session.

See also SessionOptions.PayloadSizeLimit and GetEstablishedPacketSize

func (*Session) GetRemoteIdentity

func (sess *Session) GetRemoteIdentity() (result *Identity)

GetRemoteIdentity returns the remote identity. It's not a copy, don't modify the content.

func (*Session) GetState

func (sess *Session) GetState() SessionState

GetState returns the current state of the Session.

See SessionState.

func (*Session) GetUnexpectedPacketIDCount

func (sess *Session) GetUnexpectedPacketIDCount() uint64

GetUnexpectedPacketIDCount returns the amount of packets which were ignored due to a wrong PacketID.

See option `SessionOptions.AllowReorderingAndDuplication`.

func (*Session) ID

func (sess *Session) ID() SessionID

ID returns the unique (though the program execution) session ID

func (*Session) InfoOutputChan

func (sess *Session) InfoOutputChan() <-chan DebugOutputEntry

InfoOutputChan returns the channel to receive the INFO messages from.

func (*Session) NewMessenger

func (sess *Session) NewMessenger(msgType MessageType) *Messenger

NewMessenger returns a io.ReadWriteCloser for a specified MessageType. It overrides other handlers/messengers for this MessageType (if they set).

func (*Session) Read

func (sess *Session) Read(p []byte) (int, error)

Read implements io.Reader

func (*Session) SetHandlerFuncs

func (sess *Session) SetHandlerFuncs(
	msgType MessageType,
	handle func([]byte) error,
	onError func(error),
)

SetHandlerFuncs sets Handler functions for the specified MessageType: * `msgType` should be the same on the both sides of one communication; a MessageType could be received using function MessageTypeChannel(). * `handle` handles incoming traffic/messages. * `onError` handles errors.

func (*Session) SetPause

func (sess *Session) SetPause(newValue bool) (err error)

SetPause with value `true` temporary disables the reading process from the backend Reader. To use this method the backend Reader should has method SetReadDeadline or/and SetDeadline.

SetPause(true) could be used only from state SessionStateEstablished.

SetPause with value `false` re-enables the reading process from the backend Reader. It could be used only from state SessionStatePaused.

Returns nil if the action was successful.

func (*Session) Start

func (sess *Session) Start(ctx context.Context) error

Start runs all the goroutines to make the session work. The session will not work until Start will be called.

func (*Session) WaitForClosure

func (sess *Session) WaitForClosure()

WaitForClosure waits until the Session will be closed and will finish everything.

func (*Session) WaitForState

func (sess *Session) WaitForState(ctx context.Context, states ...SessionState) SessionState

WaitForState waits until the Session will get into any of the selected states.

func (*Session) Write

func (sess *Session) Write(p []byte) (int, error)

Write implements io.Writer

func (*Session) WriteMessage

func (sess *Session) WriteMessage(
	msgType MessageType,
	payload []byte,
) (int, error)

WriteMessage synchronously sends a message of MessageType `msgType`.

func (*Session) WriteMessageAsync

func (sess *Session) WriteMessageAsync(
	msgType MessageType,
	payload []byte,
) (sendInfo *SendInfo)

WriteMessageAsync asynchronously writes a message of MessageType `msgType`.

Temporary hack (may be will be removed in future): If SendDelay is zero then the message will be sent synchronously, anyway.

func (*Session) WriteMessageSingle

func (sess *Session) WriteMessageSingle(
	msgType MessageType,
	payload []byte,
) (int, error)

WriteMessageSingle synchronously sends a message of MessageType `msgType` as a single message (without merging with other messages, like if `SessionOptions.SendDelay` is negative).

type SessionID

type SessionID struct {
	CreatedAt uint64
	Random    uint64
}

SessionID is the struct to represent an unique Session ID. CreateAt always grows (in never repeats within an application instance).

func (*SessionID) Bytes

func (sessID *SessionID) Bytes() (result [16]byte)

Bytes returns SessionID as a byte array.

func (*SessionID) FillFromBytes

func (sessID *SessionID) FillFromBytes(b []byte)

FillFromBytes fills SessionID using bytes slice. A bytes slice could be received via method Bytes()

type SessionOptions

type SessionOptions struct {
	// EnableDebug enables the DEBUG messages to be passed through
	// `(*Session).DebugOutputChan()`. It causes performance penalty.
	//
	// This option will work only if the application was built
	// with tag "secureiodebug" (and/or "testlogging").
	EnableDebug bool

	// EnableInfo enables the INFO messages to be passed through
	// `(*Session).InfoOutputChan()`. It causes performance penalty.
	EnableInfo bool

	// SendDelay is the aggregation delay.
	// It is used to merge small messages into one while sending it
	// through the backend to reduce overheads.
	//
	// If it is set to a nil-value then DefaultSendDelay is used instead.
	//
	// If it is set to zero (`&[]time.Duration{0}[0]`) then no delay
	// will be performed and all messages will be sent right away.
	//
	// If you disable this option then you probably also would like
	// to disable the negotiator,
	// see `SessionOptions.NegotiatorOptions.Disable`.
	SendDelay *time.Duration

	// DetachOnMessagesCount is an amount of incoming messages after which
	// a Session will detach from the backend and close itself.
	// To use this feature the backend Reader should have
	// method SetReadDeadline or SetDeadline.
	//
	// If it is set to a zero-value then "never".
	DetachOnMessagesCount uint64

	// DetachOnSequentialDecryptFailsCount is an amount of sequential incoming
	// messages failed to be decrypted after which a Session will detach from
	// the backend and close itself.
	// To use this feature the backend Reader should have
	// method SetReadDeadline or SetDeadline.
	//
	// If it is set to a zero-value then "never".
	DetachOnSequentialDecryptFailsCount uint64

	// ErrorOnSequentialDecryptFailsCount is an amount of sequential incoming
	// messages failed to be decrypted after which a Session will report
	// and error.
	//
	// If it is set to a nil-value then
	// DefaultErrorOnSequentialDecryptFailsCount will be used instead.
	ErrorOnSequentialDecryptFailsCount *uint64

	// KeyExchangerOptions is the structure with options related only
	// to the key exchanging.
	//
	// See the description of fields of KeyExchangerOptions.
	KeyExchangerOptions KeyExchangerOptions

	// PayloadSizeLimit defines the maximal size of a messages passed
	// through the Session. The more this value is the more memory is consumed
	// and the larger payloads will be send through the underlying io.Writer.
	//
	// A whole packet (to be sent through the underlying io.Writer) will
	// be bigger (on size messagesContainerHeadersSize + messageHeadersSize).
	//
	// See also NegotiatorOptions.Disable.
	PayloadSizeLimit uint32

	// NegotiatorOptions is the structure with options related only
	// to the negotiation process. The negotiation process follows
	// after key-exchanging and called upon to find optimal settings
	// to communicate through given underlying io.ReadWriteCloser.
	NegotiatorOptions NegotiatorOptions

	// PacketIDStorageSize defines how many PacketID values could be
	// remembered to be able to check if packet was duplicated or
	// reordered.
	//
	// By default we try to eliminate possibility of duplicated packets
	// because it could be used by malefactors. So we remember
	// few (PacketIDStorageSize) highest values of received PacketID
	// values and:
	// * Drop if a packet has an ID we already remembered
	// * Drop if a packet has an ID lower than any remembered.
	//
	// If it's required to disable the mechanism of dropping packets
	// with invalid PacketID then set a negative value.
	//
	// Value "1" is a special value which enables the behaviour
	// where PacketID is allowed to grow only (no misordering is allowed).
	//
	// The default value (which is forced on a zero value) is
	// DefaultPacketIDStorageSize.
	PacketIDStorageSize int

	// EnableFragmentation allows to fragment messages. By default
	// a message larger than (*Session).GetEstablishedPayloadSize() are not
	// fragmented (and defragmented on the remote side), an error is returned
	// instead.
	EnableFragmentation bool

	// MaxChainIDDiff is the allowed maximum difference between the latest
	// chain ID and the oldest chain ID (stored in memory). The more this
	// value is the more memory will be consumed, but more-out-of-order
	// fragments still will be recognized.
	//
	// Each large message (which is larger than
	// (*Session).GetEstablishedPayloadSize()) is fragmented into smaller
	// packets (see also EnableFragmentation). These bunch of packets is
	// called "chain". And if there's a lot of chains and their packets are
	// received in a wrong order (for example due to properties of UDP) then
	// a lot of fragments are stored in memory. Moreover some packets
	// might be lost and we will store some fragments in memory forever.
	// And to do not consumer infinite amount of memory we limit how old
	// chains is permitted to keep.
	MaxChainIDDiff uint64

	// MaxFragmentedMessageSize is the maximum allows size of a message
	// (after assembling from all its fragments). The more this value is
	// the larder messages are allowed, but more memory is consumed.
	MaxFragmentedMessageSize uint64
}

SessionOptions is a structure to configure a Session while calling `(*Identity).NewSession`.

type SessionState

type SessionState uint64

SessionState is the state of a Session. It describes what's going on right now with the Session.

func (*SessionState) Load

func (state *SessionState) Load() SessionState

Load atomically returns the currents state

func (SessionState) String

func (state SessionState) String() string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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