protocol

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Jun 7, 2023 License: MIT Imports: 7 Imported by: 0

README

Protocol

goe2ee protocol aims to give a similar security level to TLS 1.3, using Diffie–Hellman key exchange, with some flexibility regarding sharing secrets between multiple connections from the same host. The key sharing should save handshakes, improving the initial performance.

For example, the full handshake can be described as:

sequenceDiagram
activate client
client->>server: sends auto-generate ECDH public-key + random id
activate server
server->>server: calculate shared secret and stores with id
server-->>client: sends auto-generate ECDH public-key signed
deactivate server
client->>client: validate signature
client->>client: calculate shared secret
loop every message
  client->>server: id + encypted message with shared secret
  activate server
  server-->>client: encrypted message with shared secret
  deactivate server
end
deactivate client

While other clients from the same host could share the same secret and jump directly to the encrypted message phase:

sequenceDiagram
loop every message
  client->>server: id + encypted message with shared secret
  activate server
  server-->>client: encrypted message with shared secret
  deactivate server
end

The protocol was also designed to work with both TCP and UDP protocols. For some application scenarios where the data isn't big and a delivery guarantee is not a requirement, the use of UDP could bring great performance benefits.

Finally, the protocol allows the client to retrieve the server public key using many different strategies, throwing light on certification authority alternatives, such as the DNSSEC chain of trust.

Structure

All requests follow the pattern:

+------------------|-----------------|-------------------+
| version (4 bits) | action (4 bits) | message (n bytes) |
+------------------|-----------------|-------------------+

Where:

  • version defines the protocol version. This is designed to allow up to 16 versions of the protocol;
  • action is an enumerate that tells the server what this request is about (4 bits will allow up to 16 actions);
  • message changes according to the action, it's optional.

The following actions are accepted:

Action Description
0x1 Hello
0x2 Setup
0x3 Process
0x4 Fetch key

Having a version will allow evolving of the protocol as new approaches are identified. This fixed header allows skipping the handshake stages if the shared secret was already agreed upon previously because the client doesn't need to follow a specific order of actions with the server. On the other hand, the server needs to keep a state of the shared secrets associated with IDs even after the client connection is closed. The technical approach for handling this cache can be decided by the server implementation.

Once the request is processed correctly, all server responses will follow the pattern:

+-----------------+-------------------+-------------------+
| success (1 bit) | reserved (7 bits) | message (n bytes) |
+-----------------+-------------------+-------------------+

Where:

  • success is an enabled flag (1) to indicate success;
  • reserved these flags are reserved for future use;
  • message changes according to the request action, it's optional.

Otherwise, if the server rejected the request for any reason, the response will follow the pattern below. Any request can return a failed response.

+-----------------+-------------------+----------------------+------------------------------+-------------------------+
| success (1 bit) | reserved (7 bits) | error-code (4 bytes) | error-message-size (8 bytes) | error-message (n bytes) |
+-----------------+-------------------+----------------------+------------------------------+-------------------------+

Where:

  • success is a disabled flag (0) to indicate failure;
  • error-code identifies the type of error that happened;
  • error-message-size defines the error message size. The maximum message size is an unsigned 64-bit number;
  • error-message will contain details of the error, it's optional.

The following error codes are accepted:

Error Code Description
0x01 Malformed request
0x02 Server error
0x03 Unknown client
0x04 Unsupported version

The structure of each action can be found in the following sections.

Hello (action 0x1)

Used to check if the connection with the server is still valid. It's the most basic mechanism of the protocol.

Request:

+------|------+
| 0001 | 0001 |
+------+------+

Response:

+---+-------------------+
| 1 | reserved (7 bits) |
+---+-------------------+
Setup (action 0x2)

This is the main action of the protocol, where the client and server exchange public keys so a new shared secret can be generated. The use of the Diffie–Hellman key exchange guarantees that the shared secret will never be carried over the network, increasing security.

At this stage, the client also provides an identifier to be associated with the shared secret. This identifier will be used on the server side to correctly select the client-shared secret to decrypt the message. It MUST be unique per host.

Request:

+------+------+---------------+---------------------------+----------------------+
| 0001 | 0010 | id (16 bytes) | public-key-size (4 bytes) | public-key (n bytes) |
+------+------+---------------+---------------------------+----------------------+

Where:

  • id is a client-unique identifier for the secret following RFC 4122. A client could have multiple connections with the same server, each one with a different secret, so an identifier is required to uniquely identify each client instance. This should also cover scenarios where there're network boxes between the client and server, such as proxies.
  • public-key is encoded in PKIX, ASN.1 DER form. The encoded public key is a SubjectPublicKeyInfo structure (see RFC 5280, Section 4.1). This is the ECDH key generated by the client on-the-fly.

Response:

