0%

ALPN相关

1. 为什么需要ALPN

  TLS只负责建立加密通道,不负责上层到底是什么应用,所以如果用户想在1个地址上支持多种应用协议,比如1个443端口,既想支持HTTP/1.1,还能支持HTTP/2、SPDY/1,没有ALPN(App-Layer Protocol Negotiation)的话,用户需要在ssl建立连接之后,再协商是用哪个协议,然后分发到各个协议的处理流程中,这样多了一个来回。ALPN把应用层协商附带到握手协商中,让用户在握手建立之后就立即知道使用的应用协议,这样节省了一个来回。

2. TLS中的具体实现

  主要见 RFC 7301

  ALPN作为扩展项存在ClientHello和ServerHello中,ALPN格式为:

1
2
3
4
5
6
7
8
9
10
11
12
enum {
application_layer_protocol_negotiation(16), (65535)
} ExtensionType;

The "extension_data" field of the ("application_layer_protocol_negotiation(16)") extension SHALL
contain a "ProtocolNameList" value.

opaque ProtocolName<1..2^8-1>;

struct {
ProtocolName protocol_name_list<2..2^16-1>
} ProtocolNameList;

  协商过程:

  1. client在ClientHello中设置期望的协议列表,优先支持的放在前边。

  2. server设置支持的应用协议列表,优先支持的放到前边。收到ClientHello后,应用选择支持的协议。

  3. server在ServerHello中返回支持的1个应用协议,此后的应用层就使用该协议。

  4. client收到ServerHello中的应用协议,此后的应用层就使用该协议。

  5. 如果server没有支持的协议,会返回握手失败的警告。

  需要注意的是ALPN协商每次都在握手的时候进行,不会保存到session中,所以即使会话恢复,也会重新协商。会话恢复是TLS层的东西,不是应用层的,不管是哪个协议的应用,都可以使用TLS恢复的会话。

3. openssl中的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// client或者server设置支持的应用协议,格式是带1个字节长度前缀的协议,类似
// "\x08HTTP/1.1" 或者 "\x08HTTP/1.1\x06SPDY/1"
int SSL_CTX_set_alpn_protos(SSL_CTX *ctx, const unsigned char *protos,
unsigned int protos_len);
int SSL_set_alpn_protos(SSL *ssl, const unsigned char *protos,
unsigned int protos_len);

// 设置协商回调,server在收到ALPN时会调用该回调,让用户去进行协商和一些其他操作
void SSL_CTX_set_alpn_select_cb(SSL_CTX *ctx,
int (*cb) (SSL *ssl,
const unsigned char **out,
unsigned char *outlen,
const unsigned char *in,
unsigned int inlen,
void *arg), void *arg);

// openssl提供的标准的协商过程,应用可以在握手的时候就知道准备用什么协议了。
// 该函数应该在SSL_CTX_set_alpn_select_cb()设置的回调中使用
int SSL_select_next_proto(unsigned char **out, unsigned char *outlen,
const unsigned char *server,
unsigned int server_len,
const unsigned char *client,
unsigned int client_len)


// 取出已经协商好的应用协议
void SSL_get0_alpn_selected(const SSL *ssl, const unsigned char **data,
unsigned int *len);

参考

  1. RFC 7301