A port of Mbed-TLS for the Classic Macintosh OS 7/8/9
Classic Macintosh OS 7/8/9 上的 Mbed-TLS 移植
bbenchoff/MacSSL
文件夹和文件
名称| 名称| 最后提交消息| 最后提交日期
---|---|---|---
最新提交
历史记录
3 次提交
Project| Project
.gitattributes| .gitattributes
Archive.sit| Archive.sit
README.md| README.md
查看所有文件
仓库文件导航
MacSSL
Classic Macintosh OS 7/8/9 上的 Mbed-TLS 移植
这是一个用于 Mac System 7/8/9 的 C89/C90 版本的 MbedTLS 移植。它能正常工作,并且可以在 Metrowerks Codewarrior Pro 4 下编译。以下是证明:
这是一个基本的应用程序,可以对 api.h 中的任何内容执行 GET 请求,并将结果输出到文本框(当然,包含大量调试信息)。这个项目的想法是为 640by480 构建一个“应用程序”,它是我为老式数码相机制作的“instagram 克隆版”。想法是登录、发布图像、查看图像和阅读评论。我需要 HTTPS 才能做到这一点,所以这里有一个经典 mac 的 MbedTLS 端口。
此仓库中的内容
该仓库包含我的 Metrowerks Codewarrior 项目中 /Project 文件夹中的所有文件。理想情况下,如果 Mac 没有整个“资源问题”以及“\n \r \cr”问题,您可以将该文件夹下载到您的 Mac,使用 Metrowerks Codewarrior Pro 4 打开项目文件,并编译该应用程序。
但我们并没有生活在一个完美的世界中,我必须处理 Mac 文件资源等等,所以我还压缩了该文件夹。整个 项目文件夹,包含 Codewarrior 项目文件、源文件、编译器输出和 polarssl 库,都可以在 Archive.sit
文件中找到。这是使用 DropStuff 4.0 压缩的,应该可以使用任何 Stuffit 工具打开。下载 Archive.sit
项目,在您的 mac 上进行 unstuffit,您将拥有所需的一切。
整个 PolarSSL 库并非_完全_在此项目中。编译所需的文件位于 /PolarSSL 文件夹中。在 PolarSSL 仓库中找到但在项目中未使用的文件位于 /polarssl-repo 文件夹中。是的,这是仓库的完整副本,减去了我使用的文件夹。是的,这很烦人,跟我说说。
此端口基于什么,以及局限性
此端口基于 polarssl,它本身是 Mbed-TLS 的一个分支,版本大约为 2.29.9。这是一个 C 库,实现了加密原语、X.509 证书操作和 SSL/TLS 协议。
目前,此库的最低配置支持以下内容:
密码套件
MBEDTLS_TLS_RSA_WITH_AES_128_CBC_SHA
MBEDTLS_TLS_RSA_WITH_AES_256_CBC_SHA
椭圆曲线
MBEDTLS_ECP_DP_SECP256R1
签名算法
SHA-256 + RSA
SHA-384 + RSA
SHA-1 + RSA
证书处理
- 根证书
ISRG Root X1
- 中间证书
Let's Encrypt R11
所有这些都包装到对 TLS 1.1
的支持中。这足以满足 我 想要做的事情,但它提供了一个添加 TLS 1.2
的基本框架,额外的密码套件(如 ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
)和更多的椭圆曲线(如 ECP_DP_CURVE25519
)。框架就在那里,但如果您想添加这些,则需要做一些工作。
示例应用程序
该仓库为 Mac System 7/8/9 生成一个 FAT 编译的应用程序。它通过 OpenTransport 库工作,即不支持 MacTCP。此应用程序向服务器的 API 端点 https://640by480.com/api/v1/posts 发送 GET 请求,并将结果返回到文本框,并将文件写入到运行该应用程序的同一位置的磁盘。此文件 SSL-Debug.txt
还会保存来自 mbedtls_debug_set_threshold()
的调试信息;可以在 SSLWrapper.c
中调整此调试阈值的值。更多信息如下。
SSL 实现挑战
Mbedtls 是为 C99 编译器编写的,但我的 CodeWarrior 版本仅支持 C89/C90。过渡需要进行大量的代码修改:
- 为现代 C 整数类型创建兼容性层
- 实现 64 位整数仿真
- 重构代码以在块开头声明变量(C89 要求)
- 解决 Mac 的非 *NIX 样文件系统中的包含路径限制
最后一点——解决路径限制——是一个大问题。你知道你如何编写 #include "mbedtls/aes.h"
,并且编译器将从 aes.h
文件(位于 mbedtls
文件夹中)中提取代码吗?您不能在 Mac 上执行此操作!或者至少我不知道 Codewarrior 如何定义路径。解决方案基本上是将 Mbed-TLS 中的所有文件作为平面目录放入项目中。
最大的问题? C89 不支持可变参数宏或方法重载。在此平台上完全不知道 64 位整数。如果您不知道我在说什么,这里有一个方法重载的示例:
void print(int x);
void print(const char* s);
这些是两个函数,它们都返回 nothing,但其中一个采用 int,另一个采用字符串。它们都被命名为相同的东西。如果您有方法重载(例如 C99 中发现的),这会起作用。C89/90 没有它,并且由于这个原因,将 C89 代码移植到 C99 是一个麻烦。这也出现在可变参数宏中,我相信这是 variable argument 的一个组合词。它看起来像这样:
#define superprint(...) fprintf(stderr, __VA_ARGS__);
这是一种执行类似于方法重载的方法,但使用预处理器而不是语言本身。显然,我们在这个世纪看到了更多的方法重载,仅仅是因为语言现在支持它,所以对于同一事物使用不同的名称,我猜。
是的,这是一个非常耗时且无聊的修复。
转换为 64 位数据结构
mbedtls 库使用 64 位数据类型。int64_t
、uint64_t
等。我的编译器不知道这些是什么。所以我需要创建它们。这在 stdint.h
的重新定义中完成,如下所示:
/*
* mac_stdint.h
*
* 兼容性标头,为 Classic Mac OS 上的 CodeWarrior Pro 4 提供 C99 固定宽度整数类型
*/
#ifndef MAC_STDINT_H
#define MAC_STDINT_H
/* 包括 Mac OS 类型 */
#include <Types.h>
/*
* 基于 Mac OS 类型定义固定宽度整数类型
* Classic Mac OS on 68K processors is big-endian
*/
/* 精确宽度有符号整数类型 */
typedef signed char int8_t;
typedef short int16_t;
typedef long int32_t;
/* 精确宽度无符号整数类型 */
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned long uint32_t;
/* 最小宽度有符号整数类型 */
typedef signed char int_least8_t;
typedef short int_least16_t;
typedef long int_least32_t;
/* 最小宽度无符号整数类型 */
typedef unsigned char uint_least8_t;
typedef unsigned short uint_least16_t;
typedef unsigned long uint_least32_t;
/* 快速最小宽度有符号整数类型 */
typedef signed char int_fast8_t;
typedef short int_fast16_t;
typedef long int_fast32_t;
/* 快速最小宽度无符号整数类型 */
typedef unsigned char uint_fast8_t;
typedef unsigned short uint_fast16_t;
typedef unsigned long uint_fast32_t;
/* 最大宽度整数类型 */
typedef long intmax_t;
typedef unsigned long uintmax_t;
/* 能够保存指针的整数类型 */
typedef long intptr_t;
typedef unsigned long uintptr_t;
/* 精确宽度整数类型的限制 */
#define INT8_MIN (-128)
#define INT16_MIN (-32767-1)
#define INT32_MIN (-2147483647L-1)
#define INT8_MAX 127
#define INT16_MAX 32767
#define INT32_MAX 2147483647L
#define UINT8_MAX 255U
#define UINT16_MAX 65535U
#define UINT32_MAX 4294967295UL
/* 最小宽度整数类型的限制 */
#define INT_LEAST8_MIN INT8_MIN
#define INT_LEAST16_MIN INT16_MIN
#define INT_LEAST32_MIN INT32_MIN
#define INT_LEAST8_MAX INT8_MAX
#define INT_LEAST16_MAX INT16_MAX
#define INT_LEAST32_MAX INT32_MAX
#define UINT_LEAST8_MAX UINT8_MAX
#define UINT_LEAST16_MAX UINT16_MAX
#define UINT_LEAST32_MAX UINT32_MAX
/* 最快最小宽度整数类型的限制 */
#define INT_FAST8_MIN INT8_MIN
#define INT_FAST16_MIN INT16_MIN
#define INT_FAST32_MIN INT32_MIN
#define INT_FAST8_MAX INT8_MAX
#define INT_FAST16_MAX INT16_MAX
#define INT_FAST32_MAX INT32_MAX
#define UINT_FAST8_MAX UINT8_MAX
#define UINT_FAST16_MAX UINT16_MAX
#define UINT_FAST32_MAX UINT32_MAX
/* 能够保存对象指针的整数类型的限制 */
#define INTPTR_MIN INT32_MIN
#define INTPTR_MAX INT32_MAX
#define UINTPTR_MAX UINT32_MAX
/* 最大宽度整数类型的限制 */
#define INTMAX_MIN INT32_MIN
#define INTMAX_MAX INT32_MAX
#define UINTMAX_MAX UINT32_MAX
#define SIZE_MAX UINT32_MAX
/*
* 64 位类型在 Classic Mac OS on 68K 中不受原生支持
* 因此这里有它们
*/
typedef struct {
uint32_t high;
uint32_t low;
} uint64_t;
typedef struct {
int32_t high;
int32_t low;
} int64_t;
/* 用于初始化 64 位值的函数 */
static inline uint64_t uint64_init(unsigned long high, unsigned long low)
{
uint64_t result;
result.high = high;
result.low = low;
return result;
}
/* 用于将 64 位值设置为零的函数 */
static inline void uint64_zero(uint64_t *x)
{
x->high = 0;
x->low = 0;
}
static inline uint64_t uint64_shift_right(uint64_t x, int shift)
{
uint64_t result;
if(shift >= 32)
{
result.high = 0;
result.low = x.high >> (shift - 32);
} else {
result.high = x.high >> shift;
result.low = (x.low >> shift) | (x.high << (32 - shift));
}
return result;
}
static inline uint64_t uint64_shift_left(uint64_t x, int shift)
{
uint64_t result;
if(shift >= 32)
{
result.high = x.low << (shift - 32);
result.low = 0;
} else {
result.high = (x.high << shift) | (x.low >> (32 - shift));
result.low = x.low << shift;
}
return result;
}
static inline uint64_t uint64_xor(uint64_t a, uint64_t b)
{
uint64_t result;
result.high = a.high ^ b.high;
result.low = a.low ^ b.low;
return result;
}
static inline uint64_t uint64_or(uint64_t a, uint64_t b)
{
uint64_t result;
result.high = a.high | b.high;
result.low = a.low | b.low;
return result;
}
static inline uint64_t uint64_from_uint32_high(uint32_t x)
{
uint64_t result;
result.high = x;
result.low = 0;
return result;
}
static inline int uint64_less_than(uint64_t a, uint64_t b)
{
if(a.high < b.high) return 1;
if(a.high > b.high) return 0;
return a.low < b.low;
}
static inline uint64_t uint64_add_size_t(uint64_t a, unsigned long b)
{
uint64_t result;
unsigned long temp = a.low +b;
if(temp<a.low)
{
result.high = a.high + 1;
} else {
result.high = a.high;
}
result.low = temp;
return result;
}
static inline uint64_t uint64_multiply_by_8(uint64_t x)
{
uint64_t result;
unsigned long carry;
//check if shifting left by three would cause bits to shift from low to high
carry = (x.low & 0xE0000000) >> 29; //extract top three bits
result.low = x.low << 3;
//shift high part left by three and add carry
result.high = (x.high << 3) | carry;
return result;
}
static inline int uint64_is_non_zero(uint64_t x)
{
return(x.high != 0 || x.low != 0);
}
#define UINT64_C(h, l) ((uint64_t){(h), (l)})
#define INT64_C(h, l) ((int64_t){(h), (l)})
#define UINT64_LOW(x) ((x).low)
#define UINT64_HIGH(x) ((x).high)
#endif /* MAC_STDINT_H */
mbedtls 库对这些 64 位数据类型执行的操作非常少,将值清零,异或等等,并向右和向左移动。这提供了一个使用 64 位数据类型的基本框架,但它不是自动的。代码中的每个 64 位操作都需要修改。没有快速的方法来做到这一点,它只是点击编译,查看下一个错误,将其更改为可以工作的内容,然后再次点击编译。
在完成此标头文件后,我编写了一个几乎完整的 64 位数据类型,用于实际上根本不支持它的系统。
熵收集噩梦
我发现了一个阿西莫夫短篇小说中的一个大情节漏洞。如果您想知道如何才能大幅度降低宇宙的净熵量,答案不是使用未来数万亿年的计算机,而是使用三十年前制造的计算机。
经典的 Mac OS 熵非常少,这是高质量随机性所必需的。这意味着我的 SSL 实现给出了错误代码 MBEDTLS_ERR_ENTROPY_SOURCE_FAILED
。我创建了一个自定义的熵收集系统,该系统从多个来源提取数据:
- 微秒分辨率的系统时钟和滴答计数
- 鼠标移动跟踪
- 内存状态和分配模式
- 硬件时序变化
- 使用 OTGetTimeStamp() 的网络数据包时序
- TCP 序列号和连接统计信息
- 用户交互之间的时间延迟
- 屏幕保护程序激活所需的时间
所有这些来源都组合并进行异或运算,形成一个足以进行加密操作的随机池。我不会完全称之为 random ,但它足够随机,可以初始化 mbedtls 中的加密子系统。它有效,但我不能保证此熵函数的安全性。 此 mbedtls 实现应被视为不安全。
证书处理
是的,这段代码可以处理证书。当前的证书处理设置为 OPTIONAL
,但在 REQUIRED
时也可以工作。
根证书是 ISRG Root X1,中间证书是 Let's Encrypt R11。这提供了足够的连接到 640by480.com 的终端证书。根证书信任存储在 SSLWrapper.c 的代码中。
调试日志
如上所述,此应用程序有两种输出方法:它将信息(以及最终的 GET 请求结果)显示到文本框。它还将 所有内容 保存到磁盘上的文件中。调试信息的这种分支是由于经典 Macintosh Toolbox 的 TETextBox 的 32k 限制。如果没有一点工作,此窗口不能显示超过 32000 个字符,并且调试信息和 GET 结果的组合可能会将其推到 32k 限制之上。
这意味着我可以将所有 SSL 调试信息保存到一个文件,并将其粘贴到这里。这是使用此应用程序的连接的内部调试信息:
=== SSL DEBUG LOG STARTED ===
SSL DBG [2] => write close notify
SSL DBG [2] <= write close notify
SSL DBG [2] => free
SSL DBG [2] <= free
SSL DBG [2] => handshake
SSL DBG [2] client state: 0
SSL DBG [2] => flush output
SSL DBG [2] <= flush output
SSL DBG [2] client state: 1
SSL DBG [2] => flush output
SSL DBG [2] <= flush output
SSL DBG [2] => write client hello
SSL DBG [3] client hello, max version: [3:2]
SSL DBG [3] dumping 'client hello, random bytes' (32 bytes)
SSL DBG [3] 0000: 95 08 1f c2 40 ed 62 08 c2 e1 e2 0b f1 b1 fa 1d ....@.b.........
SSL DBG [3] 0010: 26 1f a1 02 40 74 b1 58 29 f4 73 b1 de d3 6c a3 &...@t.X).s...l.
SSL DBG [3] client hello, session id len.: %zu
SSL DBG [3] dumping 'client hello, session id' (0 bytes)
SSL DBG [3] client hello, add ciphersuite: 0x2f (TLS-RSA-WITH-AES-128-CBC-SHA)
SSL DBG [3] client hello, add ciphersuite: 0x35 (TLS-RSA-WITH-AES-256-CBC-SHA)
SSL DBG [3] client hello, got %zu ciphersuites (excluding SCSVs)
SSL DBG [3] adding EMPTY_RENEGOTIATION_INFO_SCSV
SSL DBG [3] client hello, compress len.: 1
SSL DBG [3] client hello, compress alg.: 0
SSL DBG [3] client hello, adding server name extension: 640by480.com
SSL DBG [3] client hello, total extension length: %zu
SSL DBG [2] => write handshake message
SSL DBG [2] => write record
SSL DBG [3] output record: msgtype = 22, version = [3:2], msglen = %zu
SSL DBG [4] dumping 'output record sent to network' (77 bytes)
SSL DBG [4] 0000: 16 03 02 00 48 01 00 00 44 03 02 95 08 1f c2 40 ....H...D......@
SSL DBG [4] 0010: ed 62 08 c2 e1 e2 0b f1 b1 fa 1d 26 1f a1 02 40 .b.........&...@
SSL DBG [4] 0020: 74 b1 58 29 f4 73 b1 de d3 6c a3 00 00 06 00 2f t.X).s...l...../
SSL DBG [4] 0030: 00 35 00 ff 01 00 00 15 00 00 00 11 00 0f 00 00 .5..............
SSL DBG [4] 0040: 0c 36 34 30 62 79 34 38 30 2e 63 6f 6d .640by480.com
SSL DBG [2] => flush output
SSL DBG [2] message length: %zu, out_left: %zu
SSL DBG [2] ssl->f_send() returned 77 (-0xffffffb3)
SSL DBG [2] <= flush output
SSL DBG [2] <= write record
SSL DBG [2] <= write handshake message
SSL DBG [2] <= write client hello
SSL DBG [2] client state: 2
SSL DBG [2] => flush output
SSL DBG [2] <= flush output
SSL DBG [2] => parse server hello
SSL DBG [2] => read record
SSL DBG [2] => fetch input
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] ssl->f_recv(_timeout)() returned 5 (-0xfffffffb)
SSL DBG [2] <= fetch input
SSL DBG [4] dumping 'input record header' (5 bytes)
SSL DBG [4] 0000: 16 03 02 00 55 ....U
SSL DBG [3] input record: msgtype = 22, version = [3:2], msglen = %zu
SSL DBG [2] => fetch input
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] ssl->f_recv(_timeout)() returned 85 (-0xffffffab)
SSL DBG [2] <= fetch input
SSL DBG [4] dumping 'input record from network' (90 bytes)
SSL DBG [4] 0000: 16 03 02 00 55 02 00 00 51 03 02 47 08 e7 7a 96 ....U...Q..G..z.
SSL DBG [4] 0010: 68 af 36 03 e3 9d 56 d0 a3 e9 23 df 95 c7 c8 e4 h.6...V...#.....
SSL DBG [4] 0020: 7f 98 b9 44 4f 57 4e 47 52 44 00 20 79 36 3f 83 ...DOWNGRD. y6?.
SSL DBG [4] 0030: e5 a0 e5 be 30 00 62 a4 72 53 a8 30 61 1b 42 a2 ....0.b.rS.0a.B.
SSL DBG [4] 0040: 8d 48 a9 3f c1 a5 52 27 b4 f5 64 cd 00 2f 00 00 .H.?..R'..d../..
SSL DBG [4] 0050: 09 ff 01 00 01 00 00 00 00 00 ..........
SSL DBG [3] handshake message: msglen = %zu, type = %u, hslen = %zu
SSL DBG [2] <= read record
SSL DBG [3] dumping 'server hello, version' (2 bytes)
SSL DBG [3] 0000: 03 02 ..
SSL DBG [3] server hello, current time: 1191765882
SSL DBG [3] dumping 'server hello, random bytes' (32 bytes)
SSL DBG [3] 0000: 47 08 e7 7a 96 68 af 36 03 e3 9d 56 d0 a3 e9 23 G..z.h.6...V...#
SSL DBG [3] 0010: df 95 c7 c8 e4 7f 98 b9 44 4f 57 4e 47 52 44 00 ........DOWNGRD.
SSL DBG [3] server hello, session id len.: %zu
SSL DBG [3] dumping 'server hello, session id' (32 bytes)
SSL DBG [3] 0000: 79 36 3f 83 e5 a0 e5 be 30 00 62 a4 72 53 a8 30 y6?.....0.b.rS.0
SSL DBG [3] 0010: 61 1b 42 a2 8d 48 a9 3f c1 a5 52 27 b4 f5 64 cd a.B..H.?..R'..d.
SSL DBG [3] no session has been resumed
SSL DBG [3] server hello, chosen ciphersuite: 002f
SSL DBG [3] server hello, compress alg.: 0
SSL DBG [3] server hello, chosen ciphersuite: TLS-RSA-WITH-AES-128-CBC-SHA
SSL DBG [2] server hello, total extension length: %zu
SSL DBG [3] found renegotiation extension
SSL DBG [3] unknown extension found: 0 (ignoring)
SSL DBG [2] <= parse server hello
SSL DBG [2] client state: 3
SSL DBG [2] => flush output
SSL DBG [2] <= flush output
SSL DBG [2] => parse certificate
SSL DBG [2] => read record
SSL DBG [2] => fetch input
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] ssl->f_recv(_timeout)() returned 5 (-0xfffffffb)
SSL DBG [2] <= fetch input
SSL DBG [4] dumping 'input record header' (5 bytes)
SSL DBG [4] 0000: 16 03 02 0a 16 .....
SSL DBG [3] input record: msgtype = 22, version = [3:2], msglen = %zu
SSL DBG [2] => fetch input
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] in_left: %zu, nb_want: %zu
SSL DBG [2] ssl->f_recv(_timeout)() returned 2582 (-0xfffff5ea)
SSL DBG [2] <= fetch input
SSL DBG [4] dumping 'input record from network' (2587 bytes)
SSL DBG [4] 0000: 16 03 02 0a 16 0b 00 0a 12 00 0a 0f 00 04 ff 30 ...............0
SSL DBG [4] 0010: 82 04 fb 30 82 03 e3 a0 03 02 01 02 02 12 04 95 ...0............
SSL DBG [4] 0020: 84 2c 95 9d 54 17 0a da a7 aa bb 26 15 0e 87 82 .,..T......&....
SSL DBG [4] 0030: 30 0d 06 0