Documentation ¶
Overview ¶
Package curvetls is a simple, robust transport encryption library.
This is a pluggable wrapper (client / server) for network I/O, which allows you to upgrade your regular network sockets to a protocol that supports robust framing, transport security and authentication, so long as your net.Conn is of any reliable kind (e.g. a TCP- or file-backed net.Conn).
Usage Instructions ¶
(a) Generate keypairs for clients and server, persisting them to disk if you want to, so you can later load them again.
(b) Distribute, however you see fit, the public keys of the server to the clients, and the public keys of the clients to the server.
(c) Generate one long nonce per server keypair, and one long nonce per client keypair. You can do this at runtime. Never reuse the same long nonce for two different keypairs.
(d) Make your server Listen() on a TCP socket, and Accept() incoming connections to obtain one or more server net.Conn.
(e) Make your clients Connect() on a TCP socket to the Listen() address of the server.
(f) On your client, right after Connect(), wrap the net.Conn you received by using WrapClient() on that client net.Conn, and giving it the client keypair, its corresponding client long nonce, and the server public key. WrapClient() will return an encrypted socket you can use to talk to the server.
(g) On your server, right after Accept(), wrap the net.Conn you received by using WrapServer() on that server net.Conn, and giving it the server keypair together with its corresponding server long nonce. Use the authorizer and the public key that WrapServer() returns to decide whether to call Allow() or Deny() on the authorizer. Allow() will return an encrypted socket you can use to talk to the client.
Congratulations, at this point you have a connection between peers that is encrypted with (a limited version of) the CurveZMQ protocol.
Sending and receiving traffic is covered by the documentation of the Read(), ReadFrame() and Write() methods of EncryptedConn. Two example programs are included in the cmd/ directory of this package.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GenKeyPair ¶
GenKeyPair generates a pair of private and public keys as Privkey and Pubkey structs.
It is safe to invoke this function concurrently.
func IsAuthenticationError ¶
IsAuthenticationError returns true when the error returned by WrapClient() was caused by the server rejecting the client for authentication reasons with Deny().
func NewLongNonce ¶
func NewLongNonce() (*longNonce, error)
NewLongNonce generates a long nonce for use with curvetls.WrapServer and curvetls.WrapClient. A long nonce is needed and must be unique per long-term private key, whether the private key belongs to the server or the client. Long nonces must not be reused for new private keys.
func WrapServer ¶
func WrapServer(conn net.Conn, serverprivkey Privkey, serverpubkey Pubkey, long_nonce *longNonce) (*Authorizer, Pubkey, error)
WrapServer wraps an existing, connected net.Conn with encryption and framing.
Returned Values ¶
An Authorizer object with two methods Allow() and Deny(), one of which you must call. Allow() will return an EncryptedConn (a net.Conn compatible object) that you can use to send and receive data. See the documentation of Authorizer for more information.
The public key of the client; use this key to authenticate the client and decide whether it is authorized to continue the conversation, then either call Allow() to signal to the client that it is authorized, or call Deny() to signal to the client that it is not authorized and terminate the connection.
An error. It can be an underlying socket error, an internal error produced by a bug in the library, or a protocol error indicating that the communication encountered corrupt or malformed data from the peer.
Lifecycle Information ¶
If WrapServer() returns an error, the passed socket will have been closed by the time this function returns.
If you read or write any data to the underlying socket rather than go through the returned socket, your data will be transmitted in plaintext and the endpoint will become confused and close the connection. Don't do that.
Types ¶
type Authorizer ¶
type Authorizer struct {
// contains filtered or unexported fields
}
Authorizer is returned by WrapServer() together with the connecting client's public key. It lets your server make an authorization decision with respect to the client.
By the time the caller of WrapServer() has received the authorizer and the client's public key, the client has been authenticated (to mean: the server knows the client truthfully holds its keypair). At that point your server can invoke an authorization service of your choice to decide whether the client is authorized to proceed.
To signal to the client that it is authorized to proceed, call Allow() on the authorizer. This returns an EncryptedConn that lets your server communicate with the client securely.
Conversely, to signal to the client that it is not authorized, call Deny() on the authorizer.
You must call one of the two methods. Failure to do so will leave the client hanging, and will leak file descriptors on the server.
See the documentation of Allow() and Deny() for important information.
func (*Authorizer) Allow ¶
func (c *Authorizer) Allow() (*EncryptedConn, error)
Allow signals the client that it is authorized, finishing the handshake and returning an EncryptedConn to talk to the client.
Returned Values ¶
An EncryptedConn (a net.Conn compatible object) that you can use to send and receive data. Data sent and received will be framed and encrypted.
Upon successful return of this function, the Close() method of the returned net.Conn will also Close() the underlying net.Conn.
Lifecycle Information ¶
If Allow() returns an error, the passed socket will have been closed by the time this function returns.
func (*Authorizer) Deny ¶
func (c *Authorizer) Deny() error
Deny, signals the client that it is not authorized to continue, and closes the underlying socket passed to WrapServer.
Lifecycle Information ¶
When Deny() returns, the underlying socket will have been closed too.
Clients which receive a Deny() denial SHALL NOT reconnect with the same credentials, but wise implementors know that hostile clients can do what they want, so they will need to implement throttling based on public key. WrapServer() returns the verified public key of the client before the server has made an authentication policy decision, so the server can implement throttling based on client public key.
type EncryptedConn ¶
type EncryptedConn struct { net.Conn // FIXME reorg struct to make it more efficient, and that which is accessed frequenly should be clustered together, and measure perf diff // contains filtered or unexported fields }
EncryptedConn is the opaque structure representing an encrypted connection.
On the client, use WrapClient() to obtain one. On the server, use WrapServer() to obtain an Authorizer and then invoke Allow() on the authorizer to obtain an EncryptedConn.
Then, use the EncryptedConn methods to engage in secure communication.
EncryptedConn implemenets the net.Conn interface.
Lifecycle Information ¶
In general, it is not thread safe to perform reads or writes on an EncryptedConn while any part of a handshake (WrapClient(), WrapServer(), Allow() or Deny()) is going on in a different goroutine. You should complete the handshakes on a single goroutine. It is also not safe to perform reads simultaneously on two or more goroutines. It is also not safe to perform writes simultaneously on two or more goroutines. It is also not safe to intersperse calls to Read() and ReadFrame(), even from the same goroutine.
Concurrent things that are safe: (1) one read and one write each on a distinct goroutine (2) same as (1) while Close() is invoked on another goroutine (the ongoing read and write should return normally with an EOF or UnexpectedEOF in that case). (3) performing any operation on one EncryptedConn in a single goroutine, while any other operation on another EncryptedConn is ongoing in another goroutine. There is no global mutable state shared among EncryptedConn instances.
func WrapClient ¶
func WrapClient(conn net.Conn, clientprivkey Privkey, clientpubkey Pubkey, permServerPubkey Pubkey, long_nonce *longNonce) (*EncryptedConn, error)
WrapClient wraps an existing, connected net.Conn with encryption and framing.
Returned Values ¶
An EncryptedConn (a net.Conn compatible object) that you can use to send and receive data. Data sent and received will be framed and encrypted.
An error. It can be an underlying socket error, an internal error produced by a bug in the library, a protocol error indicating that the communication encountered corrupt or malformed data from the peer, or an authentication error. A method to distinguish authentication errors is provided by the IsAuthenticationError() function. No method is provided to distinguish among the other errors because the only sane thing to do at that point is to close the connection.
Lifecycle Information ¶
If WrapClient() returns an error, the passed socket will have been closed by the time this function returns.
Upon successful return of this function, the Close() method of the returned net.Conn will also Close() the passed net.Conn.
Upon unauthorized use (the server Authorizer rejects the client with Deny()) this function will return an error which can be checked with the function IsAuthenticationError(). See note on Deny() to learn more about reconnection policy.
If you read or write any data to the underlying socket rather than go through the returned socket, your data will be transmitted in plaintext and the endpoint will become confused and close the connection. Don't do that.
func (*EncryptedConn) Read ¶
func (w *EncryptedConn) Read(b []byte) (int, error)
Read reads one frame from the other side, decrypts the encrypted frame, then copies the bytes read to the passed slice.
If the destination buffer is not large enough to contain the whole received frame, then a partial read is made and written to the buffer, and subsequent Read() calls will continue reading the remainder of that frame.
When the peer has closed the socket, Read() will return a standard EOF.
Lifecycle Information ¶
If Read() returns an error, the socket remains technically open, but (much like TLS) it is highly unlikely that, after your program receives the error, the connection will continue working.
It is an error to invoke an EncryptedConn's Read() from a goroutine while another goroutine is invoking Read() or ReadFrame() on the same EncryptedConn. Even with plain old sockets, you'd get nothing but corrupted reads that way. It should, however, be safe to invoke Read() on an EncryptedConn within one goroutine while another goroutine invokes Write() on the same EncryptedConn.
func (*EncryptedConn) ReadFrame ¶
func (w *EncryptedConn) ReadFrame() ([]byte, error)
ReadFrame reads one frame from the other side, decrypts the encrypted frame, then returns the whole frame as a slice of bytes.
When the peer has closed the socket, ReadFrame() will return a standard EOF.
Lifecycle Information ¶
If ReadFrame() returns an error, the socket remains technically open, but (much like TLS) it is highly unlikely that, after your program receives the error, the connection will continue working.
It is an error to call ReadFrame when a previous Read was only partially written to its output buffer.
It is an error to invoke an EncryptedConn's ReadFrame() from a goroutine while another goroutine is invoking ReadFrame() or Read() on the same EncryptedConn. Even with plain old sockets, you'd get nothing but corruption that way. It should, however, be safe to invoke ReadFrame() on an EncryptedConn within one goroutine while another goroutine invokes Write() on the same EncryptedConn.
func (*EncryptedConn) Write ¶
func (w *EncryptedConn) Write(b []byte) (int, error)
Write frames, encrypts and sends to the other side the passed bytes.
If this function returns an error, the socket remains open, but (much like TLS) it is highly unlikely that, after returning an error, the connection will continue working.
It is an error to invoke Write() on the same EncryptedConn simultaneously from two goroutines. Even with plain old sockets, you'd get nothing but corruption that way. It should, however, be safe to invoke Write() on an EncryptedConn within one goroutine while another goroutine invokes Read() or ReadFrame() on the same EncryptedConn.
type Privkey ¶
type Privkey key
Privkey is an opaque type representing a private key as used in curvetls.
func PrivkeyFromString ¶
PrivkeyFromString deserializes a Privkey as supplied in the string. See Privkey.String() for information on the string format of Privkeys.
type Pubkey ¶
type Pubkey key
Pubkey is an opaque type representing a public key as used in curvetls.
func PubkeyFromString ¶
PubkeyFromString deserializes a Pubkey as supplied in the string. See Pubkey.String() for information on the string format of Pubkeys.
String format of Privkey is the letter p plus a base64 rendering of 32 bytes.