Classic Macintosh OS 7/8/9 上的 Mbed-TLS 移植

bbenchoff/MacSSL

main 分支标签 转到文件 代码

文件夹和文件

名称| 名称| 最后提交消息| 最后提交日期
---|---|---|---

最新提交

历史记录

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 下编译。以下是证明: Proof of pulling an API request down 这是一个基本的应用程序,可以对 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 协议。

目前,此库的最低配置支持以下内容:

密码套件

椭圆曲线

签名算法

证书处理

所有这些都包装到对 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。过渡需要进行大量的代码修改:

最后一点——解决路径限制——是一个大问题。你知道你如何编写 #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_tuint64_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。我创建了一个自定义的熵收集系统,该系统从多个来源提取数据:

所有这些来源都组合并进行异或运算,形成一个足以进行加密操作的随机池。我不会完全称之为 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