Compare commits
29 Commits
Author | SHA1 | Date |
---|---|---|
Andri Yngvason | c700a2c02c | |
Andri Yngvason | 89b759c838 | |
Andri Yngvason | 3c3de5f323 | |
Andri Yngvason | 0f5d8e87de | |
Andri Yngvason | 135127dcc1 | |
Andri Yngvason | c4f48bc47d | |
Andri Yngvason | 923fa4a53a | |
Andri Yngvason | 24e4c5900a | |
Andri Yngvason | 1a15af0845 | |
Andri Yngvason | 6e97000f11 | |
Andri Yngvason | e7a24822a7 | |
Andri Yngvason | 7eb9882877 | |
Andri Yngvason | 103fbe996e | |
Andri Yngvason | b2ad06ae3c | |
Andri Yngvason | b7614f64a4 | |
Andri Yngvason | 1d2e6c05a9 | |
Andri Yngvason | 3f949d8e66 | |
Andri Yngvason | d418b33dd7 | |
Andri Yngvason | e65660aea0 | |
Andri Yngvason | 6869eb42e6 | |
Andri Yngvason | abf9cc54c0 | |
Andri Yngvason | 83ed3273ce | |
Andri Yngvason | 0199d87368 | |
Andri Yngvason | 727fd785c6 | |
Andri Yngvason | ff5ca722b1 | |
Andri Yngvason | 8cb4910d76 | |
Andri Yngvason | ef4be68e4f | |
Andri Yngvason | 8ddca0a197 | |
Andri Yngvason | f6336e02be |
|
@ -0,0 +1,25 @@
|
||||||
|
/* Copyright (c) 2023 Andri Yngvason
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
* PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#define BASE64_ENCODED_SIZE(x) ((((x) + 2) / 3) * 4 + 1)
|
||||||
|
#define BASE64_DECODED_MAX_SIZE(x) ((((x) + 3) / 4) * 3)
|
||||||
|
|
||||||
|
void base64_encode(char* dst, const uint8_t* src, size_t src_len);
|
||||||
|
ssize_t base64_decode(uint8_t* dst, const char* src);
|
|
@ -26,6 +26,10 @@
|
||||||
#include "neatvnc.h"
|
#include "neatvnc.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
#include "crypto.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef ENABLE_TLS
|
#ifdef ENABLE_TLS
|
||||||
#include <gnutls/gnutls.h>
|
#include <gnutls/gnutls.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -43,6 +47,13 @@ enum nvnc_client_state {
|
||||||
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_VERSION,
|
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_VERSION,
|
||||||
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_SUBTYPE,
|
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_SUBTYPE,
|
||||||
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_PLAIN_AUTH,
|
VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_PLAIN_AUTH,
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
VNC_CLIENT_STATE_WAITING_FOR_APPLE_DH_RESPONSE,
|
||||||
|
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY,
|
||||||
|
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CHALLENGE,
|
||||||
|
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CLIENT_HASH,
|
||||||
|
VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CREDENTIALS,
|
||||||
#endif
|
#endif
|
||||||
VNC_CLIENT_STATE_WAITING_FOR_INIT,
|
VNC_CLIENT_STATE_WAITING_FOR_INIT,
|
||||||
VNC_CLIENT_STATE_READY,
|
VNC_CLIENT_STATE_READY,
|
||||||
|
@ -53,6 +64,9 @@ struct stream;
|
||||||
struct aml_handler;
|
struct aml_handler;
|
||||||
struct aml_idle;
|
struct aml_idle;
|
||||||
struct nvnc_display;
|
struct nvnc_display;
|
||||||
|
struct crypto_key;
|
||||||
|
struct crypto_rsa_pub_key;
|
||||||
|
struct crypto_rsa_priv_key;
|
||||||
|
|
||||||
struct nvnc_common {
|
struct nvnc_common {
|
||||||
void* userdata;
|
void* userdata;
|
||||||
|
@ -93,6 +107,18 @@ struct nvnc_client {
|
||||||
struct encoder* encoder;
|
struct encoder* encoder;
|
||||||
uint32_t cursor_seq;
|
uint32_t cursor_seq;
|
||||||
int quality;
|
int quality;
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
struct crypto_key* apple_dh_secret;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
enum crypto_hash_type hash_type;
|
||||||
|
enum crypto_cipher_type cipher_type;
|
||||||
|
size_t challenge_len;
|
||||||
|
uint8_t challenge[32];
|
||||||
|
struct crypto_rsa_pub_key* pub;
|
||||||
|
} rsa;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
LIST_HEAD(nvnc_client_list, nvnc_client);
|
LIST_HEAD(nvnc_client_list, nvnc_client);
|
||||||
|
@ -132,6 +158,11 @@ struct nvnc {
|
||||||
void* auth_ud;
|
void* auth_ud;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
struct crypto_rsa_pub_key* rsa_pub;
|
||||||
|
struct crypto_rsa_priv_key* rsa_priv;
|
||||||
|
#endif
|
||||||
|
|
||||||
uint32_t n_damage_clients;
|
uint32_t n_damage_clients;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
struct crypto_key;
|
||||||
|
struct crypto_cipher;
|
||||||
|
struct crypto_hash;
|
||||||
|
struct crypto_rsa_pub_key;
|
||||||
|
struct crypto_rsa_priv_key;
|
||||||
|
struct vec;
|
||||||
|
|
||||||
|
enum crypto_cipher_type {
|
||||||
|
CRYPTO_CIPHER_INVALID = 0,
|
||||||
|
CRYPTO_CIPHER_AES128_ECB,
|
||||||
|
CRYPTO_CIPHER_AES_EAX,
|
||||||
|
CRYPTO_CIPHER_AES256_EAX,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum crypto_hash_type {
|
||||||
|
CRYPTO_HASH_INVALID = 0,
|
||||||
|
CRYPTO_HASH_MD5,
|
||||||
|
CRYPTO_HASH_SHA1,
|
||||||
|
CRYPTO_HASH_SHA256,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_data_entry {
|
||||||
|
uint8_t* data;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
void crypto_dump_base16(const char* msg, const uint8_t* bytes, size_t len);
|
||||||
|
void crypto_dump_base64(const char* msg, const uint8_t* bytes, size_t len);
|
||||||
|
|
||||||
|
void crypto_random(uint8_t* dst, size_t len);
|
||||||
|
|
||||||
|
// Key generation
|
||||||
|
struct crypto_key* crypto_key_new(int g, const uint8_t *p, uint32_t p_len,
|
||||||
|
const uint8_t* q, uint32_t q_len);
|
||||||
|
void crypto_key_del(struct crypto_key* key);
|
||||||
|
|
||||||
|
int crypto_key_g(const struct crypto_key* key);
|
||||||
|
uint32_t crypto_key_p(const struct crypto_key* key, uint8_t* dst,
|
||||||
|
uint32_t dst_size);
|
||||||
|
uint32_t crypto_key_q(const struct crypto_key* key, uint8_t* dst,
|
||||||
|
uint32_t dst_size);
|
||||||
|
|
||||||
|
struct crypto_key* crypto_keygen(void);
|
||||||
|
|
||||||
|
// Diffie-Hellman
|
||||||
|
struct crypto_key* crypto_derive_public_key(const struct crypto_key* priv);
|
||||||
|
struct crypto_key* crypto_derive_shared_secret(
|
||||||
|
const struct crypto_key* own_secret,
|
||||||
|
const struct crypto_key* remote_public_key);
|
||||||
|
|
||||||
|
// Ciphers
|
||||||
|
struct crypto_cipher* crypto_cipher_new(const uint8_t* enc_key,
|
||||||
|
const uint8_t* dec_key, enum crypto_cipher_type type);
|
||||||
|
void crypto_cipher_del(struct crypto_cipher* self);
|
||||||
|
|
||||||
|
bool crypto_cipher_encrypt(struct crypto_cipher* self, struct vec* dst,
|
||||||
|
uint8_t* mac, const uint8_t* src, size_t len,
|
||||||
|
const uint8_t* ad, size_t ad_len);
|
||||||
|
ssize_t crypto_cipher_decrypt(struct crypto_cipher* self, uint8_t* dst,
|
||||||
|
uint8_t* mac, const uint8_t* src, size_t len,
|
||||||
|
const uint8_t* ad, size_t ad_len);
|
||||||
|
|
||||||
|
// Hashing
|
||||||
|
struct crypto_hash* crypto_hash_new(enum crypto_hash_type type);
|
||||||
|
void crypto_hash_del(struct crypto_hash* self);
|
||||||
|
|
||||||
|
void crypto_hash_append(struct crypto_hash* self, const uint8_t* src,
|
||||||
|
size_t len);
|
||||||
|
void crypto_hash_digest(struct crypto_hash* self, uint8_t* dst,
|
||||||
|
size_t len);
|
||||||
|
|
||||||
|
void crypto_hash_one(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
|
||||||
|
const uint8_t* src, size_t src_len);
|
||||||
|
void crypto_hash_many(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
|
||||||
|
const struct crypto_data_entry *src);
|
||||||
|
|
||||||
|
// RSA
|
||||||
|
struct crypto_rsa_pub_key* crypto_rsa_pub_key_new(void);
|
||||||
|
void crypto_rsa_pub_key_del(struct crypto_rsa_pub_key*);
|
||||||
|
|
||||||
|
// Returns length in bytes
|
||||||
|
size_t crypto_rsa_pub_key_length(const struct crypto_rsa_pub_key* key);
|
||||||
|
|
||||||
|
struct crypto_rsa_pub_key* crypto_rsa_pub_key_import(const uint8_t* modulus,
|
||||||
|
const uint8_t* exponent, size_t size);
|
||||||
|
|
||||||
|
void crypto_rsa_pub_key_modulus(const struct crypto_rsa_pub_key* key,
|
||||||
|
uint8_t* dst, size_t dst_size);
|
||||||
|
void crypto_rsa_pub_key_exponent(const struct crypto_rsa_pub_key* key,
|
||||||
|
uint8_t* dst, size_t dst_size);
|
||||||
|
|
||||||
|
bool crypto_rsa_priv_key_import_pkcs1_der(struct crypto_rsa_priv_key* priv,
|
||||||
|
struct crypto_rsa_pub_key* pub, const uint8_t* key,
|
||||||
|
size_t size);
|
||||||
|
|
||||||
|
bool crypto_rsa_priv_key_load(struct crypto_rsa_priv_key* priv,
|
||||||
|
struct crypto_rsa_pub_key* pub, const char* path);
|
||||||
|
|
||||||
|
struct crypto_rsa_priv_key *crypto_rsa_priv_key_new(void);
|
||||||
|
void crypto_rsa_priv_key_del(struct crypto_rsa_priv_key*);
|
||||||
|
|
||||||
|
bool crypto_rsa_keygen(struct crypto_rsa_pub_key*, struct crypto_rsa_priv_key*);
|
||||||
|
|
||||||
|
ssize_t crypto_rsa_encrypt(struct crypto_rsa_pub_key* pub, uint8_t* dst,
|
||||||
|
size_t dst_size, const uint8_t* src, size_t src_size);
|
||||||
|
ssize_t crypto_rsa_decrypt(struct crypto_rsa_priv_key* priv, uint8_t* dst,
|
||||||
|
size_t dst_size, const uint8_t* src, size_t src_size);
|
|
@ -48,6 +48,9 @@
|
||||||
#define nvnc_trace(...)
|
#define nvnc_trace(...)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define NVNC_BASE64_ENCODED_SIZE(x) ((((x) + 2) / 3) * 4 + 1)
|
||||||
|
#define NVNC_BASE64_DECODED_MAX_SIZE(x) ((((x) + 3) / 4) * 3)
|
||||||
|
|
||||||
struct nvnc;
|
struct nvnc;
|
||||||
struct nvnc_client;
|
struct nvnc_client;
|
||||||
struct nvnc_desktop_layout;
|
struct nvnc_desktop_layout;
|
||||||
|
@ -154,10 +157,19 @@ void nvnc_set_client_cleanup_fn(struct nvnc_client* self, nvnc_client_fn fn);
|
||||||
void nvnc_set_cut_text_fn(struct nvnc*, nvnc_cut_text_fn fn);
|
void nvnc_set_cut_text_fn(struct nvnc*, nvnc_cut_text_fn fn);
|
||||||
void nvnc_set_desktop_layout_fn(struct nvnc* self, nvnc_desktop_layout_fn);
|
void nvnc_set_desktop_layout_fn(struct nvnc* self, nvnc_desktop_layout_fn);
|
||||||
|
|
||||||
|
/* TODO: Changes this interface so that we have enable_auth(auth_fn),
|
||||||
|
* set_tls_creds(key, cert), and has_tls() -> bool
|
||||||
|
*/
|
||||||
|
|
||||||
bool nvnc_has_auth(void);
|
bool nvnc_has_auth(void);
|
||||||
|
|
||||||
int nvnc_enable_auth(struct nvnc* self, const char* privkey_path,
|
int nvnc_enable_auth(struct nvnc* self, const char* privkey_path,
|
||||||
const char* cert_path, nvnc_auth_fn, void* userdata);
|
const char* cert_path, nvnc_auth_fn, void* userdata);
|
||||||
|
|
||||||
|
int nvnc_enable_auth2(struct nvnc* self, nvnc_auth_fn, void* userdata);
|
||||||
|
|
||||||
|
int nvnc_set_rsa_creds(struct nvnc* self, const char* private_key_path);
|
||||||
|
|
||||||
struct nvnc_fb* nvnc_fb_new(uint16_t width, uint16_t height,
|
struct nvnc_fb* nvnc_fb_new(uint16_t width, uint16_t height,
|
||||||
uint32_t fourcc_format, uint16_t stride);
|
uint32_t fourcc_format, uint16_t stride);
|
||||||
struct nvnc_fb* nvnc_fb_from_buffer(void* buffer, uint16_t width,
|
struct nvnc_fb* nvnc_fb_from_buffer(void* buffer, uint16_t width,
|
||||||
|
@ -230,3 +242,6 @@ void nvnc_set_cursor(struct nvnc*, struct nvnc_fb*, uint16_t width,
|
||||||
void nvnc_set_log_fn(nvnc_log_fn);
|
void nvnc_set_log_fn(nvnc_log_fn);
|
||||||
void nvnc_set_log_level(enum nvnc_log_level);
|
void nvnc_set_log_level(enum nvnc_log_level);
|
||||||
void nvnc__log(const struct nvnc_log_data*, const char* fmt, ...);
|
void nvnc__log(const struct nvnc_log_data*, const char* fmt, ...);
|
||||||
|
|
||||||
|
void nvnc_base64_encode(char* dst, const uint8_t* src, size_t src_len);
|
||||||
|
ssize_t nvnc_base64_decode(uint8_t* dst, const char* src);
|
||||||
|
|
|
@ -29,8 +29,11 @@ enum rfb_security_type {
|
||||||
RFB_SECURITY_TYPE_INVALID = 0,
|
RFB_SECURITY_TYPE_INVALID = 0,
|
||||||
RFB_SECURITY_TYPE_NONE = 1,
|
RFB_SECURITY_TYPE_NONE = 1,
|
||||||
RFB_SECURITY_TYPE_VNC_AUTH = 2,
|
RFB_SECURITY_TYPE_VNC_AUTH = 2,
|
||||||
|
RFB_SECURITY_TYPE_RSA_AES = 5,
|
||||||
RFB_SECURITY_TYPE_TIGHT = 16,
|
RFB_SECURITY_TYPE_TIGHT = 16,
|
||||||
RFB_SECURITY_TYPE_VENCRYPT = 19,
|
RFB_SECURITY_TYPE_VENCRYPT = 19,
|
||||||
|
RFB_SECURITY_TYPE_APPLE_DH = 30,
|
||||||
|
RFB_SECURITY_TYPE_RSA_AES256 = 129,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum rfb_security_handshake_result {
|
enum rfb_security_handshake_result {
|
||||||
|
@ -106,9 +109,14 @@ enum rfb_resize_status {
|
||||||
RFB_RESIZE_STATUS_REQUEST_FORWARDED = 4,
|
RFB_RESIZE_STATUS_REQUEST_FORWARDED = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum rfb_rsa_aes_cred_subtype {
|
||||||
|
RFB_RSA_AES_CRED_SUBTYPE_USER_AND_PASS = 1,
|
||||||
|
RFB_RSA_AES_CRED_SUBTYPE_ONLY_PASS = 2,
|
||||||
|
};
|
||||||
|
|
||||||
struct rfb_security_types_msg {
|
struct rfb_security_types_msg {
|
||||||
uint8_t n;
|
uint8_t n;
|
||||||
uint8_t types[1];
|
uint8_t types[0];
|
||||||
} RFB_PACKED;
|
} RFB_PACKED;
|
||||||
|
|
||||||
struct rfb_error_reason {
|
struct rfb_error_reason {
|
||||||
|
@ -237,3 +245,24 @@ struct rfb_ntp_msg {
|
||||||
uint8_t padding[3];
|
uint8_t padding[3];
|
||||||
uint32_t t0, t1, t2, t3;
|
uint32_t t0, t1, t2, t3;
|
||||||
} RFB_PACKED;
|
} RFB_PACKED;
|
||||||
|
|
||||||
|
struct rfb_apple_dh_server_msg {
|
||||||
|
uint16_t generator;
|
||||||
|
uint16_t key_size;
|
||||||
|
uint8_t modulus_and_key[0];
|
||||||
|
} RFB_PACKED;
|
||||||
|
|
||||||
|
struct rfb_apple_dh_client_msg {
|
||||||
|
uint8_t encrypted_credentials[128];
|
||||||
|
uint8_t public_key[0];
|
||||||
|
} RFB_PACKED;
|
||||||
|
|
||||||
|
struct rfb_rsa_aes_pub_key_msg {
|
||||||
|
uint32_t length;
|
||||||
|
uint8_t modulus_and_exponent[0];
|
||||||
|
} RFB_PACKED;
|
||||||
|
|
||||||
|
struct rfb_rsa_aes_challenge_msg {
|
||||||
|
uint16_t length;
|
||||||
|
uint8_t challenge[0];
|
||||||
|
} RFB_PACKED;
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Andri Yngvason
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
* PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stream.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
struct stream;
|
||||||
|
|
||||||
|
int stream_tcp_init(struct stream* self, int fd, stream_event_fn on_event,
|
||||||
|
void* userdata);
|
||||||
|
int stream_tcp_close(struct stream* self);
|
||||||
|
void stream_tcp_destroy(struct stream* self);
|
||||||
|
ssize_t stream_tcp_read(struct stream* self, void* dst, size_t size);
|
||||||
|
int stream_tcp_send(struct stream* self, struct rcbuf* payload,
|
||||||
|
stream_req_fn on_done, void* userdata);
|
||||||
|
int stream_tcp_send_first(struct stream* self, struct rcbuf* payload);
|
||||||
|
void stream_tcp_exec_and_send(struct stream* self,
|
||||||
|
stream_exec_fn exec_fn, void* userdata);
|
||||||
|
int stream_tcp_install_cipher(struct stream* self,
|
||||||
|
struct crypto_cipher* cipher);
|
|
@ -19,6 +19,11 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "sys/queue.h"
|
#include "sys/queue.h"
|
||||||
#include "rcbuf.h"
|
#include "rcbuf.h"
|
||||||
|
#include "vec.h"
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
#include "crypto.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
@ -45,6 +50,7 @@ enum stream_event {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stream;
|
struct stream;
|
||||||
|
struct crypto_cipher;
|
||||||
|
|
||||||
typedef void (*stream_event_fn)(struct stream*, enum stream_event);
|
typedef void (*stream_event_fn)(struct stream*, enum stream_event);
|
||||||
typedef void (*stream_req_fn)(void*, enum stream_req_status);
|
typedef void (*stream_req_fn)(void*, enum stream_req_status);
|
||||||
|
@ -86,6 +92,9 @@ struct stream {
|
||||||
uint32_t bytes_received;
|
uint32_t bytes_received;
|
||||||
|
|
||||||
bool cork;
|
bool cork;
|
||||||
|
|
||||||
|
struct crypto_cipher* cipher;
|
||||||
|
struct vec tmp_buf;
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef ENABLE_WEBSOCKET
|
#ifdef ENABLE_WEBSOCKET
|
||||||
|
@ -108,3 +117,9 @@ void stream_exec_and_send(struct stream* self, stream_exec_fn, void* userdata);
|
||||||
#ifdef ENABLE_TLS
|
#ifdef ENABLE_TLS
|
||||||
int stream_upgrade_to_tls(struct stream* self, void* context);
|
int stream_upgrade_to_tls(struct stream* self, void* context);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
int stream_upgrade_to_rsa_eas(struct stream* base,
|
||||||
|
enum crypto_cipher_type cipher_type,
|
||||||
|
const uint8_t* enc_key, const uint8_t* dec_key);
|
||||||
|
#endif
|
||||||
|
|
|
@ -51,6 +51,8 @@ pixman = dependency('pixman-1')
|
||||||
libturbojpeg = dependency('libturbojpeg', required: get_option('jpeg'))
|
libturbojpeg = dependency('libturbojpeg', required: get_option('jpeg'))
|
||||||
gnutls = dependency('gnutls', required: get_option('tls'))
|
gnutls = dependency('gnutls', required: get_option('tls'))
|
||||||
nettle = dependency('nettle', required: get_option('nettle'))
|
nettle = dependency('nettle', required: get_option('nettle'))
|
||||||
|
hogweed = dependency('hogweed', required: get_option('nettle'))
|
||||||
|
gmp = dependency('gmp', required: get_option('nettle'))
|
||||||
zlib = dependency('zlib')
|
zlib = dependency('zlib')
|
||||||
gbm = dependency('gbm', required: get_option('gbm'))
|
gbm = dependency('gbm', required: get_option('gbm'))
|
||||||
libdrm = dependency('libdrm', required: get_option('h264'))
|
libdrm = dependency('libdrm', required: get_option('h264'))
|
||||||
|
@ -93,6 +95,7 @@ sources = [
|
||||||
'src/encoder.c',
|
'src/encoder.c',
|
||||||
'src/cursor.c',
|
'src/cursor.c',
|
||||||
'src/logging.c',
|
'src/logging.c',
|
||||||
|
'src/base64.c',
|
||||||
]
|
]
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
@ -118,9 +121,11 @@ if gnutls.found()
|
||||||
config.set('ENABLE_TLS', true)
|
config.set('ENABLE_TLS', true)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if nettle.found()
|
if nettle.found() and hogweed.found() and gmp.found()
|
||||||
dependencies += nettle
|
dependencies += [ nettle, hogweed, gmp ]
|
||||||
enable_websocket = true
|
enable_websocket = true
|
||||||
|
config.set('HAVE_CRYPTO', true)
|
||||||
|
sources += ['src/crypto-nettle.c', 'src/stream-rsa-aes.c']
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt.h')
|
if host_system == 'linux' and get_option('systemtap') and cc.has_header('sys/sdt.h')
|
||||||
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
/* Copyright (c) 2023 Andri Yngvason
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
* PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "base64.h"
|
||||||
|
#include "neatvnc.h"
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define EXPORT __attribute__((visibility("default")))
|
||||||
|
|
||||||
|
static const char base64_enc_lut[] =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||||
|
|
||||||
|
static const uint8_t base64_validation_lut[256] = {
|
||||||
|
['A'] = 1, ['B'] = 1, ['C'] = 1, ['D'] = 1,
|
||||||
|
['E'] = 1, ['F'] = 1, ['G'] = 1, ['H'] = 1,
|
||||||
|
['I'] = 1, ['J'] = 1, ['K'] = 1, ['L'] = 1,
|
||||||
|
['M'] = 1, ['N'] = 1, ['O'] = 1, ['P'] = 1,
|
||||||
|
['Q'] = 1, ['R'] = 1, ['S'] = 1, ['T'] = 1,
|
||||||
|
['U'] = 1, ['V'] = 1, ['W'] = 1, ['X'] = 1,
|
||||||
|
['Y'] = 1, ['Z'] = 1, ['a'] = 1, ['b'] = 1,
|
||||||
|
['c'] = 1, ['d'] = 1, ['e'] = 1, ['f'] = 1,
|
||||||
|
['g'] = 1, ['h'] = 1, ['i'] = 1, ['j'] = 1,
|
||||||
|
['k'] = 1, ['l'] = 1, ['m'] = 1, ['n'] = 1,
|
||||||
|
['o'] = 1, ['p'] = 1, ['q'] = 1, ['r'] = 1,
|
||||||
|
['s'] = 1, ['t'] = 1, ['u'] = 1, ['v'] = 1,
|
||||||
|
['w'] = 1, ['x'] = 1, ['y'] = 1, ['z'] = 1,
|
||||||
|
['0'] = 1, ['1'] = 1, ['2'] = 1, ['3'] = 1,
|
||||||
|
['4'] = 1, ['5'] = 1, ['6'] = 1, ['7'] = 1,
|
||||||
|
['8'] = 1, ['9'] = 1, ['+'] = 1, ['/'] = 1,
|
||||||
|
['-'] = 1, ['_'] = 1, ['='] = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t base64_dec_lut[256] = {
|
||||||
|
['A'] = 0x00, ['B'] = 0x01, ['C'] = 0x02, ['D'] = 0x03,
|
||||||
|
['E'] = 0x04, ['F'] = 0x05, ['G'] = 0x06, ['H'] = 0x07,
|
||||||
|
['I'] = 0x08, ['J'] = 0x09, ['K'] = 0x0a, ['L'] = 0x0b,
|
||||||
|
['M'] = 0x0c, ['N'] = 0x0d, ['O'] = 0x0e, ['P'] = 0x0f,
|
||||||
|
['Q'] = 0x10, ['R'] = 0x11, ['S'] = 0x12, ['T'] = 0x13,
|
||||||
|
['U'] = 0x14, ['V'] = 0x15, ['W'] = 0x16, ['X'] = 0x17,
|
||||||
|
['Y'] = 0x18, ['Z'] = 0x19, ['a'] = 0x1a, ['b'] = 0x1b,
|
||||||
|
['c'] = 0x1c, ['d'] = 0x1d, ['e'] = 0x1e, ['f'] = 0x1f,
|
||||||
|
['g'] = 0x20, ['h'] = 0x21, ['i'] = 0x22, ['j'] = 0x23,
|
||||||
|
['k'] = 0x24, ['l'] = 0x25, ['m'] = 0x26, ['n'] = 0x27,
|
||||||
|
['o'] = 0x28, ['p'] = 0x29, ['q'] = 0x2a, ['r'] = 0x2b,
|
||||||
|
['s'] = 0x2c, ['t'] = 0x2d, ['u'] = 0x2e, ['v'] = 0x2f,
|
||||||
|
['w'] = 0x30, ['x'] = 0x31, ['y'] = 0x32, ['z'] = 0x33,
|
||||||
|
['0'] = 0x34, ['1'] = 0x35, ['2'] = 0x36, ['3'] = 0x37,
|
||||||
|
['4'] = 0x38, ['5'] = 0x39, ['6'] = 0x3a, ['7'] = 0x3b,
|
||||||
|
['8'] = 0x3c, ['9'] = 0x3d, ['+'] = 0x3e, ['/'] = 0x3f,
|
||||||
|
['-'] = 0x3e, ['_'] = 0x3f,
|
||||||
|
};
|
||||||
|
|
||||||
|
void base64_encode(char* dst, const uint8_t* src, size_t src_len)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
for (; i < src_len / 3; ++i) {
|
||||||
|
uint32_t tmp = 0;
|
||||||
|
tmp |= (uint32_t)src[i * 3 + 0] << 16;
|
||||||
|
tmp |= (uint32_t)src[i * 3 + 1] << 8;
|
||||||
|
tmp |= (uint32_t)src[i * 3 + 2];
|
||||||
|
|
||||||
|
dst[i * 4 + 0] = base64_enc_lut[tmp >> 18];
|
||||||
|
dst[i * 4 + 1] = base64_enc_lut[(tmp >> 12) & 0x3f];
|
||||||
|
dst[i * 4 + 2] = base64_enc_lut[(tmp >> 6) & 0x3f];
|
||||||
|
dst[i * 4 + 3] = base64_enc_lut[tmp & 0x3f];
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t rem = src_len % 3;
|
||||||
|
if (rem == 0) {
|
||||||
|
dst[i * 4] = '\0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t tmp = 0;
|
||||||
|
for (size_t r = 0; r < rem; ++r) {
|
||||||
|
size_t s = (2 - r) * 8;
|
||||||
|
tmp |= (uint32_t)src[i * 3 + r] << s;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t di = 0;
|
||||||
|
for (; di < rem + 1; ++di) {
|
||||||
|
size_t s = (3 - di) * 6;
|
||||||
|
dst[i * 4 + di] = base64_enc_lut[(tmp >> s) & 0x3f];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; di < 4; ++di) {
|
||||||
|
dst[i * 4 + di] = '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
dst[i * 4 + di] = '\0';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool base64_is_valid(const char* src)
|
||||||
|
{
|
||||||
|
for (int i = 0; src[i]; i++)
|
||||||
|
if (!base64_validation_lut[(uint8_t)src[i]])
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t base64_decode(uint8_t* dst, const char* src)
|
||||||
|
{
|
||||||
|
if (!base64_is_valid(src))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
size_t src_len = strcspn(src, "=");
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
for (; i < src_len / 4; ++i) {
|
||||||
|
uint32_t tmp = 0;
|
||||||
|
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 0]] << 18;
|
||||||
|
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 1]] << 12;
|
||||||
|
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 2]] << 6;
|
||||||
|
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + 3]];
|
||||||
|
|
||||||
|
dst[i * 3 + 0] = tmp >> 16;
|
||||||
|
dst[i * 3 + 1] = (tmp >> 8) & 0xff;
|
||||||
|
dst[i * 3 + 2] = tmp & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t rem = src_len % 4;
|
||||||
|
if (rem == 0)
|
||||||
|
return i * 3;
|
||||||
|
|
||||||
|
size_t di = 0;
|
||||||
|
|
||||||
|
uint32_t tmp = 0;
|
||||||
|
for (size_t r = 0; r < rem; ++r) {
|
||||||
|
size_t s = (3 - r) * 6;
|
||||||
|
tmp |= (uint32_t)base64_dec_lut[(uint8_t)src[i * 4 + r]] << s;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; di < (rem * 3) / 4; ++di) {
|
||||||
|
size_t s = (2 - di) * 8;
|
||||||
|
dst[i * 3 + di] = (tmp >> s) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
return i * 3 + di;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPORT
|
||||||
|
void nvnc_base64_encode(char* dst, const uint8_t* src, size_t src_len)
|
||||||
|
{
|
||||||
|
base64_encode(dst, src, src_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPORT
|
||||||
|
ssize_t nvnc_base64_decode(uint8_t* dst, const char* src)
|
||||||
|
{
|
||||||
|
return base64_decode(dst, src);
|
||||||
|
}
|
|
@ -0,0 +1,738 @@
|
||||||
|
#include "crypto.h"
|
||||||
|
#include "neatvnc.h"
|
||||||
|
#include "vec.h"
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
#include <gmp.h>
|
||||||
|
#include <nettle/base64.h>
|
||||||
|
#include <nettle/base16.h>
|
||||||
|
#include <nettle/aes.h>
|
||||||
|
#include <nettle/eax.h>
|
||||||
|
#include <nettle/md5.h>
|
||||||
|
#include <nettle/sha1.h>
|
||||||
|
#include <nettle/sha.h>
|
||||||
|
#include <nettle/rsa.h>
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
// TODO: This is linux specific
|
||||||
|
#include <sys/random.h>
|
||||||
|
|
||||||
|
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
|
||||||
|
|
||||||
|
struct vec;
|
||||||
|
|
||||||
|
struct crypto_key {
|
||||||
|
int g;
|
||||||
|
mpz_t p;
|
||||||
|
mpz_t q;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_aes_eax {
|
||||||
|
struct eax_aes128_ctx ctx;
|
||||||
|
uint64_t count[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_aes256_eax {
|
||||||
|
struct EAX_CTX(struct aes256_ctx) ctx;
|
||||||
|
uint64_t count[2];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_cipher {
|
||||||
|
union {
|
||||||
|
struct aes128_ctx aes128_ecb;
|
||||||
|
struct crypto_aes_eax aes_eax;
|
||||||
|
struct crypto_aes256_eax aes256_eax;
|
||||||
|
} enc_ctx;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct aes128_ctx aes128_ecb;
|
||||||
|
struct crypto_aes_eax aes_eax;
|
||||||
|
struct crypto_aes256_eax aes256_eax;
|
||||||
|
} dec_ctx;
|
||||||
|
|
||||||
|
bool (*encrypt)(struct crypto_cipher*, struct vec* dst, uint8_t* mac,
|
||||||
|
const uint8_t* src, size_t src_len, const uint8_t* ad,
|
||||||
|
size_t ad_len);
|
||||||
|
ssize_t (*decrypt)(struct crypto_cipher*, uint8_t* dst, uint8_t* mac,
|
||||||
|
const uint8_t* src, size_t src_len, const uint8_t* ad,
|
||||||
|
size_t ad_len);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_hash {
|
||||||
|
union {
|
||||||
|
struct md5_ctx md5;
|
||||||
|
struct sha1_ctx sha1;
|
||||||
|
struct sha256_ctx sha256;
|
||||||
|
} ctx;
|
||||||
|
|
||||||
|
void (*update)(void* ctx, size_t len, const uint8_t* src);
|
||||||
|
void (*digest)(void* ctx, size_t len, uint8_t* dst);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_rsa_pub_key {
|
||||||
|
struct rsa_public_key key;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct crypto_rsa_priv_key {
|
||||||
|
struct rsa_private_key key;
|
||||||
|
};
|
||||||
|
|
||||||
|
void crypto_dump_base64(const char* msg, const uint8_t* bytes, size_t len)
|
||||||
|
{
|
||||||
|
struct base64_encode_ctx ctx = {};
|
||||||
|
size_t buflen = BASE64_ENCODE_LENGTH(len);
|
||||||
|
char* buffer = malloc(buflen + BASE64_ENCODE_FINAL_LENGTH + 1);
|
||||||
|
assert(buffer);
|
||||||
|
nettle_base64_encode_init(&ctx);
|
||||||
|
size_t count = nettle_base64_encode_update(&ctx, buffer, len, bytes);
|
||||||
|
count += nettle_base64_encode_final(&ctx, buffer + count);
|
||||||
|
buffer[count] = '\0';
|
||||||
|
|
||||||
|
nvnc_log(NVNC_LOG_DEBUG, "%s: %s", msg, buffer);
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_dump_base16(const char* msg, const uint8_t* bytes, size_t len)
|
||||||
|
{
|
||||||
|
size_t buflen = BASE16_ENCODE_LENGTH(len);
|
||||||
|
char* buffer = calloc(1, buflen + 1);
|
||||||
|
assert(buffer);
|
||||||
|
nettle_base16_encode_update(buffer, len, bytes);
|
||||||
|
|
||||||
|
nvnc_log(NVNC_LOG_DEBUG, "%s: %s", msg, buffer);
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_random(uint8_t* dst, size_t len)
|
||||||
|
{
|
||||||
|
getrandom(dst, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void crypto_import(mpz_t n, const uint8_t* src, size_t len)
|
||||||
|
{
|
||||||
|
int order = 1;
|
||||||
|
int unit_size = 1;
|
||||||
|
int endian = 1;
|
||||||
|
int skip_bits = 0;
|
||||||
|
|
||||||
|
mpz_import(n, len, order, unit_size, endian, skip_bits, src);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_key *crypto_key_new(int g, const uint8_t* p, uint32_t p_len,
|
||||||
|
const uint8_t* q, uint32_t q_len)
|
||||||
|
{
|
||||||
|
struct crypto_key* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
self->g = g;
|
||||||
|
|
||||||
|
mpz_init(self->p);
|
||||||
|
crypto_import(self->p, p, p_len);
|
||||||
|
|
||||||
|
mpz_init(self->q);
|
||||||
|
crypto_import(self->q, q, q_len);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_key_del(struct crypto_key* key)
|
||||||
|
{
|
||||||
|
if (!key)
|
||||||
|
return;
|
||||||
|
mpz_clear(key->q);
|
||||||
|
mpz_clear(key->p);
|
||||||
|
free(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
int crypto_key_g(const struct crypto_key* key)
|
||||||
|
{
|
||||||
|
return key->g;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t crypto_export(uint8_t* dst, size_t dst_size, const mpz_t n)
|
||||||
|
{
|
||||||
|
int order = 1; // msb first
|
||||||
|
int unit_size = 1; // byte
|
||||||
|
int endian = 1; // msb first
|
||||||
|
int skip_bits = 0;
|
||||||
|
|
||||||
|
size_t bitsize = mpz_sizeinbase(n, 2);
|
||||||
|
size_t bytesize = (bitsize + 7) / 8;
|
||||||
|
|
||||||
|
assert(bytesize <= dst_size);
|
||||||
|
|
||||||
|
memset(dst, 0, dst_size);
|
||||||
|
mpz_export(dst + dst_size - bytesize, &bytesize, order, unit_size,
|
||||||
|
endian, skip_bits, n);
|
||||||
|
|
||||||
|
return bytesize;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t crypto_key_p(const struct crypto_key* key, uint8_t* dst,
|
||||||
|
uint32_t dst_size)
|
||||||
|
{
|
||||||
|
return crypto_export(dst, dst_size, key->p);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t crypto_key_q(const struct crypto_key* key, uint8_t* dst,
|
||||||
|
uint32_t dst_size)
|
||||||
|
{
|
||||||
|
return crypto_export(dst, dst_size, key->q);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initialise_p(mpz_t p)
|
||||||
|
{
|
||||||
|
// RFC 3526, section 3
|
||||||
|
static const char s[] =
|
||||||
|
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1"
|
||||||
|
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD"
|
||||||
|
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245"
|
||||||
|
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
|
||||||
|
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D"
|
||||||
|
"C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F"
|
||||||
|
"83655D23DCA3AD961C62F356208552BB9ED529077096966D"
|
||||||
|
"670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
|
||||||
|
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9"
|
||||||
|
"DE2BCBF6955817183995497CEA956AE515D2261898FA0510"
|
||||||
|
"15728E5A8AACAA68FFFFFFFFFFFFFFFF";
|
||||||
|
|
||||||
|
char buf[256];
|
||||||
|
size_t len = 0;
|
||||||
|
struct base16_decode_ctx ctx;
|
||||||
|
nettle_base16_decode_init(&ctx);
|
||||||
|
nettle_base16_decode_update(&ctx, &len, (uint8_t*)buf, sizeof(s) - 1, s);
|
||||||
|
nettle_base16_decode_final(&ctx);
|
||||||
|
assert(len == sizeof(buf));
|
||||||
|
|
||||||
|
crypto_import(p, (const uint8_t*)buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generate_random(mpz_t n)
|
||||||
|
{
|
||||||
|
uint8_t buf[256];
|
||||||
|
getrandom(buf, sizeof(buf), 0);
|
||||||
|
crypto_import(n, buf, sizeof(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_key* crypto_keygen(void)
|
||||||
|
{
|
||||||
|
struct crypto_key* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
self->g = 2;
|
||||||
|
|
||||||
|
mpz_init(self->p);
|
||||||
|
initialise_p(self->p);
|
||||||
|
|
||||||
|
mpz_init(self->q);
|
||||||
|
generate_random(self->q);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_key* crypto_derive_public_key(const struct crypto_key* priv)
|
||||||
|
{
|
||||||
|
struct crypto_key* pub = calloc(1, sizeof(*pub));
|
||||||
|
if (!pub)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
pub->g = priv->g;
|
||||||
|
mpz_set(pub->p, priv->p);
|
||||||
|
mpz_init(pub->q);
|
||||||
|
|
||||||
|
mpz_t g;
|
||||||
|
mpz_init(g);
|
||||||
|
mpz_set_ui(g, priv->g);
|
||||||
|
|
||||||
|
mpz_powm_sec(pub->q, g, priv->q, priv->p);
|
||||||
|
mpz_clear(g);
|
||||||
|
|
||||||
|
return pub;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_key* crypto_derive_shared_secret(
|
||||||
|
const struct crypto_key* own_secret,
|
||||||
|
const struct crypto_key* remote_public_key)
|
||||||
|
{
|
||||||
|
if (own_secret->g != remote_public_key->g) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mpz_cmp(own_secret->p, remote_public_key->p) != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_key* shared = calloc(1, sizeof(*shared));
|
||||||
|
if (!shared)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
shared->g = own_secret->g;
|
||||||
|
mpz_set(shared->p, own_secret->p);
|
||||||
|
|
||||||
|
mpz_t g;
|
||||||
|
mpz_init(g);
|
||||||
|
mpz_set_ui(g, own_secret->g);
|
||||||
|
|
||||||
|
mpz_powm_sec(shared->q, remote_public_key->q, own_secret->q,
|
||||||
|
own_secret->p);
|
||||||
|
mpz_clear(g);
|
||||||
|
|
||||||
|
return shared;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool crypto_cipher_aes128_ecb_encrypt(struct crypto_cipher* self,
|
||||||
|
struct vec* dst, uint8_t* mac, const uint8_t* src,
|
||||||
|
size_t len, const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
vec_reserve(dst, dst->len + len);
|
||||||
|
aes128_encrypt(&self->enc_ctx.aes128_ecb, len, dst->data, src);
|
||||||
|
dst->len = len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t crypto_cipher_aes128_ecb_decrypt(struct crypto_cipher* self,
|
||||||
|
uint8_t* dst, uint8_t* mac, const uint8_t* src, size_t len,
|
||||||
|
const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
aes128_decrypt(&self->dec_ctx.aes128_ecb, len, dst, src);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct crypto_cipher* crypto_cipher_new_aes128_ecb(
|
||||||
|
const uint8_t* enc_key, const uint8_t* dec_key)
|
||||||
|
{
|
||||||
|
struct crypto_cipher* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (enc_key)
|
||||||
|
aes128_set_encrypt_key(&self->enc_ctx.aes128_ecb, enc_key);
|
||||||
|
|
||||||
|
if (dec_key)
|
||||||
|
aes128_set_decrypt_key(&self->enc_ctx.aes128_ecb, dec_key);
|
||||||
|
|
||||||
|
self->encrypt = crypto_cipher_aes128_ecb_encrypt;
|
||||||
|
self->decrypt = crypto_cipher_aes128_ecb_decrypt;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void crypto_aes_eax_update_nonce(struct crypto_aes_eax* self)
|
||||||
|
{
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
nettle_eax_aes128_set_nonce(&self->ctx, 16, (const uint8_t*)self->count);
|
||||||
|
#else
|
||||||
|
uint64_t c[2];
|
||||||
|
c[0] = __builtin_bswap64(self->count[0]);
|
||||||
|
c[1] = __builtin_bswap64(self->count[1]);
|
||||||
|
nettle_eax_aes128_set_nonce(&self->ctx, 16, (const uint8_t*)c);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (++self->count[0] == 0)
|
||||||
|
++self->count[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool crypto_cipher_aes_eax_encrypt(struct crypto_cipher* self,
|
||||||
|
struct vec* dst, uint8_t* mac, const uint8_t* src,
|
||||||
|
size_t src_len, const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
vec_reserve(dst, dst->len + src_len);
|
||||||
|
|
||||||
|
crypto_aes_eax_update_nonce(&self->enc_ctx.aes_eax);
|
||||||
|
nettle_eax_aes128_update(&self->enc_ctx.aes_eax.ctx, ad_len,
|
||||||
|
(uint8_t*)ad);
|
||||||
|
nettle_eax_aes128_encrypt(&self->enc_ctx.aes_eax.ctx, src_len,
|
||||||
|
(uint8_t*)dst->data + dst->len, src);
|
||||||
|
dst->len += src_len;
|
||||||
|
|
||||||
|
nettle_eax_aes128_digest(&self->enc_ctx.aes_eax.ctx, 16, mac);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t crypto_cipher_aes_eax_decrypt(struct crypto_cipher* self,
|
||||||
|
uint8_t* dst, uint8_t* mac, const uint8_t* src, size_t len,
|
||||||
|
const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
crypto_aes_eax_update_nonce(&self->dec_ctx.aes_eax);
|
||||||
|
nettle_eax_aes128_update(&self->dec_ctx.aes_eax.ctx, ad_len, ad);
|
||||||
|
nettle_eax_aes128_decrypt(&self->dec_ctx.aes_eax.ctx, len, dst, src);
|
||||||
|
nettle_eax_aes128_digest(&self->dec_ctx.aes_eax.ctx, 16, mac);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct crypto_cipher* crypto_cipher_new_aes_eax(const uint8_t* enc_key,
|
||||||
|
const uint8_t* dec_key)
|
||||||
|
{
|
||||||
|
struct crypto_cipher* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
eax_aes128_set_key(&self->enc_ctx.aes_eax.ctx, enc_key);
|
||||||
|
eax_aes128_set_key(&self->dec_ctx.aes_eax.ctx, dec_key);
|
||||||
|
|
||||||
|
self->encrypt = crypto_cipher_aes_eax_encrypt;
|
||||||
|
self->decrypt = crypto_cipher_aes_eax_decrypt;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void crypto_aes256_eax_update_nonce(struct crypto_aes256_eax* self)
|
||||||
|
{
|
||||||
|
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||||
|
EAX_SET_NONCE(&self->ctx, aes256_encrypt, 16, (const uint8_t*)self->count);
|
||||||
|
#else
|
||||||
|
uint64_t c[2];
|
||||||
|
c[0] = __builtin_bswap64(self->count[0]);
|
||||||
|
c[1] = __builtin_bswap64(self->count[1]);
|
||||||
|
EAX_SET_NONCE(&self->ctx, aes256_encrypt, 16, (const uint8_t*)c);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (++self->count[0] == 0)
|
||||||
|
++self->count[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool crypto_cipher_aes256_eax_encrypt(struct crypto_cipher* self,
|
||||||
|
struct vec* dst, uint8_t* mac, const uint8_t* src,
|
||||||
|
size_t src_len, const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
vec_reserve(dst, dst->len + src_len);
|
||||||
|
|
||||||
|
crypto_aes256_eax_update_nonce(&self->enc_ctx.aes256_eax);
|
||||||
|
|
||||||
|
EAX_UPDATE(&self->enc_ctx.aes256_eax.ctx, aes256_encrypt, ad_len, ad);
|
||||||
|
EAX_ENCRYPT(&self->enc_ctx.aes256_eax.ctx, aes256_encrypt, src_len,
|
||||||
|
(uint8_t*)dst->data + dst->len, src);
|
||||||
|
dst->len += src_len;
|
||||||
|
|
||||||
|
EAX_DIGEST(&self->enc_ctx.aes256_eax.ctx, aes256_encrypt, 16, mac);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t crypto_cipher_aes256_eax_decrypt(struct crypto_cipher* self,
|
||||||
|
uint8_t* dst, uint8_t* mac, const uint8_t* src, size_t len,
|
||||||
|
const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
crypto_aes256_eax_update_nonce(&self->dec_ctx.aes256_eax);
|
||||||
|
EAX_UPDATE(&self->dec_ctx.aes256_eax.ctx, aes256_encrypt, ad_len, ad);
|
||||||
|
EAX_DECRYPT(&self->dec_ctx.aes256_eax.ctx, aes256_encrypt, len, dst, src);
|
||||||
|
EAX_DIGEST(&self->dec_ctx.aes256_eax.ctx, aes256_encrypt, 16, mac);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct crypto_cipher* crypto_cipher_new_aes256_eax(const uint8_t* enc_key,
|
||||||
|
const uint8_t* dec_key)
|
||||||
|
{
|
||||||
|
struct crypto_cipher* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
EAX_SET_KEY(&self->enc_ctx.aes256_eax.ctx, aes256_set_encrypt_key,
|
||||||
|
aes256_encrypt, enc_key);
|
||||||
|
EAX_SET_KEY(&self->dec_ctx.aes256_eax.ctx, aes256_set_encrypt_key,
|
||||||
|
aes256_encrypt, dec_key);
|
||||||
|
|
||||||
|
self->encrypt = crypto_cipher_aes256_eax_encrypt;
|
||||||
|
self->decrypt = crypto_cipher_aes256_eax_decrypt;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_cipher* crypto_cipher_new(const uint8_t* enc_key,
|
||||||
|
const uint8_t* dec_key, enum crypto_cipher_type type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case CRYPTO_CIPHER_AES128_ECB:
|
||||||
|
return crypto_cipher_new_aes128_ecb(enc_key, dec_key);
|
||||||
|
case CRYPTO_CIPHER_AES_EAX:
|
||||||
|
return crypto_cipher_new_aes_eax(enc_key, dec_key);
|
||||||
|
case CRYPTO_CIPHER_AES256_EAX:
|
||||||
|
return crypto_cipher_new_aes256_eax(enc_key, dec_key);
|
||||||
|
case CRYPTO_CIPHER_INVALID:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvnc_log(NVNC_LOG_PANIC, "Invalid type: %d", type);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_cipher_del(struct crypto_cipher* self)
|
||||||
|
{
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool crypto_cipher_encrypt(struct crypto_cipher* self, struct vec* dst,
|
||||||
|
uint8_t* mac, const uint8_t* src, size_t src_len,
|
||||||
|
const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
return self->encrypt(self, dst, mac, src, src_len, ad, ad_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t crypto_cipher_decrypt(struct crypto_cipher* self, uint8_t* dst,
|
||||||
|
uint8_t* mac, const uint8_t* src, size_t src_len,
|
||||||
|
const uint8_t* ad, size_t ad_len)
|
||||||
|
{
|
||||||
|
return self->decrypt(self, dst, mac, src, src_len, ad, ad_len);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_hash* crypto_hash_new(enum crypto_hash_type type)
|
||||||
|
{
|
||||||
|
struct crypto_hash* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case CRYPTO_HASH_INVALID:
|
||||||
|
nvnc_log(NVNC_LOG_PANIC, "Invalid hash type");
|
||||||
|
break;
|
||||||
|
case CRYPTO_HASH_MD5:
|
||||||
|
md5_init(&self->ctx.md5);
|
||||||
|
self->update = (void*)nettle_md5_update;
|
||||||
|
self->digest = (void*)nettle_md5_digest;
|
||||||
|
break;
|
||||||
|
case CRYPTO_HASH_SHA1:
|
||||||
|
sha1_init(&self->ctx.sha1);
|
||||||
|
self->update = (void*)nettle_sha1_update;
|
||||||
|
self->digest = (void*)nettle_sha1_digest;
|
||||||
|
break;
|
||||||
|
case CRYPTO_HASH_SHA256:
|
||||||
|
sha256_init(&self->ctx.sha256);
|
||||||
|
self->update = (void*)nettle_sha256_update;
|
||||||
|
self->digest = (void*)nettle_sha256_digest;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_hash_del(struct crypto_hash* self)
|
||||||
|
{
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_hash_append(struct crypto_hash* self, const uint8_t* src,
|
||||||
|
size_t len)
|
||||||
|
{
|
||||||
|
self->update(&self->ctx, len, src);
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_hash_digest(struct crypto_hash* self, uint8_t* dst, size_t len)
|
||||||
|
{
|
||||||
|
self->digest(&self->ctx, len, dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_hash_one(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
|
||||||
|
const uint8_t* src, size_t src_len)
|
||||||
|
{
|
||||||
|
struct crypto_hash *hash = crypto_hash_new(type);
|
||||||
|
crypto_hash_append(hash, src, src_len);
|
||||||
|
crypto_hash_digest(hash, dst, dst_len);
|
||||||
|
crypto_hash_del(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_hash_many(uint8_t* dst, size_t dst_len, enum crypto_hash_type type,
|
||||||
|
const struct crypto_data_entry *src)
|
||||||
|
{
|
||||||
|
struct crypto_hash *hash = crypto_hash_new(type);
|
||||||
|
for (int i = 0; src[i].data && src[i].len; ++i)
|
||||||
|
crypto_hash_append(hash, src[i].data, src[i].len);
|
||||||
|
crypto_hash_digest(hash, dst, dst_len);
|
||||||
|
crypto_hash_del(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_rsa_pub_key *crypto_rsa_pub_key_new(void)
|
||||||
|
{
|
||||||
|
struct crypto_rsa_pub_key* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
rsa_public_key_init(&self->key);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_rsa_pub_key_del(struct crypto_rsa_pub_key* self)
|
||||||
|
{
|
||||||
|
if (!self)
|
||||||
|
return;
|
||||||
|
rsa_public_key_clear(&self->key);
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_rsa_pub_key* crypto_rsa_pub_key_import(const uint8_t* modulus,
|
||||||
|
const uint8_t* exponent, size_t size)
|
||||||
|
{
|
||||||
|
struct crypto_rsa_pub_key* self = crypto_rsa_pub_key_new();
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
rsa_public_key_init(&self->key);
|
||||||
|
mpz_init(self->key.n);
|
||||||
|
crypto_import(self->key.n, modulus, size);
|
||||||
|
mpz_init(self->key.e);
|
||||||
|
crypto_import(self->key.e, exponent, size);
|
||||||
|
rsa_public_key_prepare(&self->key);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool crypto_rsa_priv_key_import_pkcs1_der(struct crypto_rsa_priv_key* priv,
|
||||||
|
struct crypto_rsa_pub_key* pub, const uint8_t* key,
|
||||||
|
size_t size)
|
||||||
|
{
|
||||||
|
return rsa_keypair_from_der(&pub->key, &priv->key, 0, size, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool crypto_rsa_priv_key_load(struct crypto_rsa_priv_key* priv,
|
||||||
|
struct crypto_rsa_pub_key* pub, const char* path)
|
||||||
|
{
|
||||||
|
FILE* stream = fopen(path, "r");
|
||||||
|
if (!stream) {
|
||||||
|
nvnc_log(NVNC_LOG_ERROR, "Could not open file: %m");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char* line = NULL;
|
||||||
|
size_t n = 0;
|
||||||
|
if (getline(&line, &n, stream) < 0) {
|
||||||
|
nvnc_log(NVNC_LOG_ERROR, "RSA private key file is not PEM");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char head[128];
|
||||||
|
strlcpy(head, line, sizeof(head));
|
||||||
|
char* end = strchr(head, '\n');
|
||||||
|
if (end)
|
||||||
|
*end = '\0';
|
||||||
|
|
||||||
|
nvnc_trace("Read PEM head: \"%s\"\n", head);
|
||||||
|
|
||||||
|
struct vec base64_der;
|
||||||
|
vec_init(&base64_der, 4096);
|
||||||
|
|
||||||
|
while (getline(&line, &n, stream) >= 0) {
|
||||||
|
if (strncmp(line, "-----END", 8) == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
|
||||||
|
vec_append(&base64_der, line, strcspn(line, "\n"));
|
||||||
|
}
|
||||||
|
free(line);
|
||||||
|
fclose(stream);
|
||||||
|
|
||||||
|
vec_append_zero(&base64_der, 1);
|
||||||
|
|
||||||
|
uint8_t* der = malloc(BASE64_DECODED_MAX_SIZE(base64_der.len));
|
||||||
|
assert(der);
|
||||||
|
vec_destroy(&base64_der);
|
||||||
|
|
||||||
|
ssize_t der_len = base64_decode(der, base64_der.data);
|
||||||
|
if (der_len < 0) {
|
||||||
|
free(der);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
if (strcmp(head, "-----BEGIN RSA PRIVATE KEY-----") == 0) {
|
||||||
|
ok = crypto_rsa_priv_key_import_pkcs1_der(priv, pub, der, der_len);
|
||||||
|
} else {
|
||||||
|
nvnc_log(NVNC_LOG_ERROR, "Unsupported RSA private key format");
|
||||||
|
}
|
||||||
|
|
||||||
|
nvnc_trace("Private key is %d bits long", priv->key.size * 8);
|
||||||
|
|
||||||
|
free(der);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_rsa_pub_key_modulus(const struct crypto_rsa_pub_key* key,
|
||||||
|
uint8_t* dst, size_t dst_size)
|
||||||
|
{
|
||||||
|
crypto_export(dst, dst_size, key->key.n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_rsa_pub_key_exponent(const struct crypto_rsa_pub_key* key,
|
||||||
|
uint8_t* dst, size_t dst_size)
|
||||||
|
{
|
||||||
|
char* str = mpz_get_str(NULL, 16, key->key.e);
|
||||||
|
free(str);
|
||||||
|
|
||||||
|
crypto_export(dst, dst_size, key->key.e);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct crypto_rsa_priv_key *crypto_rsa_priv_key_new(void)
|
||||||
|
{
|
||||||
|
struct crypto_rsa_priv_key* self = calloc(1, sizeof(*self));
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
rsa_private_key_init(&self->key);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
void crypto_rsa_priv_key_del(struct crypto_rsa_priv_key* self)
|
||||||
|
{
|
||||||
|
if (!self)
|
||||||
|
return;
|
||||||
|
rsa_private_key_clear(&self->key);
|
||||||
|
free(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t crypto_rsa_pub_key_length(const struct crypto_rsa_pub_key* key)
|
||||||
|
{
|
||||||
|
return key->key.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void generate_random_for_rsa(void* random_ctx, size_t len, uint8_t* dst)
|
||||||
|
{
|
||||||
|
getrandom(dst, len, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool crypto_rsa_keygen(struct crypto_rsa_pub_key* pub,
|
||||||
|
struct crypto_rsa_priv_key* priv)
|
||||||
|
{
|
||||||
|
void* random_ctx = NULL;
|
||||||
|
nettle_random_func* random_func = generate_random_for_rsa;
|
||||||
|
void* progress_ctx = NULL;
|
||||||
|
nettle_progress_func* progress = NULL;
|
||||||
|
|
||||||
|
int rc = rsa_generate_keypair(&pub->key, &priv->key, random_ctx,
|
||||||
|
random_func, progress_ctx, progress, 2048, 30);
|
||||||
|
return rc != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t crypto_rsa_encrypt(struct crypto_rsa_pub_key* pub, uint8_t* dst,
|
||||||
|
size_t dst_size, const uint8_t* src, size_t src_size)
|
||||||
|
{
|
||||||
|
mpz_t ciphertext;
|
||||||
|
mpz_init(ciphertext);
|
||||||
|
int r = rsa_encrypt(&pub->key, NULL, generate_random_for_rsa,
|
||||||
|
src_size, src, ciphertext);
|
||||||
|
if (r == 0) {
|
||||||
|
mpz_clear(ciphertext);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
size_t len = crypto_export(dst, dst_size, ciphertext);
|
||||||
|
mpz_clear(ciphertext);
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t crypto_rsa_decrypt(struct crypto_rsa_priv_key* priv, uint8_t* dst,
|
||||||
|
size_t dst_size, const uint8_t* src, size_t src_size)
|
||||||
|
{
|
||||||
|
mpz_t ciphertext;
|
||||||
|
mpz_init(ciphertext);
|
||||||
|
crypto_import(ciphertext, src, src_size);
|
||||||
|
int r = rsa_decrypt(&priv->key, &dst_size, dst, ciphertext);
|
||||||
|
mpz_clear(ciphertext);
|
||||||
|
return r != 0 ? (ssize_t)dst_size : -1;
|
||||||
|
}
|
471
src/server.c
471
src/server.c
|
@ -54,6 +54,10 @@
|
||||||
#include <gnutls/gnutls.h>
|
#include <gnutls/gnutls.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
#include "crypto.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef DRM_FORMAT_INVALID
|
#ifndef DRM_FORMAT_INVALID
|
||||||
#define DRM_FORMAT_INVALID 0
|
#define DRM_FORMAT_INVALID 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -63,6 +67,10 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DEFAULT_NAME "Neat VNC"
|
#define DEFAULT_NAME "Neat VNC"
|
||||||
|
#define SECURITY_TYPES_MAX 3
|
||||||
|
#define APPLE_DH_SERVER_KEY_LENGTH 256
|
||||||
|
|
||||||
|
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
|
||||||
|
|
||||||
#define EXPORT __attribute__((visibility("default")))
|
#define EXPORT __attribute__((visibility("default")))
|
||||||
|
|
||||||
|
@ -119,6 +127,11 @@ static void client_close(struct nvnc_client* client)
|
||||||
nvnc_fb_unref(client->current_fb);
|
nvnc_fb_unref(client->current_fb);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
crypto_key_del(client->apple_dh_secret);
|
||||||
|
crypto_rsa_pub_key_del(client->rsa.pub);
|
||||||
|
#endif
|
||||||
|
|
||||||
LIST_REMOVE(client, link);
|
LIST_REMOVE(client, link);
|
||||||
stream_destroy(client->net_stream);
|
stream_destroy(client->net_stream);
|
||||||
if (client->encoder) {
|
if (client->encoder) {
|
||||||
|
@ -196,6 +209,8 @@ static int handle_unsupported_version(struct nvnc_client* client)
|
||||||
|
|
||||||
static int on_version_message(struct nvnc_client* client)
|
static int on_version_message(struct nvnc_client* client)
|
||||||
{
|
{
|
||||||
|
struct nvnc* server = client->server;
|
||||||
|
|
||||||
if (client->buffer_len - client->buffer_index < 12)
|
if (client->buffer_len - client->buffer_index < 12)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
|
@ -206,17 +221,33 @@ static int on_version_message(struct nvnc_client* client)
|
||||||
if (strcmp(RFB_VERSION_MESSAGE, version_string) != 0)
|
if (strcmp(RFB_VERSION_MESSAGE, version_string) != 0)
|
||||||
return handle_unsupported_version(client);
|
return handle_unsupported_version(client);
|
||||||
|
|
||||||
struct rfb_security_types_msg security = { 0 };
|
uint8_t buf[sizeof(struct rfb_security_types_msg) +
|
||||||
security.n = 1;
|
SECURITY_TYPES_MAX] = {};
|
||||||
security.types[0] = RFB_SECURITY_TYPE_NONE;
|
struct rfb_security_types_msg* security =
|
||||||
|
(struct rfb_security_types_msg*)buf;
|
||||||
|
|
||||||
|
security->n = 0;
|
||||||
|
if (client->server->auth_fn) {
|
||||||
#ifdef ENABLE_TLS
|
#ifdef ENABLE_TLS
|
||||||
if (client->server->auth_fn)
|
if (server->tls_creds) {
|
||||||
security.types[0] = RFB_SECURITY_TYPE_VENCRYPT;
|
security->types[security->n++] = RFB_SECURITY_TYPE_VENCRYPT;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
stream_write(client->net_stream, &security, sizeof(security), NULL,
|
#ifdef HAVE_CRYPTO
|
||||||
NULL);
|
security->types[security->n++] = RFB_SECURITY_TYPE_RSA_AES256;
|
||||||
|
security->types[security->n++] = RFB_SECURITY_TYPE_RSA_AES;
|
||||||
|
security->types[security->n++] = RFB_SECURITY_TYPE_APPLE_DH;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (security->n == 0) {
|
||||||
|
security->n = 1;
|
||||||
|
security->types[0] = RFB_SECURITY_TYPE_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_write(client->net_stream, security, sizeof(*security) +
|
||||||
|
security->n, NULL, NULL);
|
||||||
|
|
||||||
client->state = VNC_CLIENT_STATE_WAITING_FOR_SECURITY;
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_SECURITY;
|
||||||
return 12;
|
return 12;
|
||||||
|
@ -368,12 +399,380 @@ static int on_vencrypt_plain_auth_message(struct nvnc_client* client)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
static int apple_dh_send_public_key(struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
client->apple_dh_secret = crypto_keygen();
|
||||||
|
assert(client->apple_dh_secret);
|
||||||
|
|
||||||
|
struct crypto_key* pub =
|
||||||
|
crypto_derive_public_key(client->apple_dh_secret);
|
||||||
|
assert(pub);
|
||||||
|
|
||||||
|
uint8_t mod[APPLE_DH_SERVER_KEY_LENGTH] = {};
|
||||||
|
int mod_len = crypto_key_p(pub, mod, sizeof(mod));
|
||||||
|
assert(mod_len == sizeof(mod));
|
||||||
|
|
||||||
|
uint8_t q[APPLE_DH_SERVER_KEY_LENGTH] = {};
|
||||||
|
int q_len = crypto_key_q(pub, q, sizeof(q));
|
||||||
|
assert(q_len == sizeof(q));
|
||||||
|
|
||||||
|
struct rfb_apple_dh_server_msg msg = {
|
||||||
|
.generator = htons(crypto_key_g(client->apple_dh_secret)),
|
||||||
|
.key_size = htons(q_len),
|
||||||
|
};
|
||||||
|
|
||||||
|
stream_write(client->net_stream, &msg, sizeof(msg), NULL, NULL);
|
||||||
|
stream_write(client->net_stream, mod, mod_len, NULL, NULL);
|
||||||
|
stream_write(client->net_stream, q, q_len, NULL, NULL);
|
||||||
|
|
||||||
|
crypto_key_del(pub);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_apple_dh_response(struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
struct nvnc* server = client->server;
|
||||||
|
|
||||||
|
struct rfb_apple_dh_client_msg* msg =
|
||||||
|
(void*)(client->msg_buffer + client->buffer_index);
|
||||||
|
|
||||||
|
uint8_t p[APPLE_DH_SERVER_KEY_LENGTH];
|
||||||
|
int key_len = crypto_key_p(client->apple_dh_secret, p, sizeof(p));
|
||||||
|
assert(key_len == sizeof(p));
|
||||||
|
|
||||||
|
if (client->buffer_len - client->buffer_index < sizeof(*msg) + key_len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
int g = crypto_key_g(client->apple_dh_secret);
|
||||||
|
|
||||||
|
struct crypto_key* remote_key = crypto_key_new(g, p, key_len,
|
||||||
|
msg->public_key, key_len);
|
||||||
|
assert(remote_key);
|
||||||
|
|
||||||
|
struct crypto_key* shared_secret =
|
||||||
|
crypto_derive_shared_secret(client->apple_dh_secret, remote_key);
|
||||||
|
assert(shared_secret);
|
||||||
|
|
||||||
|
uint8_t shared_buf[APPLE_DH_SERVER_KEY_LENGTH];
|
||||||
|
crypto_key_q(shared_secret, shared_buf, sizeof(shared_buf));
|
||||||
|
crypto_key_del(shared_secret);
|
||||||
|
|
||||||
|
uint8_t hash[16] = {};
|
||||||
|
crypto_hash_one(hash, sizeof(hash), CRYPTO_HASH_MD5, shared_buf,
|
||||||
|
sizeof(shared_buf));
|
||||||
|
|
||||||
|
struct crypto_cipher* cipher;
|
||||||
|
cipher = crypto_cipher_new(NULL, hash, CRYPTO_CIPHER_AES128_ECB);
|
||||||
|
assert(cipher);
|
||||||
|
|
||||||
|
char username[128] = {};
|
||||||
|
char* password = username + 64;
|
||||||
|
|
||||||
|
crypto_cipher_decrypt(cipher, (uint8_t*)username, NULL,
|
||||||
|
msg->encrypted_credentials, sizeof(username), NULL, 0);
|
||||||
|
username[63] = '\0';
|
||||||
|
username[127] = '\0';
|
||||||
|
crypto_cipher_del(cipher);
|
||||||
|
|
||||||
|
if (server->auth_fn(username, password, server->auth_ud)) {
|
||||||
|
nvnc_log(NVNC_LOG_INFO, "User \"%s\" authenticated", username);
|
||||||
|
security_handshake_ok(client);
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_INIT;
|
||||||
|
} else {
|
||||||
|
nvnc_log(NVNC_LOG_INFO, "User \"%s\" rejected", username);
|
||||||
|
security_handshake_failed(client, "Invalid username or password");
|
||||||
|
crypto_cipher_del(cipher);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizeof(*msg) + key_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rsa_aes_send_public_key(struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
struct nvnc* server = client->server;
|
||||||
|
|
||||||
|
if (!server->rsa_priv) {
|
||||||
|
assert(!server->rsa_pub);
|
||||||
|
|
||||||
|
nvnc_log(NVNC_LOG_WARNING, "An RSA key has not been set. A new key will be generated.");
|
||||||
|
|
||||||
|
server->rsa_priv = crypto_rsa_priv_key_new();
|
||||||
|
server->rsa_pub = crypto_rsa_pub_key_new();
|
||||||
|
|
||||||
|
crypto_rsa_keygen(server->rsa_pub, server->rsa_priv);
|
||||||
|
}
|
||||||
|
assert(server->rsa_pub && server->rsa_priv);
|
||||||
|
|
||||||
|
size_t key_len = crypto_rsa_pub_key_length(server->rsa_pub);
|
||||||
|
size_t buf_len = sizeof(struct rfb_rsa_aes_pub_key_msg) + key_len * 2;
|
||||||
|
|
||||||
|
char* buffer = calloc(1, buf_len);
|
||||||
|
assert(buffer);
|
||||||
|
struct rfb_rsa_aes_pub_key_msg* msg =
|
||||||
|
(struct rfb_rsa_aes_pub_key_msg*)buffer;
|
||||||
|
|
||||||
|
uint8_t* modulus = msg->modulus_and_exponent;
|
||||||
|
uint8_t* exponent = msg->modulus_and_exponent + key_len;
|
||||||
|
|
||||||
|
msg->length = htonl(key_len * 8);
|
||||||
|
crypto_rsa_pub_key_modulus(server->rsa_pub, modulus, key_len);
|
||||||
|
crypto_rsa_pub_key_exponent(server->rsa_pub, exponent, key_len);
|
||||||
|
|
||||||
|
stream_send(client->net_stream, rcbuf_new(buffer, buf_len), NULL, NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int rsa_aes_send_challenge(struct nvnc_client* client,
|
||||||
|
struct crypto_rsa_pub_key* pub)
|
||||||
|
{
|
||||||
|
crypto_random(client->rsa.challenge, client->rsa.challenge_len);
|
||||||
|
|
||||||
|
uint8_t buffer[1024];
|
||||||
|
struct rfb_rsa_aes_challenge_msg *msg =
|
||||||
|
(struct rfb_rsa_aes_challenge_msg*)buffer;
|
||||||
|
|
||||||
|
ssize_t len = crypto_rsa_encrypt(pub, msg->challenge,
|
||||||
|
crypto_rsa_pub_key_length(client->rsa.pub),
|
||||||
|
client->rsa.challenge, client->rsa.challenge_len);
|
||||||
|
msg->length = htons(len);
|
||||||
|
|
||||||
|
stream_write(client->net_stream, buffer, sizeof(*msg) + len, NULL, NULL);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_rsa_aes_public_key(struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
struct rfb_rsa_aes_pub_key_msg* msg =
|
||||||
|
(void*)(client->msg_buffer + client->buffer_index);
|
||||||
|
|
||||||
|
if (client->buffer_len - client->buffer_index < sizeof(*msg))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint32_t bit_length = ntohl(msg->length);
|
||||||
|
size_t byte_length = UDIV_UP(bit_length, 8);
|
||||||
|
|
||||||
|
if (client->buffer_len - client->buffer_index <
|
||||||
|
sizeof(*msg) + byte_length * 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
const uint8_t* modulus = msg->modulus_and_exponent;
|
||||||
|
const uint8_t* exponent = msg->modulus_and_exponent + byte_length;
|
||||||
|
|
||||||
|
client->rsa.pub =
|
||||||
|
crypto_rsa_pub_key_import(modulus, exponent, byte_length);
|
||||||
|
assert(client->rsa.pub);
|
||||||
|
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CHALLENGE;
|
||||||
|
rsa_aes_send_challenge(client, client->rsa.pub);
|
||||||
|
|
||||||
|
return sizeof(*msg) + byte_length * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t client_rsa_aes_hash_len(const struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
switch (client->rsa.hash_type) {
|
||||||
|
case CRYPTO_HASH_SHA1: return 20;
|
||||||
|
case CRYPTO_HASH_SHA256: return 32;
|
||||||
|
default:;
|
||||||
|
}
|
||||||
|
abort();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_rsa_aes_challenge(struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
struct rfb_rsa_aes_challenge_msg* msg =
|
||||||
|
(void*)(client->msg_buffer + client->buffer_index);
|
||||||
|
|
||||||
|
if (client->buffer_len - client->buffer_index < sizeof(*msg))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint16_t length = ntohs(msg->length);
|
||||||
|
if (client->buffer_len - client->buffer_index < sizeof(*msg) + length)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct nvnc* server = client->server;
|
||||||
|
|
||||||
|
uint8_t client_random[32] = {};
|
||||||
|
ssize_t len = crypto_rsa_decrypt(server->rsa_priv, client_random,
|
||||||
|
client->rsa.challenge_len, msg->challenge, length);
|
||||||
|
if (len < 0) {
|
||||||
|
nvnc_log(NVNC_LOG_ERROR, "Failed to decrypt client's challenge");
|
||||||
|
client->state = VNC_CLIENT_STATE_ERROR;
|
||||||
|
nvnc_client_close(client);
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClientSessionKey = the first 16 bytes of SHA1(ServerRandom || ClientRandom)
|
||||||
|
uint8_t client_session_key[32];
|
||||||
|
crypto_hash_many(client_session_key, client_rsa_aes_hash_len(client),
|
||||||
|
client->rsa.hash_type, (const struct crypto_data_entry[]) {
|
||||||
|
{ client->rsa.challenge, client->rsa.challenge_len },
|
||||||
|
{ client_random, client->rsa.challenge_len },
|
||||||
|
{}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ServerSessionKey = the first 16 bytes of SHA1(ClientRandom || ServerRandom)
|
||||||
|
uint8_t server_session_key[32];
|
||||||
|
crypto_hash_many(server_session_key, client_rsa_aes_hash_len(client),
|
||||||
|
client->rsa.hash_type, (const struct crypto_data_entry[]) {
|
||||||
|
{ client_random, client->rsa.challenge_len },
|
||||||
|
{ client->rsa.challenge, client->rsa.challenge_len },
|
||||||
|
{}
|
||||||
|
});
|
||||||
|
|
||||||
|
stream_upgrade_to_rsa_eas(client->net_stream, client->rsa.cipher_type,
|
||||||
|
server_session_key, client_session_key);
|
||||||
|
|
||||||
|
size_t server_key_len = crypto_rsa_pub_key_length(server->rsa_pub);
|
||||||
|
uint8_t* server_modulus = malloc(server_key_len * 2);
|
||||||
|
uint8_t* server_exponent = server_modulus + server_key_len;
|
||||||
|
|
||||||
|
crypto_rsa_pub_key_modulus(server->rsa_pub, server_modulus,
|
||||||
|
server_key_len);
|
||||||
|
crypto_rsa_pub_key_exponent(server->rsa_pub, server_exponent,
|
||||||
|
server_key_len);
|
||||||
|
|
||||||
|
size_t client_key_len = crypto_rsa_pub_key_length(client->rsa.pub);
|
||||||
|
uint8_t* client_modulus = malloc(client_key_len * 2);
|
||||||
|
uint8_t* client_exponent = client_modulus + client_key_len;
|
||||||
|
|
||||||
|
crypto_rsa_pub_key_modulus(client->rsa.pub, client_modulus,
|
||||||
|
client_key_len);
|
||||||
|
crypto_rsa_pub_key_exponent(client->rsa.pub, client_exponent,
|
||||||
|
client_key_len);
|
||||||
|
|
||||||
|
uint32_t server_key_len_be = htonl(server_key_len * 8);
|
||||||
|
uint32_t client_key_len_be = htonl(client_key_len * 8);
|
||||||
|
|
||||||
|
uint8_t server_hash[32] = {};
|
||||||
|
crypto_hash_many(server_hash, client_rsa_aes_hash_len(client),
|
||||||
|
client->rsa.hash_type, (const struct crypto_data_entry[]) {
|
||||||
|
{ (uint8_t*)&server_key_len_be, 4 },
|
||||||
|
{ server_modulus, server_key_len },
|
||||||
|
{ server_exponent, server_key_len },
|
||||||
|
{ (uint8_t*)&client_key_len_be, 4 },
|
||||||
|
{ client_modulus, client_key_len },
|
||||||
|
{ client_exponent, client_key_len },
|
||||||
|
{}
|
||||||
|
});
|
||||||
|
|
||||||
|
free(server_modulus);
|
||||||
|
free(client_modulus);
|
||||||
|
|
||||||
|
stream_write(client->net_stream, server_hash,
|
||||||
|
client_rsa_aes_hash_len(client), NULL, NULL);
|
||||||
|
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CLIENT_HASH;
|
||||||
|
done:
|
||||||
|
return sizeof(*msg) + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_rsa_aes_client_hash(struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
const char* msg = (void*)(client->msg_buffer + client->buffer_index);
|
||||||
|
|
||||||
|
if (client->buffer_len - client->buffer_index < client_rsa_aes_hash_len(client))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct nvnc* server = client->server;
|
||||||
|
|
||||||
|
size_t server_key_len = crypto_rsa_pub_key_length(server->rsa_pub);
|
||||||
|
uint8_t* server_modulus = malloc(server_key_len * 2);
|
||||||
|
uint8_t* server_exponent = server_modulus + server_key_len;
|
||||||
|
crypto_rsa_pub_key_modulus(server->rsa_pub, server_modulus,
|
||||||
|
server_key_len);
|
||||||
|
crypto_rsa_pub_key_exponent(server->rsa_pub, server_exponent,
|
||||||
|
server_key_len);
|
||||||
|
|
||||||
|
size_t client_key_len = crypto_rsa_pub_key_length(client->rsa.pub);
|
||||||
|
uint8_t* client_modulus = malloc(client_key_len * 2);
|
||||||
|
uint8_t* client_exponent = client_modulus + client_key_len;
|
||||||
|
|
||||||
|
crypto_rsa_pub_key_modulus(client->rsa.pub, client_modulus,
|
||||||
|
client_key_len);
|
||||||
|
crypto_rsa_pub_key_exponent(client->rsa.pub, client_exponent,
|
||||||
|
client_key_len);
|
||||||
|
|
||||||
|
uint32_t server_key_len_be = htonl(server_key_len * 8);
|
||||||
|
uint32_t client_key_len_be = htonl(client_key_len * 8);
|
||||||
|
|
||||||
|
uint8_t client_hash[32] = {};
|
||||||
|
crypto_hash_many(client_hash, client_rsa_aes_hash_len(client),
|
||||||
|
client->rsa.hash_type, (const struct crypto_data_entry[]) {
|
||||||
|
{ (uint8_t*)&client_key_len_be, 4 },
|
||||||
|
{ client_modulus, client_key_len },
|
||||||
|
{ client_exponent, client_key_len },
|
||||||
|
{ (uint8_t*)&server_key_len_be, 4 },
|
||||||
|
{ server_modulus, server_key_len },
|
||||||
|
{ server_exponent, server_key_len },
|
||||||
|
{}
|
||||||
|
});
|
||||||
|
|
||||||
|
free(client_modulus);
|
||||||
|
free(server_modulus);
|
||||||
|
|
||||||
|
if (memcmp(msg, client_hash, client_rsa_aes_hash_len(client)) != 0) {
|
||||||
|
nvnc_log(NVNC_LOG_INFO, "Client hash mismatch");
|
||||||
|
nvnc_client_close(client);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Read this from config
|
||||||
|
uint8_t subtype = RFB_RSA_AES_CRED_SUBTYPE_USER_AND_PASS;
|
||||||
|
stream_write(client->net_stream, &subtype, 1, NULL, NULL);
|
||||||
|
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CREDENTIALS;
|
||||||
|
return client_rsa_aes_hash_len(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int on_rsa_aes_credentials(struct nvnc_client* client)
|
||||||
|
{
|
||||||
|
const uint8_t* msg = (void*)(client->msg_buffer + client->buffer_index);
|
||||||
|
|
||||||
|
if (client->buffer_len - client->buffer_index < 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t username_len = msg[0];
|
||||||
|
if (client->buffer_len - client->buffer_index < 2 + username_len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t password_len = msg[1 + username_len];
|
||||||
|
if (client->buffer_len - client->buffer_index < 2 + username_len +
|
||||||
|
password_len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct nvnc* server = client->server;
|
||||||
|
|
||||||
|
char username[256];
|
||||||
|
char password[256];
|
||||||
|
|
||||||
|
strlcpy(username, (const char*)(msg + 1), username_len + 1);
|
||||||
|
strlcpy(password, (const char*)(msg + 2 + username_len),
|
||||||
|
password_len + 1);
|
||||||
|
|
||||||
|
if (server->auth_fn(username, password, server->auth_ud)) {
|
||||||
|
nvnc_log(NVNC_LOG_INFO, "User \"%s\" authenticated", username);
|
||||||
|
security_handshake_ok(client);
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_INIT;
|
||||||
|
} else {
|
||||||
|
nvnc_log(NVNC_LOG_INFO, "User \"%s\" rejected", username);
|
||||||
|
security_handshake_failed(client, "Invalid username or password");
|
||||||
|
}
|
||||||
|
|
||||||
|
return 2 + username_len + password_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // HAVE_CRYPTO
|
||||||
|
|
||||||
static int on_security_message(struct nvnc_client* client)
|
static int on_security_message(struct nvnc_client* client)
|
||||||
{
|
{
|
||||||
if (client->buffer_len - client->buffer_index < 1)
|
if (client->buffer_len - client->buffer_index < 1)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
uint8_t type = client->msg_buffer[client->buffer_index];
|
uint8_t type = client->msg_buffer[client->buffer_index];
|
||||||
|
nvnc_log(NVNC_LOG_DEBUG, "Client chose security type: %d", type);
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case RFB_SECURITY_TYPE_NONE:
|
case RFB_SECURITY_TYPE_NONE:
|
||||||
|
@ -385,6 +784,26 @@ static int on_security_message(struct nvnc_client* client)
|
||||||
vencrypt_send_version(client);
|
vencrypt_send_version(client);
|
||||||
client->state = VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_VERSION;
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_VERSION;
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
case RFB_SECURITY_TYPE_APPLE_DH:
|
||||||
|
apple_dh_send_public_key(client);
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_APPLE_DH_RESPONSE;
|
||||||
|
break;
|
||||||
|
case RFB_SECURITY_TYPE_RSA_AES:
|
||||||
|
client->rsa.hash_type = CRYPTO_HASH_SHA1;
|
||||||
|
client->rsa.cipher_type = CRYPTO_CIPHER_AES_EAX;
|
||||||
|
client->rsa.challenge_len = 16;
|
||||||
|
rsa_aes_send_public_key(client);
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY;
|
||||||
|
break;
|
||||||
|
case RFB_SECURITY_TYPE_RSA_AES256:
|
||||||
|
client->rsa.hash_type = CRYPTO_HASH_SHA256;
|
||||||
|
client->rsa.cipher_type = CRYPTO_CIPHER_AES256_EAX;
|
||||||
|
client->rsa.challenge_len = 32;
|
||||||
|
rsa_aes_send_public_key(client);
|
||||||
|
client->state = VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY;
|
||||||
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
security_handshake_failed(client, "Unsupported security type");
|
security_handshake_failed(client, "Unsupported security type");
|
||||||
|
@ -1165,6 +1584,18 @@ static int try_read_client_message(struct nvnc_client* client)
|
||||||
return on_vencrypt_subtype_message(client);
|
return on_vencrypt_subtype_message(client);
|
||||||
case VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_PLAIN_AUTH:
|
case VNC_CLIENT_STATE_WAITING_FOR_VENCRYPT_PLAIN_AUTH:
|
||||||
return on_vencrypt_plain_auth_message(client);
|
return on_vencrypt_plain_auth_message(client);
|
||||||
|
#endif
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
case VNC_CLIENT_STATE_WAITING_FOR_APPLE_DH_RESPONSE:
|
||||||
|
return on_apple_dh_response(client);
|
||||||
|
case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_PUBLIC_KEY:
|
||||||
|
return on_rsa_aes_public_key(client);
|
||||||
|
case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CHALLENGE:
|
||||||
|
return on_rsa_aes_challenge(client);
|
||||||
|
case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CLIENT_HASH:
|
||||||
|
return on_rsa_aes_client_hash(client);
|
||||||
|
case VNC_CLIENT_STATE_WAITING_FOR_RSA_AES_CREDENTIALS:
|
||||||
|
return on_rsa_aes_credentials(client);
|
||||||
#endif
|
#endif
|
||||||
case VNC_CLIENT_STATE_READY:
|
case VNC_CLIENT_STATE_READY:
|
||||||
return on_client_message(client);
|
return on_client_message(client);
|
||||||
|
@ -1909,6 +2340,16 @@ cert_alloc_failure:
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EXPORT
|
||||||
|
int nvnc_enable_auth2(struct nvnc* self, nvnc_auth_fn auth_fn, void* userdata)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
self->auth_fn = auth_fn;
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
EXPORT
|
EXPORT
|
||||||
void nvnc_set_cursor(struct nvnc* self, struct nvnc_fb* fb, uint16_t width,
|
void nvnc_set_cursor(struct nvnc* self, struct nvnc_fb* fb, uint16_t width,
|
||||||
uint16_t height, uint16_t hotspot_x, uint16_t hotspot_y,
|
uint16_t height, uint16_t hotspot_x, uint16_t hotspot_y,
|
||||||
|
@ -1947,3 +2388,19 @@ void nvnc_set_cursor(struct nvnc* self, struct nvnc_fb* fb, uint16_t width,
|
||||||
LIST_FOREACH(client, &self->clients, link)
|
LIST_FOREACH(client, &self->clients, link)
|
||||||
process_fb_update_requests(client);
|
process_fb_update_requests(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EXPORT
|
||||||
|
int nvnc_set_rsa_creds(struct nvnc* self, const char* path)
|
||||||
|
{
|
||||||
|
#ifdef HAVE_CRYPTO
|
||||||
|
crypto_rsa_priv_key_del(self->rsa_priv);
|
||||||
|
crypto_rsa_pub_key_del(self->rsa_pub);
|
||||||
|
|
||||||
|
self->rsa_priv = crypto_rsa_priv_key_new();
|
||||||
|
self->rsa_pub = crypto_rsa_pub_key_new();
|
||||||
|
|
||||||
|
bool ok = crypto_rsa_priv_key_load(self->rsa_priv, self->rsa_pub, path);
|
||||||
|
return ok ? 0 : -1;
|
||||||
|
#endif
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
|
@ -82,6 +82,8 @@ static int stream_gnutls__flush(struct stream* base)
|
||||||
{
|
{
|
||||||
struct stream_gnutls* self = (struct stream_gnutls*)base;
|
struct stream_gnutls* self = (struct stream_gnutls*)base;
|
||||||
while (!TAILQ_EMPTY(&self->base.send_queue)) {
|
while (!TAILQ_EMPTY(&self->base.send_queue)) {
|
||||||
|
assert(self->base.state != STREAM_STATE_CLOSED);
|
||||||
|
|
||||||
struct stream_req* req = TAILQ_FIRST(&self->base.send_queue);
|
struct stream_req* req = TAILQ_FIRST(&self->base.send_queue);
|
||||||
|
|
||||||
ssize_t rc = gnutls_record_send(self->session,
|
ssize_t rc = gnutls_record_send(self->session,
|
||||||
|
|
|
@ -0,0 +1,208 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 Andri Yngvason
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
||||||
|
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
* PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
|
||||||
|
#include "rcbuf.h"
|
||||||
|
#include "stream.h"
|
||||||
|
#include "stream-tcp.h"
|
||||||
|
#include "stream-common.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
#include "neatvnc.h"
|
||||||
|
|
||||||
|
#define RSA_AES_BUFFER_SIZE 8192
|
||||||
|
#define UDIV_UP(a, b) (((a) + (b) - 1) / (b))
|
||||||
|
|
||||||
|
struct stream_rsa_aes {
|
||||||
|
struct stream base;
|
||||||
|
|
||||||
|
size_t read_index;
|
||||||
|
uint8_t* read_buffer;
|
||||||
|
|
||||||
|
struct crypto_cipher* cipher;
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(struct stream_rsa_aes) <= STREAM_ALLOC_SIZE,
|
||||||
|
"struct stream_rsa_aes has grown too large, increase STREAM_ALLOC_SIZE");
|
||||||
|
|
||||||
|
static void stream_rsa_aes_destroy(struct stream* base)
|
||||||
|
{
|
||||||
|
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
|
||||||
|
crypto_cipher_del(self->cipher);
|
||||||
|
free(self->read_buffer);
|
||||||
|
stream_tcp_destroy(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void stream_rsa_aes_read_into_buffer(struct stream_rsa_aes* self)
|
||||||
|
{
|
||||||
|
ssize_t n_read = stream_tcp_read(&self->base,
|
||||||
|
self->read_buffer + self->read_index,
|
||||||
|
RSA_AES_BUFFER_SIZE - self->read_index);
|
||||||
|
if (n_read > 0)
|
||||||
|
self->read_index += n_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t stream_rsa_aes_parse_header(struct stream_rsa_aes* self)
|
||||||
|
{
|
||||||
|
if (self->read_index <= 2) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t len_be;
|
||||||
|
memcpy(&len_be, self->read_buffer, sizeof(len_be));
|
||||||
|
size_t len = ntohs(len_be);
|
||||||
|
|
||||||
|
if (self->read_index < 2 + 16 + len) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t stream_rsa_aes_read_message(struct stream_rsa_aes* self,
|
||||||
|
uint8_t* dst, size_t size)
|
||||||
|
{
|
||||||
|
ssize_t msg_len = stream_rsa_aes_parse_header(self);
|
||||||
|
if (msg_len < 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The entire message must fit in dst
|
||||||
|
/* TODO: With this, stream_tcp__on_event won't run until network input
|
||||||
|
* is received. We need to somehow schedule on_event or also buffer the
|
||||||
|
* decrypted data here.
|
||||||
|
* Another option would be to keep back the message counter in the
|
||||||
|
* cipher until the message has been fully read.
|
||||||
|
*/
|
||||||
|
if ((size_t)msg_len > size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
uint16_t msg_len_be = htons(msg_len);
|
||||||
|
|
||||||
|
uint8_t expected_mac[16];
|
||||||
|
ssize_t n = crypto_cipher_decrypt(self->cipher, dst, expected_mac,
|
||||||
|
self->read_buffer + 2, msg_len,
|
||||||
|
(uint8_t*)&msg_len_be, sizeof(msg_len_be));
|
||||||
|
|
||||||
|
uint8_t* actual_mac = self->read_buffer + 2 + msg_len;
|
||||||
|
if (memcmp(expected_mac, actual_mac, 16) != 0) {
|
||||||
|
nvnc_log(NVNC_LOG_DEBUG, "Message authentication failed");
|
||||||
|
errno = EBADMSG;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->read_index -= 2 + 16 + msg_len;
|
||||||
|
memmove(self->read_buffer, self->read_buffer + 2 + 16 + msg_len,
|
||||||
|
self->read_index);
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t stream_rsa_aes_read(struct stream* base, void* dst, size_t size)
|
||||||
|
{
|
||||||
|
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
|
||||||
|
|
||||||
|
stream_rsa_aes_read_into_buffer(self);
|
||||||
|
if (self->base.state == STREAM_STATE_CLOSED)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
size_t total_read = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ssize_t n_read = stream_rsa_aes_read_message(self, dst, size);
|
||||||
|
if (n_read == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (n_read < 0) {
|
||||||
|
if (errno == EAGAIN) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
total_read += n_read;
|
||||||
|
dst += n_read;
|
||||||
|
size -= n_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
return total_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int stream_rsa_aes_send(struct stream* base, struct rcbuf* payload,
|
||||||
|
stream_req_fn on_done, void* userdata)
|
||||||
|
{
|
||||||
|
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
|
||||||
|
size_t n_msg = UDIV_UP(payload->size, RSA_AES_BUFFER_SIZE);
|
||||||
|
|
||||||
|
struct vec buf;
|
||||||
|
vec_init(&buf, payload->size + n_msg * (2 + 16));
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n_msg; ++i) {
|
||||||
|
size_t msglen = MIN(payload->size - i * RSA_AES_BUFFER_SIZE,
|
||||||
|
RSA_AES_BUFFER_SIZE);
|
||||||
|
uint16_t msglen_be = htons(msglen);
|
||||||
|
|
||||||
|
vec_append(&buf, &msglen_be, sizeof(msglen_be));
|
||||||
|
|
||||||
|
uint8_t mac[16];
|
||||||
|
crypto_cipher_encrypt(self->cipher, &buf, mac,
|
||||||
|
payload->payload + i * RSA_AES_BUFFER_SIZE,
|
||||||
|
msglen, (uint8_t*)&msglen_be, sizeof(msglen_be));
|
||||||
|
vec_append(&buf, mac, sizeof(mac));
|
||||||
|
}
|
||||||
|
|
||||||
|
int r = stream_tcp_send(base, rcbuf_new(buf.data, buf.len), on_done,
|
||||||
|
userdata);
|
||||||
|
if (r < 0) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return payload->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct stream_impl impl = {
|
||||||
|
.close = stream_tcp_close,
|
||||||
|
.destroy = stream_rsa_aes_destroy,
|
||||||
|
.read = stream_rsa_aes_read,
|
||||||
|
.send = stream_rsa_aes_send,
|
||||||
|
};
|
||||||
|
|
||||||
|
int stream_upgrade_to_rsa_eas(struct stream* base,
|
||||||
|
enum crypto_cipher_type cipher_type,
|
||||||
|
const uint8_t* enc_key, const uint8_t* dec_key)
|
||||||
|
{
|
||||||
|
struct stream_rsa_aes* self = (struct stream_rsa_aes*)base;
|
||||||
|
|
||||||
|
self->read_index = 0;
|
||||||
|
self->read_buffer = malloc(RSA_AES_BUFFER_SIZE);
|
||||||
|
if (!self->read_buffer)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
self->cipher = crypto_cipher_new(enc_key, dec_key, cipher_type);
|
||||||
|
if (!self->cipher) {
|
||||||
|
free(self->read_buffer);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->base.impl = &impl;
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -30,12 +30,14 @@
|
||||||
#include "rcbuf.h"
|
#include "rcbuf.h"
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "stream-common.h"
|
#include "stream-common.h"
|
||||||
|
#include "stream-tcp.h"
|
||||||
#include "sys/queue.h"
|
#include "sys/queue.h"
|
||||||
|
#include "neatvnc.h"
|
||||||
|
|
||||||
static_assert(sizeof(struct stream) <= STREAM_ALLOC_SIZE,
|
static_assert(sizeof(struct stream) <= STREAM_ALLOC_SIZE,
|
||||||
"struct stream has grown too large, increase STREAM_ALLOC_SIZE");
|
"struct stream has grown too large, increase STREAM_ALLOC_SIZE");
|
||||||
|
|
||||||
static int stream_tcp_close(struct stream* self)
|
int stream_tcp_close(struct stream* self)
|
||||||
{
|
{
|
||||||
if (self->state == STREAM_STATE_CLOSED)
|
if (self->state == STREAM_STATE_CLOSED)
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -55,8 +57,9 @@ static int stream_tcp_close(struct stream* self)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stream_tcp_destroy(struct stream* self)
|
void stream_tcp_destroy(struct stream* self)
|
||||||
{
|
{
|
||||||
|
vec_destroy(&self->tmp_buf);
|
||||||
stream_close(self);
|
stream_close(self);
|
||||||
aml_unref(self->handler);
|
aml_unref(self->handler);
|
||||||
free(self);
|
free(self);
|
||||||
|
@ -76,7 +79,8 @@ static int stream_tcp__flush(struct stream* self)
|
||||||
if (req->exec) {
|
if (req->exec) {
|
||||||
if (req->payload)
|
if (req->payload)
|
||||||
rcbuf_unref(req->payload);
|
rcbuf_unref(req->payload);
|
||||||
req->payload = req->exec(self, req->userdata);
|
struct rcbuf* payload = req->exec(self, req->userdata);
|
||||||
|
req->payload = payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
iov[n_msgs].iov_base = req->payload->payload;
|
iov[n_msgs].iov_base = req->payload->payload;
|
||||||
|
@ -182,20 +186,28 @@ static void stream_tcp__on_event(void* obj)
|
||||||
stream_tcp__on_writable(self);
|
stream_tcp__on_writable(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ssize_t stream_tcp_read(struct stream* self, void* dst, size_t size)
|
ssize_t stream_tcp_read(struct stream* self, void* dst, size_t size)
|
||||||
{
|
{
|
||||||
if (self->state != STREAM_STATE_NORMAL)
|
if (self->state != STREAM_STATE_NORMAL)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
ssize_t rc = read(self->fd, dst, size);
|
uint8_t* read_buffer = dst;
|
||||||
|
|
||||||
|
if (self->cipher) {
|
||||||
|
vec_reserve(&self->tmp_buf, size);
|
||||||
|
read_buffer = self->tmp_buf.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t rc = read(self->fd, read_buffer, size);
|
||||||
if (rc == 0)
|
if (rc == 0)
|
||||||
stream__remote_closed(self);
|
stream__remote_closed(self);
|
||||||
if (rc > 0)
|
if (rc > 0)
|
||||||
self->bytes_received += rc;
|
self->bytes_received += rc;
|
||||||
|
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stream_tcp_send(struct stream* self, struct rcbuf* payload,
|
int stream_tcp_send(struct stream* self, struct rcbuf* payload,
|
||||||
stream_req_fn on_done, void* userdata)
|
stream_req_fn on_done, void* userdata)
|
||||||
{
|
{
|
||||||
if (self->state == STREAM_STATE_CLOSED)
|
if (self->state == STREAM_STATE_CLOSED)
|
||||||
|
@ -214,7 +226,7 @@ static int stream_tcp_send(struct stream* self, struct rcbuf* payload,
|
||||||
return stream_tcp__flush(self);
|
return stream_tcp__flush(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int stream_tcp_send_first(struct stream* self, struct rcbuf* payload)
|
int stream_tcp_send_first(struct stream* self, struct rcbuf* payload)
|
||||||
{
|
{
|
||||||
if (self->state == STREAM_STATE_CLOSED)
|
if (self->state == STREAM_STATE_CLOSED)
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -229,7 +241,7 @@ static int stream_tcp_send_first(struct stream* self, struct rcbuf* payload)
|
||||||
return stream_tcp__flush(self);
|
return stream_tcp__flush(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void stream_tcp_exec_and_send(struct stream* self,
|
void stream_tcp_exec_and_send(struct stream* self,
|
||||||
stream_exec_fn exec_fn, void* userdata)
|
stream_exec_fn exec_fn, void* userdata)
|
||||||
{
|
{
|
||||||
if (self->state == STREAM_STATE_CLOSED)
|
if (self->state == STREAM_STATE_CLOSED)
|
||||||
|
@ -256,12 +268,9 @@ static struct stream_impl impl = {
|
||||||
.exec_and_send = stream_tcp_exec_and_send,
|
.exec_and_send = stream_tcp_exec_and_send,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stream* stream_new(int fd, stream_event_fn on_event, void* userdata)
|
int stream_tcp_init(struct stream* self, int fd, stream_event_fn on_event,
|
||||||
|
void* userdata)
|
||||||
{
|
{
|
||||||
struct stream* self = calloc(1, STREAM_ALLOC_SIZE);
|
|
||||||
if (!self)
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
self->impl = &impl,
|
self->impl = &impl,
|
||||||
self->fd = fd;
|
self->fd = fd;
|
||||||
self->on_event = on_event;
|
self->on_event = on_event;
|
||||||
|
@ -273,19 +282,31 @@ struct stream* stream_new(int fd, stream_event_fn on_event, void* userdata)
|
||||||
|
|
||||||
self->handler = aml_handler_new(fd, stream_tcp__on_event, self, NULL);
|
self->handler = aml_handler_new(fd, stream_tcp__on_event, self, NULL);
|
||||||
if (!self->handler)
|
if (!self->handler)
|
||||||
goto failure;
|
return -1;
|
||||||
|
|
||||||
if (aml_start(aml_get_default(), self->handler) < 0)
|
if (aml_start(aml_get_default(), self->handler) < 0)
|
||||||
goto start_failure;
|
goto start_failure;
|
||||||
|
|
||||||
stream__poll_r(self);
|
stream__poll_r(self);
|
||||||
|
|
||||||
return self;
|
return 0;
|
||||||
|
|
||||||
start_failure:
|
start_failure:
|
||||||
aml_unref(self->handler);
|
aml_unref(self->handler);
|
||||||
self = NULL; /* Handled in unref */
|
return -1;
|
||||||
failure:
|
}
|
||||||
|
|
||||||
|
struct stream* stream_new(int fd, stream_event_fn on_event, void* userdata)
|
||||||
|
{
|
||||||
|
struct stream* self = calloc(1, STREAM_ALLOC_SIZE);
|
||||||
|
if (!self)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
if (stream_tcp_init(self, fd, on_event, userdata) < 0) {
|
||||||
free(self);
|
free(self);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
#include "stream.h"
|
#include "stream.h"
|
||||||
#include "stream-common.h"
|
#include "stream-common.h"
|
||||||
|
#include "stream-tcp.h"
|
||||||
#include "websocket.h"
|
#include "websocket.h"
|
||||||
#include "vec.h"
|
#include "vec.h"
|
||||||
#include "neatvnc.h"
|
#include "neatvnc.h"
|
||||||
|
@ -38,31 +39,17 @@ struct stream_ws_exec_ctx {
|
||||||
|
|
||||||
struct stream_ws {
|
struct stream_ws {
|
||||||
struct stream base;
|
struct stream base;
|
||||||
|
stream_event_fn on_event;
|
||||||
enum stream_ws_state ws_state;
|
enum stream_ws_state ws_state;
|
||||||
struct ws_frame_header header;
|
struct ws_frame_header header;
|
||||||
enum ws_opcode current_opcode;
|
enum ws_opcode current_opcode;
|
||||||
uint8_t read_buffer[4096]; // TODO: Is this a reasonable size?
|
|
||||||
size_t read_index;
|
size_t read_index;
|
||||||
struct stream* tcp_stream;
|
uint8_t read_buffer[4096]; // TODO: Is this a reasonable size?
|
||||||
};
|
};
|
||||||
|
|
||||||
static int stream_ws_close(struct stream* self)
|
|
||||||
{
|
|
||||||
struct stream_ws* ws = (struct stream_ws*)self;
|
|
||||||
self->state = STREAM_STATE_CLOSED;
|
|
||||||
return stream_close(ws->tcp_stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void stream_ws_destroy(struct stream* self)
|
|
||||||
{
|
|
||||||
struct stream_ws* ws = (struct stream_ws*)self;
|
|
||||||
stream_destroy(ws->tcp_stream);
|
|
||||||
free(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void stream_ws_read_into_buffer(struct stream_ws* ws)
|
static void stream_ws_read_into_buffer(struct stream_ws* ws)
|
||||||
{
|
{
|
||||||
ssize_t n_read = stream_read(ws->tcp_stream,
|
ssize_t n_read = stream_tcp_read(&ws->base,
|
||||||
ws->read_buffer + ws->read_index,
|
ws->read_buffer + ws->read_index,
|
||||||
sizeof(ws->read_buffer) - ws->read_index);
|
sizeof(ws->read_buffer) - ws->read_index);
|
||||||
if (n_read > 0)
|
if (n_read > 0)
|
||||||
|
@ -104,14 +91,15 @@ static ssize_t stream_ws_process_ping(struct stream_ws* ws, size_t offset)
|
||||||
|
|
||||||
uint8_t buf[WS_HEADER_MIN_SIZE];
|
uint8_t buf[WS_HEADER_MIN_SIZE];
|
||||||
int reply_len = ws_write_frame_header(buf, &reply);
|
int reply_len = ws_write_frame_header(buf, &reply);
|
||||||
stream_write(ws->tcp_stream, buf, reply_len, NULL, NULL);
|
stream_tcp_send(&ws->base, rcbuf_from_mem(buf, reply_len),
|
||||||
|
NULL, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
int payload_len = MIN(ws->read_index, ws->header.payload_length);
|
int payload_len = MIN(ws->read_index, ws->header.payload_length);
|
||||||
|
|
||||||
// Feed back the payload:
|
// Feed back the payload:
|
||||||
stream_write(ws->tcp_stream, ws->read_buffer + offset,
|
stream_tcp_send(&ws->base, rcbuf_from_mem(ws->read_buffer + offset,
|
||||||
payload_len, NULL, NULL);
|
payload_len), NULL, NULL);
|
||||||
|
|
||||||
stream_ws_advance_read_buffer(ws, payload_len, offset);
|
stream_ws_advance_read_buffer(ws, payload_len, offset);
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -124,7 +112,7 @@ static ssize_t stream_ws_process_payload(struct stream_ws* ws, void* dst,
|
||||||
case WS_OPCODE_CONT:
|
case WS_OPCODE_CONT:
|
||||||
// Remote end started with a continuation frame. This is
|
// Remote end started with a continuation frame. This is
|
||||||
// unexpected, so we'll just close.
|
// unexpected, so we'll just close.
|
||||||
stream__remote_closed(ws->tcp_stream);
|
stream__remote_closed(&ws->base);
|
||||||
return 0;
|
return 0;
|
||||||
case WS_OPCODE_TEXT:
|
case WS_OPCODE_TEXT:
|
||||||
// This is unexpected, but let's just ignore it...
|
// This is unexpected, but let's just ignore it...
|
||||||
|
@ -133,7 +121,7 @@ static ssize_t stream_ws_process_payload(struct stream_ws* ws, void* dst,
|
||||||
case WS_OPCODE_BIN:
|
case WS_OPCODE_BIN:
|
||||||
return stream_ws_copy_payload(ws, dst, size, offset);
|
return stream_ws_copy_payload(ws, dst, size, offset);
|
||||||
case WS_OPCODE_CLOSE:
|
case WS_OPCODE_CLOSE:
|
||||||
stream__remote_closed(ws->tcp_stream);
|
stream__remote_closed(&ws->base);
|
||||||
return 0;
|
return 0;
|
||||||
case WS_OPCODE_PING:
|
case WS_OPCODE_PING:
|
||||||
return stream_ws_process_ping(ws, offset);
|
return stream_ws_process_ping(ws, offset);
|
||||||
|
@ -213,8 +201,8 @@ static ssize_t stream_ws_read_handshake(struct stream_ws* ws, void* dst,
|
||||||
if (header_len < 0)
|
if (header_len < 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
ws->tcp_stream->cork = false;
|
ws->base.cork = false;
|
||||||
stream_send_first(ws->tcp_stream, rcbuf_from_mem(reply, strlen(reply)));
|
stream_tcp_send_first(&ws->base, rcbuf_from_mem(reply, strlen(reply)));
|
||||||
|
|
||||||
ws->read_index -= header_len;
|
ws->read_index -= header_len;
|
||||||
memmove(ws->read_buffer, ws->read_buffer + header_len, ws->read_index);
|
memmove(ws->read_buffer, ws->read_buffer + header_len, ws->read_index);
|
||||||
|
@ -255,18 +243,23 @@ static int stream_ws_send(struct stream* self, struct rcbuf* payload,
|
||||||
uint8_t raw_head[WS_HEADER_MIN_SIZE];
|
uint8_t raw_head[WS_HEADER_MIN_SIZE];
|
||||||
int head_len = ws_write_frame_header(raw_head, &head);
|
int head_len = ws_write_frame_header(raw_head, &head);
|
||||||
|
|
||||||
stream_write(ws->tcp_stream, &raw_head, head_len, NULL, NULL);
|
stream_tcp_send(&ws->base, rcbuf_from_mem(&raw_head, head_len),
|
||||||
return stream_send(ws->tcp_stream, payload, on_done, userdata);
|
NULL, NULL);
|
||||||
|
return stream_tcp_send(&ws->base, payload, on_done, userdata);
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct rcbuf* stream_ws_chained_exec(struct stream* tcp_stream,
|
static struct rcbuf* stream_ws_chained_exec(struct stream* tcp_stream,
|
||||||
void* userdata)
|
void* userdata)
|
||||||
{
|
{
|
||||||
|
struct stream_ws* ws = (struct stream_ws*)tcp_stream;
|
||||||
struct stream_ws_exec_ctx* ctx = userdata;
|
struct stream_ws_exec_ctx* ctx = userdata;
|
||||||
struct stream_ws* ws = tcp_stream->userdata;
|
|
||||||
|
|
||||||
struct rcbuf* buf = ctx->exec(&ws->base, ctx->userdata);
|
struct rcbuf* buf = ctx->exec(&ws->base, ctx->userdata);
|
||||||
|
|
||||||
|
// TODO: This also needs to be cleaned it it's left on the send queue
|
||||||
|
// when the stream is destroyed.
|
||||||
|
free(ctx->userdata);
|
||||||
|
|
||||||
struct vec out;
|
struct vec out;
|
||||||
vec_init(&out, WS_HEADER_MIN_SIZE + buf->size + 1);
|
vec_init(&out, WS_HEADER_MIN_SIZE + buf->size + 1);
|
||||||
|
|
||||||
|
@ -279,6 +272,7 @@ static struct rcbuf* stream_ws_chained_exec(struct stream* tcp_stream,
|
||||||
out.len += head_len;
|
out.len += head_len;
|
||||||
|
|
||||||
vec_append(&out, buf->payload, buf->size);
|
vec_append(&out, buf->payload, buf->size);
|
||||||
|
rcbuf_unref(buf);
|
||||||
return rcbuf_new(out.data, out.len);
|
return rcbuf_new(out.data, out.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,23 +287,12 @@ static void stream_ws_exec_and_send(struct stream* self, stream_exec_fn exec,
|
||||||
ctx->exec = exec;
|
ctx->exec = exec;
|
||||||
ctx->userdata = userdata;
|
ctx->userdata = userdata;
|
||||||
|
|
||||||
stream_exec_and_send(ws->tcp_stream, stream_ws_chained_exec, ctx);
|
stream_tcp_exec_and_send(&ws->base, stream_ws_chained_exec, ctx);
|
||||||
}
|
|
||||||
|
|
||||||
static void stream_ws_event(struct stream* self, enum stream_event event)
|
|
||||||
{
|
|
||||||
struct stream_ws* ws = self->userdata;
|
|
||||||
|
|
||||||
if (event == STREAM_EVENT_REMOTE_CLOSED) {
|
|
||||||
ws->base.state = STREAM_STATE_CLOSED;
|
|
||||||
}
|
|
||||||
|
|
||||||
ws->base.on_event(&ws->base, event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static struct stream_impl impl = {
|
static struct stream_impl impl = {
|
||||||
.close = stream_ws_close,
|
.close = stream_tcp_close,
|
||||||
.destroy = stream_ws_destroy,
|
.destroy = stream_tcp_destroy,
|
||||||
.read = stream_ws_read,
|
.read = stream_ws_read,
|
||||||
.send = stream_ws_send,
|
.send = stream_ws_send,
|
||||||
.exec_and_send = stream_ws_exec_and_send,
|
.exec_and_send = stream_ws_exec_and_send,
|
||||||
|
@ -321,19 +304,11 @@ struct stream* stream_ws_new(int fd, stream_event_fn on_event, void* userdata)
|
||||||
if (!self)
|
if (!self)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
self->base.state = STREAM_STATE_NORMAL;
|
stream_tcp_init(&self->base, fd, on_event, userdata);
|
||||||
self->base.impl = &impl;
|
self->base.impl = &impl;
|
||||||
self->base.on_event = on_event;
|
|
||||||
self->base.userdata = userdata;
|
|
||||||
|
|
||||||
self->tcp_stream = stream_new(fd, stream_ws_event, self);
|
|
||||||
if (!self->tcp_stream) {
|
|
||||||
free(self);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't send anything until handshake is done:
|
// Don't send anything until handshake is done:
|
||||||
self->tcp_stream->cork = true;
|
self->base.cork = true;
|
||||||
|
|
||||||
return &self->base;
|
return &self->base;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
#include "websocket.h"
|
#include "websocket.h"
|
||||||
#include "http.h"
|
#include "http.h"
|
||||||
|
#include "crypto.h"
|
||||||
#include <nettle/sha1.h>
|
#include "base64.h"
|
||||||
#include <nettle/base64.h>
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -61,16 +60,16 @@ ssize_t ws_handshake(char* output, size_t output_maxlen, const char* input)
|
||||||
if (have_versions && !strstr(versions, ",13,"))
|
if (have_versions && !strstr(versions, ",13,"))
|
||||||
goto failure;
|
goto failure;
|
||||||
|
|
||||||
struct sha1_ctx ctx;
|
uint8_t hash[20];
|
||||||
sha1_init(&ctx);
|
crypto_hash_many(hash, sizeof(hash), CRYPTO_HASH_SHA1,
|
||||||
sha1_update(&ctx, strlen(challenge), (const uint8_t*)challenge);
|
(struct crypto_data_entry[]){
|
||||||
sha1_update(&ctx, strlen(magic_uuid), (const uint8_t*)magic_uuid);
|
{ (uint8_t*)challenge, strlen(challenge) },
|
||||||
|
{ (uint8_t*)magic_uuid, strlen(magic_uuid) },
|
||||||
|
{}
|
||||||
|
});
|
||||||
|
|
||||||
uint8_t hash[SHA1_DIGEST_SIZE];
|
char response[BASE64_ENCODED_SIZE(sizeof(hash))] = {};
|
||||||
sha1_digest(&ctx, sizeof(hash), hash);
|
base64_encode(response, hash, sizeof(hash));
|
||||||
|
|
||||||
char response[BASE64_ENCODE_RAW_LENGTH(SHA1_DIGEST_SIZE) + 1] = {};
|
|
||||||
base64_encode_raw(response, SHA1_DIGEST_SIZE, hash);
|
|
||||||
|
|
||||||
size_t len = snprintf(output, output_maxlen,
|
size_t len = snprintf(output, output_maxlen,
|
||||||
"HTTP/1.1 101 Switching Protocols\r\n"
|
"HTTP/1.1 101 Switching Protocols\r\n"
|
||||||
|
|
|
@ -10,3 +10,12 @@ pixels = executable('pixels',
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
test('pixels', pixels)
|
test('pixels', pixels)
|
||||||
|
|
||||||
|
base64 = executable('base64',
|
||||||
|
[
|
||||||
|
'test-base64.c',
|
||||||
|
'../src/base64.c',
|
||||||
|
],
|
||||||
|
include_directories: inc,
|
||||||
|
)
|
||||||
|
test('base64', base64)
|
||||||
|
|
|
@ -0,0 +1,129 @@
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static bool test_encode_0(void)
|
||||||
|
{
|
||||||
|
char buf[1] = {};
|
||||||
|
base64_encode(buf, (const uint8_t*)"", 0);
|
||||||
|
return strlen(buf) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_encode_1(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "a";
|
||||||
|
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
|
||||||
|
return strcmp(buf, "YQ==") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_encode_2(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "ab";
|
||||||
|
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
|
||||||
|
return strcmp(buf, "YWI=") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_encode_3(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "abc";
|
||||||
|
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
|
||||||
|
return strcmp(buf, "YWJj") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_encode_4(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "abcd";
|
||||||
|
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
|
||||||
|
return strcmp(buf, "YWJjZA==") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_encode_5(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "abcde";
|
||||||
|
char buf[BASE64_ENCODED_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
base64_encode(buf, (const uint8_t*)input, sizeof(input) - 1);
|
||||||
|
return strcmp(buf, "YWJjZGU=") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_decode_0(void)
|
||||||
|
{
|
||||||
|
uint8_t buf[1] = {};
|
||||||
|
ssize_t r = base64_decode(buf, "");
|
||||||
|
return r == 0 && buf[0] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_decode_1(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "YQ==";
|
||||||
|
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
ssize_t r = base64_decode(buf, input);
|
||||||
|
return r == 1 && memcmp(buf, "a", r) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_decode_2(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "YWI=";
|
||||||
|
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
ssize_t r = base64_decode(buf, input);
|
||||||
|
return r == 2 && memcmp(buf, "ab", r) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_decode_3(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "YWJj";
|
||||||
|
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
ssize_t r = base64_decode(buf, input);
|
||||||
|
return r == 3 && memcmp(buf, "abc", r) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_decode_4(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "YWJjZA==";
|
||||||
|
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
ssize_t r = base64_decode(buf, input);
|
||||||
|
return r == 4 && memcmp(buf, "abcd", r) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_decode_5(void)
|
||||||
|
{
|
||||||
|
static const char input[] = "YWJjZGU=";
|
||||||
|
uint8_t buf[BASE64_DECODED_MAX_SIZE(sizeof(input) - 1)] = {};
|
||||||
|
ssize_t r = base64_decode(buf, input);
|
||||||
|
return r == 5 && memcmp(buf, "abcde", r) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define XSTR(s) STR(s)
|
||||||
|
#define STR(s) #s
|
||||||
|
|
||||||
|
#define RUN_TEST(name) ({ \
|
||||||
|
bool ok = test_ ## name(); \
|
||||||
|
printf("[%s] %s\n", ok ? " OK " : "FAIL", XSTR(name)); \
|
||||||
|
ok; \
|
||||||
|
})
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
bool ok = true;
|
||||||
|
|
||||||
|
ok &= RUN_TEST(encode_0);
|
||||||
|
ok &= RUN_TEST(encode_1);
|
||||||
|
ok &= RUN_TEST(encode_2);
|
||||||
|
ok &= RUN_TEST(encode_3);
|
||||||
|
ok &= RUN_TEST(encode_4);
|
||||||
|
ok &= RUN_TEST(encode_5);
|
||||||
|
|
||||||
|
ok &= RUN_TEST(decode_0);
|
||||||
|
ok &= RUN_TEST(decode_1);
|
||||||
|
ok &= RUN_TEST(decode_2);
|
||||||
|
ok &= RUN_TEST(decode_3);
|
||||||
|
ok &= RUN_TEST(decode_4);
|
||||||
|
ok &= RUN_TEST(decode_5);
|
||||||
|
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
Loading…
Reference in New Issue