mtgnet-encryption is an extenion to the cl-mtgnet client library for the MTGNet RPC protocol that provides encrypted connections.
Note: this extension currently depends on a version cl-mtgnet that is not yet available in quicklisp, and on the cl-sodium bindings for the Sodium library, which are still incomplete.
(cr:sodium-init) (let ((framer ...) (transport ..) (secret (mtgnet.encryption:generate-secret)))) (connection (make-encrypted-connection framer transport secret))) ;; Use the connection as usual. (mtgnet:connect connection) (mtgnet:invoke-rpc-method connection ...) ... ;; Secrets must be explicitly freed. (mtgnet.encryption:free-secret secret))
Once an encrypted-rpc-connection
instance is created, it is a
drop-in replacement for regular rpc-connection
connection objects,
except for the conditions it throws. General errors encrypting or
decrypting data will be signalled, and unauthorized client
connections will cause a client-not-authorized
error to be
signalled (see below).
(cr:sodium-init)
must be called before using Sodium functions are
called, or the process will be killed.
The protocol provided by this library aims to:
- be cryptographically strong
- offer authentication as a guard against man-in-the-middle attacks
- offer perfect forward secrecy to mitigate the effects of a future key compromise.
To accomplish this, the protocol uses a static signing keypair (Ed25519) to identify each party (providing authentication), and generates an ephemeral ECDH keypair (Curve25519) for each session. Libsodium is used to provide the cryptographic primitives.
On connection, each party should:
- Generate an ephemeral ECDH keypair.
- Sign the public part of the ephemeral keypair with their static signing key.
- Send a frame to the remote end consisting of their public signing key followed by the signed ephemeral public key.
After sending the initial handshake data to the remote end, each party should
- Read the public signing key and signed ephemeral public key from the remote end.
- Verify the signature on the ephemeral public key. If the public signing key is not recognized, or the signature fails validation, the connection should be dropped.
- Use the ephemeral public key from the remote end and the local
ephemeral secret key to encrypt all future frames in the session
using libsodium’s
crypto_box_easy
API or equivalent algorithms. Encrypted frames consist of the nonce used for encryption followed by the encrypted data.
The mtgnet.encryption
package provides the consumer-level API for
this module, using the primitives provided by
mtgnet.crypto
. Under normal circumstances, this is the only
package users should need to use.
The class of encrypted rpc connection objects returned by
make-encrypted-connection
.
(make-encrypted-connection framer transport secret-key &optional (authorized-keys '(t)))
constructs a new encrypted connection. framer
and transport
are the MTGNet framer and transport objects for the
connection. secret-key
is the secret key that is used to drive
the ECDH key exchange, and must be either a base64 string (which
will be decoded with mtgnet.encryption:decode-secret-key
), or a
secret object of the sort returned by
mtgnet.encryption:decode-secret-key
or
mtgnet.encryption:generate-secret
.
authorized-keys
is a list of public keys that are permitted to
do a key exchange over this connection. If the list contains t
(the default), any public key may be used in an exchange. This
offers a limited form of authentication for connections.
Secrets created by the library use memory-protected regions that
are allocated manually and not subject to garbage collection. You
must call free-secret
on any secrets produced by
generate-secret
or decode-secret
to deallocate them.
Since server’s construct a connection out of an already-connected
socket, they normally don’t call
mtgnet:connect
. perform-handshake
is used to perform the
connection handshake in that case (call this before you submit
calls over the connection).
+secret-size+
is the size in bytes of the private keys used in
the protocol, like those generated by generate secret
. Secrets
passed to make-encrypted-connection
must be this size.
+publickey-size+
is the size in byts of the public keys used in
the connection, like those returned by compute-public-key
.
Return a new secret object with a freshly-generated private key in
it. You must call free-secret
on the result when you’re done
with it.
Return a new private key as a base-64 encoded string. Useful for humans and config files.
Return a new secret object for the private key encoded in a
base-64 string, like those produced by generate-encoded-secret
.
Given a private key in a secret object, return the matching public key. Useful for reporting the public key (used for authentication) of the secret stored in a config file.
The mtgnet.crypto
package provides the low-level cryptographic
primitives used in the encryption protocol. Users should normally
not need anything in here.
(with-secret (ptr-var) body)
is a macro that takes a secret (a
foreign-pointer
) and allows read-only access to it for the
duration of body
, restoring the no-access memory protection to
it afterwards. All secret objects generated by this library have
no-access memory protection applied to them, and can only be read
in the context of a with-secret
body or the process will be
killed.
The library uses this macro where appropriate internally – don’t
call library functions inside the body of with-secret
or you may
get memory-access errors.
(ecdh-session-key secret public)
takes a private key in a secret
object and a public key, and precomputes a new ECDH session
key. The key is a precomputed key produced by sodium’s
crypto_box_beforenm
function. This key will be shared between
the local and remote ends, and can be used with
crypto_box_easy_afternm
and crypto_box_open_easy_afternm
to
encrypt data.
Given a keypair, return the secret key.
Given a keypair, return the public key.
Deallocate a keypair and set its parts to nil
to avoid
unintentional use.
Generate and return a new ECDH secret object. Note that this not
the sort of secret produced by mtgnet.encryption:generate-secret
(see the protocol section).
Given an ECDH private key in a secret object, return the matching public key.
Generate a new ECDH keypair.
Generate a new secret key used for signatures (see the protocol section).
Given a private signing key, return the matching public key.
Generate a new keypair to be used for signatures.
Same as generate-signing-secret
, except instead of returning a
secret object, it returns a base64-encoded string. Useful for
generating new keys for humans.
Takes a base64-encoded string of the secret data and returns a secret object for that data. Useful for reading secret from e.g. config files.
Given a private signing key and a byte-vector, returned the bytes with an attached signature from that private key.
Given a public key and set of bytes with an attached signature,
verify the signature with the public key and returned the signed
bytes. If the signature is invalid, signals an
invalid-signature-error
.
Condition signalled when the signature attached to some data fails validation for a public key.