+---+-------------------+---------------------------+----------------------+--------------------+--------------------------+---------------------+
| 1 | reserved (7 bits) | public-key-size (4 bytes) | public-key (n bytes) | hash-type (1 byte) | signature-size (8 bytes) | signature (n bytes) |
+---+-------------------+---------------------------+----------------------+--------------------+--------------------------+---------------------+
  • public-key is encoded in PKIX, ASN.1 DER form. The encoded public key is a SubjectPublicKeyInfo structure (see RFC 5280, Section 4.1). This is the ECDH key generated by the server on-the-fly.
  • hash-type is the hash type used when generating the signature.
  • signature is the message signed with the server's global-key. The client should retrieve the server's public key somehow before the handshake.

The following hash types are accepted:

Type Description
0x1 SHA-1
0x2 SHA-256
0x3 SHA-384
0x4 SHA-512

The signature algorithm can follow the steps below.

  1. The server hashes the initial part of the message;
+---+-------------------+---------------------------+----------------------+
| 1 | reserved (7 bits) | public-key-size (4 bytes) | public-key (n bytes) |
+---+-------------------+---------------------------+----------------------+
  1. Using the server's private global-key, signs the hashed content and generates a signature;
  2. The server attaches to the hash type and the signature to the response;
  3. The client hashes the same part of the message using the provided hash type;
  4. Finally, the client validates the hashed content using the server's public global-key.
Process (action 0x3)

After all the handshake phase is done, is time to exchange encrypted messages using the shared secret. The client encrypts the message with the shared secret and sends the related identifier with the encrypted content. The response also contains the message encrypted with the same shared secret from the request.

Request:

+------+------+---------------------+-------------------------+---------------+----------------------------------+-----------------------------+
| 0001 | 0011 | expectReply (1 bit) | reserved-flags (7 bits) | id (16 bytes) | encrypted-message-size (8 bytes) | encrypted-message (n bytes) |
+------+------+---------------------+-------------------------+---------------+----------------------------------+-----------------------------+

Where:

  • expectedReply allows the client to inform the server that it doesn't expect a response. A typical fire-and-forget request.
  • reserved-flags are flags reserved for future use.
  • id identifies the client when sending encrypted messages. The identifier is generated by the client during the setup process and can be shared for requests from the same host.
  • encrypted-message is the payload encrypted with the shared secret associated with the provided identifier.

Response:

+---+-------------------+----------------------------------+-----------------------------+
| 1 | reserved (7 bits) | encrypted-message-size (8 bytes) | encrypted-message (n bytes) |
+---+-------------------+----------------------------------+-----------------------------+
Fetch key (action 0x4)

This is an alternative approach for the client to retrieve the server's public global-key. It's not recommended as it can be vulnerable to man-in-the-middle attacks.

Request:

+------+------+
| 0001 | 0100 |
+------+------+

Response:

+---+-------------------+------------------------+---------------------------+----------------------+
| 1 | reserved (7 bits) | key-algorithm (1 byte) | public-key-size (4 bytes) | public-key (n bytes) |
+---+-------------------+------------------------+---------------------------+----------------------+

Where:

  • key-algorithm stores the format for parsing the public key.
  • public-key is the public global-key of the server.

The following algorithms are accepted:

Algorithm Description
0x1 RSA
0x2 Ecdsa
0x3 Ed25519

Documentation

Overview

Package protocol provides the protocol used by the server and the client.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func WithProcessRequestExpectReply

func WithProcessRequestExpectReply(expectReply bool) func(*ProcessRequestOptions)

WithProcessRequestExpectReply indicates if the client is expecting a reply from the server. This is useful to avoid the client to wait for a reply when the server doesn't need to send one. By default is true.

Types

type Action

type Action uint8

Action is the type of action to be performed by the client/server.

const (
	ActionHello    Action = 0x1
	ActionSetup    Action = 0x2
	ActionProcess  Action = 0x3
	ActionFetchKey Action = 0x4
)

List of possible actions.

func (Action) String

func (a Action) String() string

String returns the string representation of the action.

type ErrorCode

type ErrorCode uint32

ErrorCode is the error code sent by the server.

const (
	ErrorCodeMalformedRequest   ErrorCode = 0x01
	ErrorCodeServerError        ErrorCode = 0x02
	ErrorCodeUnknownClient      ErrorCode = 0x03
	ErrorCodeUnsupportedVersion ErrorCode = 0x04
)

List of possible error codes.

type ErrorResponse

type ErrorResponse struct {
	ResponseCommon
	// contains filtered or unexported fields
}

ErrorResponse is the response sent by the server to the client when an error occurs.

func NewErrorResponse

func NewErrorResponse(errorCode ErrorCode, errorMessage string) ErrorResponse

