295 lines
7.9 KiB
Markdown
295 lines
7.9 KiB
Markdown
# SecLink — Secure Serial Link Library
|
|
|
|
SecLink is a convenience wrapper that ties together three lower-level
|
|
libraries into a single API for reliable, optionally encrypted serial
|
|
communication:
|
|
|
|
- **rs232** — ISR-driven UART I/O with ring buffers and flow control
|
|
- **packet** — HDLC-style framing with CRC-16 and sliding window reliability
|
|
- **security** — 1024-bit Diffie-Hellman key exchange and XTEA-CTR encryption
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Application
|
|
|
|
|
[secLink] channels, optional encryption
|
|
|
|
|
[packet] framing, CRC, retransmit, ordering
|
|
|
|
|
[rs232] ISR-driven UART, ring buffers, flow control
|
|
|
|
|
UART
|
|
```
|
|
|
|
SecLink adds a one-byte header to every packet:
|
|
|
|
```
|
|
Bit 7 Bits 6..0
|
|
----- ---------
|
|
Encrypt Channel (0-127)
|
|
```
|
|
|
|
This allows mixing encrypted and cleartext traffic on up to 128
|
|
independent logical channels over a single serial link.
|
|
|
|
## Lifecycle
|
|
|
|
```
|
|
secLinkOpen() Open COM port and packet layer
|
|
secLinkHandshake() DH key exchange (blocks until complete)
|
|
secLinkSend() Send a packet (encrypted or clear)
|
|
secLinkPoll() Receive and deliver packets to callback
|
|
secLinkClose() Tear down everything
|
|
```
|
|
|
|
The handshake is only required if you intend to send encrypted packets.
|
|
Cleartext packets can be sent immediately after `secLinkOpen()`.
|
|
|
|
## API Reference
|
|
|
|
### Types
|
|
|
|
```c
|
|
// Receive callback — called for each incoming packet with plaintext
|
|
typedef void (*SecLinkRecvT)(void *ctx, const uint8_t *data, int len, uint8_t channel);
|
|
|
|
// Opaque connection handle
|
|
typedef struct SecLinkS SecLinkT;
|
|
```
|
|
|
|
### Constants
|
|
|
|
| Name | Value | Description |
|
|
|-------------------------|-------|----------------------------------------|
|
|
| `SECLINK_MAX_PAYLOAD` | 255 | Max bytes per `secLinkSend()` call |
|
|
| `SECLINK_MAX_CHANNEL` | 127 | Highest valid channel number |
|
|
| `SECLINK_SUCCESS` | 0 | Operation succeeded |
|
|
| `SECLINK_ERR_PARAM` | -1 | Invalid parameter |
|
|
| `SECLINK_ERR_SERIAL` | -2 | Serial port error |
|
|
| `SECLINK_ERR_ALLOC` | -3 | Memory allocation failed |
|
|
| `SECLINK_ERR_HANDSHAKE` | -4 | Key exchange failed |
|
|
| `SECLINK_ERR_NOT_READY` | -5 | Encryption requested before handshake |
|
|
| `SECLINK_ERR_SEND` | -6 | Packet layer send failed |
|
|
|
|
### Functions
|
|
|
|
#### secLinkOpen
|
|
|
|
```c
|
|
SecLinkT *secLinkOpen(int com, int32_t bps, int dataBits, char parity,
|
|
int stopBits, int handshake,
|
|
SecLinkRecvT callback, void *ctx);
|
|
```
|
|
|
|
Opens the COM port via rs232, creates the packet layer, and returns a
|
|
link handle. Returns `NULL` on failure. The callback is invoked from
|
|
`secLinkPoll()` for each received packet.
|
|
|
|
#### secLinkClose
|
|
|
|
```c
|
|
void secLinkClose(SecLinkT *link);
|
|
```
|
|
|
|
Destroys cipher contexts, closes the packet layer and COM port, and
|
|
frees all memory.
|
|
|
|
#### secLinkHandshake
|
|
|
|
```c
|
|
int secLinkHandshake(SecLinkT *link);
|
|
```
|
|
|
|
Performs a Diffie-Hellman key exchange. Blocks until both sides have
|
|
exchanged public keys and derived cipher keys. The RNG must be seeded
|
|
(via `secRngSeed()` or `secRngAddEntropy()`) before calling this.
|
|
|
|
Each side derives separate TX and RX keys from the shared secret,
|
|
using public key ordering to determine directionality. This prevents
|
|
CTR counter collisions.
|
|
|
|
#### secLinkGetPending
|
|
|
|
```c
|
|
int secLinkGetPending(SecLinkT *link);
|
|
```
|
|
|
|
Returns the number of unacknowledged packets in the transmit window.
|
|
Useful for non-blocking send loops to determine if there is room to
|
|
send more data.
|
|
|
|
#### secLinkIsReady
|
|
|
|
```c
|
|
bool secLinkIsReady(SecLinkT *link);
|
|
```
|
|
|
|
Returns `true` if the handshake is complete and the link is ready for
|
|
encrypted communication.
|
|
|
|
#### secLinkPoll
|
|
|
|
```c
|
|
int secLinkPoll(SecLinkT *link);
|
|
```
|
|
|
|
Reads available serial data, processes received frames, handles ACKs
|
|
and retransmits. Decrypts encrypted packets and delivers plaintext to
|
|
the receive callback. Returns the number of packets delivered, or
|
|
negative on error.
|
|
|
|
Must be called frequently (e.g. in your main loop).
|
|
|
|
#### secLinkSend
|
|
|
|
```c
|
|
int secLinkSend(SecLinkT *link, const uint8_t *data, int len,
|
|
uint8_t channel, bool encrypt, bool block);
|
|
```
|
|
|
|
Sends up to `SECLINK_MAX_PAYLOAD` (255) bytes on the given channel.
|
|
|
|
- `channel` — logical channel number (0-127)
|
|
- `encrypt` — if `true`, encrypts the payload (requires completed handshake)
|
|
- `block` — if `true`, waits for transmit window space; if `false`,
|
|
returns `SECLINK_ERR_SEND` when the window is full
|
|
|
|
Cleartext packets (`encrypt = false`) can be sent before the handshake.
|
|
|
|
#### secLinkSendBuf
|
|
|
|
```c
|
|
int secLinkSendBuf(SecLinkT *link, const uint8_t *data, int len,
|
|
uint8_t channel, bool encrypt);
|
|
```
|
|
|
|
Sends an arbitrarily large buffer by splitting it into
|
|
`SECLINK_MAX_PAYLOAD`-byte chunks. Always blocks until all data is
|
|
sent. Returns `SECLINK_SUCCESS` or the first error encountered.
|
|
|
|
## Examples
|
|
|
|
### Basic encrypted link
|
|
|
|
```c
|
|
#include "secLink.h"
|
|
#include "../security/security.h"
|
|
|
|
void onRecv(void *ctx, const uint8_t *data, int len, uint8_t channel) {
|
|
// handle received plaintext on 'channel'
|
|
}
|
|
|
|
int main(void) {
|
|
// Seed the RNG before handshake
|
|
uint8_t entropy[16];
|
|
secRngGatherEntropy(entropy, sizeof(entropy));
|
|
secRngSeed(entropy, sizeof(entropy));
|
|
|
|
// Open link on COM1 at 115200 8N1
|
|
SecLinkT *link = secLinkOpen(0, 115200, 8, 'N', 1, 0, onRecv, NULL);
|
|
if (!link) {
|
|
return 1;
|
|
}
|
|
|
|
// Key exchange (blocks until both sides complete)
|
|
if (secLinkHandshake(link) != SECLINK_SUCCESS) {
|
|
secLinkClose(link);
|
|
return 1;
|
|
}
|
|
|
|
// Send encrypted data on channel 0
|
|
const char *msg = "Hello, secure world!";
|
|
secLinkSend(link, (const uint8_t *)msg, strlen(msg), 0, true, true);
|
|
|
|
// Main loop
|
|
while (1) {
|
|
secLinkPoll(link);
|
|
}
|
|
|
|
secLinkClose(link);
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
### Mixed encrypted and cleartext channels
|
|
|
|
```c
|
|
#define CHAN_CONTROL 0 // cleartext control channel
|
|
#define CHAN_DATA 1 // encrypted data channel
|
|
|
|
// Send a cleartext status message (no handshake needed)
|
|
secLinkSend(link, statusMsg, statusLen, CHAN_CONTROL, false, true);
|
|
|
|
// Send encrypted payload (requires completed handshake)
|
|
secLinkSend(link, payload, payloadLen, CHAN_DATA, true, true);
|
|
```
|
|
|
|
### Non-blocking file transfer
|
|
|
|
```c
|
|
int sendFile(SecLinkT *link, const uint8_t *fileData, int fileSize,
|
|
uint8_t channel, bool encrypt, int windowSize) {
|
|
int offset = 0;
|
|
int bytesLeft = fileSize;
|
|
|
|
while (bytesLeft > 0) {
|
|
secLinkPoll(link); // process ACKs, free window slots
|
|
|
|
if (secLinkGetPending(link) < windowSize) {
|
|
int chunk = bytesLeft;
|
|
if (chunk > SECLINK_MAX_PAYLOAD) {
|
|
chunk = SECLINK_MAX_PAYLOAD;
|
|
}
|
|
|
|
int rc = secLinkSend(link, fileData + offset, chunk,
|
|
channel, encrypt, false);
|
|
if (rc == SECLINK_SUCCESS) {
|
|
offset += chunk;
|
|
bytesLeft -= chunk;
|
|
}
|
|
// SECLINK_ERR_SEND means window full, just retry next iteration
|
|
}
|
|
|
|
// Application can do other work here:
|
|
// update progress bar, check for cancel, etc.
|
|
}
|
|
|
|
// Drain remaining ACKs
|
|
while (secLinkGetPending(link) > 0) {
|
|
secLinkPoll(link);
|
|
}
|
|
|
|
return SECLINK_SUCCESS;
|
|
}
|
|
```
|
|
|
|
### Blocking bulk transfer
|
|
|
|
```c
|
|
// Send an entire file in one call (blocks until complete)
|
|
secLinkSendBuf(link, fileData, fileSize, CHAN_DATA, true);
|
|
```
|
|
|
|
## Building
|
|
|
|
```
|
|
make # builds ../lib/libseclink.a
|
|
make clean # removes objects and library
|
|
```
|
|
|
|
Link against all four libraries:
|
|
|
|
```
|
|
-lseclink -lpacket -lsecurity -lrs232
|
|
```
|
|
|
|
## Dependencies
|
|
|
|
SecLink requires these libraries (all in `../lib/`):
|
|
|
|
- `librs232.a` — serial port driver
|
|
- `libpacket.a` — packet framing and reliability
|
|
- `libsecurity.a` — DH key exchange and XTEA cipher
|
|
|
|
Target: DJGPP cross-compiler, 486+ CPU.
|