NewErrorResponse creates a new ErrorResponse.

func ParseErrorResponse

func ParseErrorResponse(r io.Reader, responseCommon ResponseCommon) (ErrorResponse, error)

ParseErrorResponse parses an ErrorResponse received from the server.

func (ErrorResponse) Bytes

func (e ErrorResponse) Bytes() []byte

Bytes returns the byte representation of the response.

func (ErrorResponse) ErrorCode

func (e ErrorResponse) ErrorCode() ErrorCode

ErrorCode returns the error code if the action failed.

func (ErrorResponse) ErrorMessage

func (e ErrorResponse) ErrorMessage() string

ErrorMessage returns the error message if the action failed.

type FetchKeyRequest

type FetchKeyRequest struct {
	RequestCommon
}

FetchKeyRequest is the request sent by the client to the server to retrieve the public key.

func NewFetchKeyRequest

func NewFetchKeyRequest() FetchKeyRequest

NewFetchKeyRequest creates a new FetchKeyRequest.

type FetchKeyResponse

type FetchKeyResponse struct {
	ResponseCommon
	// contains filtered or unexported fields
}

FetchKeyResponse is the response sent by the server to the client containing the public key.

func NewFetchKeyResponse

func NewFetchKeyResponse(keyAlgorithm KeyAlgorithm, publicKey []byte) FetchKeyResponse

NewFetchKeyResponse creates a new NewFetchKeyResponse.

func ParseFetchKeyResponse

func ParseFetchKeyResponse(r io.Reader, responseCommon ResponseCommon) (FetchKeyResponse, error)

ParseFetchKeyResponse parses a FetchKeyRespons received from a server.

func (FetchKeyResponse) Bytes

func (g FetchKeyResponse) Bytes() []byte

Bytes returns the byte representation of the FetchKeyResponse.

func (FetchKeyResponse) KeyAlgorithm

func (g FetchKeyResponse) KeyAlgorithm() KeyAlgorithm

KeyAlgorithm returns the algorithm used to generate the key-pair.

func (FetchKeyResponse) PublicKey

func (g FetchKeyResponse) PublicKey() []byte

PublicKey returns the public key.

type HashType

type HashType uint8

HashType is the hash type used for verifying the signature.

const (
	HashTypeSHA1   HashType = 0x1
	HashTypeSHA256 HashType = 0x2
	HashTypeSHA384 HashType = 0x3
	HashTypeSHA512 HashType = 0x4
)

Supported hash types.

func NewHashType

func NewHashType(h crypto.Hash) HashType

NewHashType creates a new HashType from the crypto.Hash.

func (HashType) CryptoHash

func (h HashType) CryptoHash() crypto.Hash

CryptoHash returns the crypto.Hash corresponding to the hash type.

func (HashType) String

func (h HashType) String() string

String returns the string representation of the hash type.

type HelloRequest

type HelloRequest struct {
	RequestCommon
}

HelloRequest is the request sent by the client to the server to keep the connection alive.

func NewHelloRequest

func NewHelloRequest() HelloRequest

NewHelloRequest creates a new HelloRequest.

type HelloResponse

type HelloResponse struct {
	ResponseCommon
}

HelloResponse is the response sent by the server to the client acknowledging that everything is fine.

func NewHelloResponse

func NewHelloResponse() HelloResponse

NewHelloResponse creates a new NewHelloResponse.

type KeyAlgorithm

type KeyAlgorithm uint8

KeyAlgorithm is the algorithm used to generate the key-pair.

const (
	KeyAlgorithmRSA     KeyAlgorithm = 0x1
	KeyAlgorithmECDSA   KeyAlgorithm = 0x2
	KeyAlgorithmED25519 KeyAlgorithm = 0x3
)

Supported key algorithms.

func (KeyAlgorithm) String

func (k KeyAlgorithm) String() string

String returns the string representation of the key algorithm.

type ProcessRequest

type ProcessRequest struct {
	RequestCommon
	// contains filtered or unexported fields
}

ProcessRequest is the request sent by the client to the server with a generic encrypted message.

func NewProcessRequest

func NewProcessRequest(
	id [16]byte,
	message []byte,
	optFuncs ...func(*ProcessRequestOptions),
) ProcessRequest

NewProcessRequest creates a new ProcessRequest.

func ParseProcessRequest

func ParseProcessRequest(requestCommon RequestCommon, r io.Reader) (ProcessRequest, error)

ParseProcessRequest parses a ProcessRequest received from a client. The request common part must have already been read. This could return an io.EOF error if the response is not complete.

func (ProcessRequest) Bytes

func (g ProcessRequest) Bytes() []byte

Bytes returns the byte representation of the ProcessRequest.

func (ProcessRequest) ExpectReply

func (g ProcessRequest) ExpectReply() bool

ExpectReply returns true if the client is expecting a reply from the server.

func (ProcessRequest) ID

func (g ProcessRequest) ID() [16]byte

ID returns the id of the shared secret.

func (ProcessRequest) Message

func (g ProcessRequest) Message() []byte

Message returns the encrypted message.

type ProcessRequestOptions

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

ProcessRequestOptions contains the options for a ProcessRequest.

type ProcessResponse

type ProcessResponse struct {
	ResponseCommon
	// contains filtered or unexported fields
}

ProcessResponse is the response sent by the server to the client after generating a shared message.

func NewProcessResponse

func NewProcessResponse(message []byte) ProcessResponse

NewProcessResponse creates a new NewProcessResponse.

func ParseProcessResponse

func ParseProcessResponse(r io.Reader, responseCommon ResponseCommon) (ProcessResponse, error)

ParseProcessResponse parses a ProcessRespons received from a server. This could return an io.EOF error if the response is not complete.

func (ProcessResponse) Bytes

func (g ProcessResponse) Bytes() []byte

Bytes returns the byte representation of the ProcessResponse.

func (ProcessResponse) Message

func (g ProcessResponse) Message() []byte

Message returns the encrypted message.

type RequestCommon

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

RequestCommon is the common fields for all requests.

func ParseRequestCommon

func ParseRequestCommon(r io.Reader) (RequestCommon, error)

ParseRequestCommon parses the common fields of a request. It could return io.EOF if the client closes the connection.

func (RequestCommon) Action

func (r RequestCommon) Action() Action

Action returns the action to be performed.

func (RequestCommon) Bytes

func (r RequestCommon) Bytes() []byte

Bytes returns the byte representation of the request.

func (RequestCommon) Version

func (r RequestCommon) Version() Version

Version returns the protocol version.

type ResponseCommon

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

ResponseCommon is the common fields for all responses.

func ParseResponseCommon

func ParseResponseCommon(r io.Reader) (ResponseCommon, error)

ParseResponseCommon parses the common fields of a response. It could return io.EOF if the server closes the connection.

func (ResponseCommon) Bytes

func (r ResponseCommon) Bytes() []byte

Bytes returns the byte representation of the response.

func (ResponseCommon) Success

func (r ResponseCommon) Success() bool

Success returns true if the action was successful.

type SetupRequest

type SetupRequest struct {
	RequestCommon
	// contains filtered or unexported fields
}

SetupRequest is the request sent by the client to the server to register a shared secret.

func NewSetupRequest

func NewSetupRequest(id [16]byte, publicKey *ecdh.PublicKey) SetupRequest

NewSetupRequest creates a new SetupRequest.

func ParseSetupRequest

func ParseSetupRequest(requestCommon RequestCommon, r io.Reader) (SetupRequest, error)

ParseSetupRequest parses a SetupRequest received from a client. The request common part must have already been read.

func (SetupRequest) Bytes

func (s SetupRequest) Bytes() []byte

Bytes returns the byte representation of the SetupRequest.

func (SetupRequest) ID

func (s SetupRequest) ID() [16]byte

ID returns the id of the shared secret.

func (SetupRequest) PublicKey

func (s SetupRequest) PublicKey() *ecdh.PublicKey

PublicKey returns the public key for the Diffie-Hellman exchange.

type SetupResponse

type SetupResponse struct {
	ResponseCommon
	// contains filtered or unexported fields
}

SetupResponse is the response sent by the server to the client after generating a shared secret.

func NewSetupResponse

func NewSetupResponse(publicKey *ecdh.PublicKey) SetupResponse

NewSetupResponse creates a new SetupResponse.

func ParseSetupResponse

func ParseSetupResponse(responseCommon ResponseCommon, r io.Reader) (SetupResponse, error)

ParseSetupResponse parses a SetupResponse received from the server. The response common part must have already been read.

func (SetupResponse) Bytes

func (s SetupResponse) Bytes() []byte

Bytes returns the byte representation of the SetupResponse.

func (SetupResponse) HashType

func (s SetupResponse) HashType() HashType

HashType returns the hash type used to sign the response.

func (SetupResponse) PublicKey

func (s SetupResponse) PublicKey() *ecdh.PublicKey

PublicKey returns the public key for the Diffie-Hellman exchange.

func (*SetupResponse) SetSignature

func (s *SetupResponse) SetSignature(hashType HashType, signature []byte)

SetSignature sets the signature of the response.

func (SetupResponse) Signature

func (s SetupResponse) Signature() []byte

Signature prooves that the message was sent by the expected server, allowing to validate the response using the server's public key.

type Version

type Version uint8

Version is the protocol version.

const (
	Version1 Version = 1
)

List of supported versions.

Jump to

Keyboard shortcuts

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