Post

openssl utils

能找到的资料大多都是基于socketio去做ssl操作的,typical的记录一下,如果我们已经有了TCP的链路抽象,应该怎么在这个层次之上,实现一个简单的tls client的流程,server的相反就可以了

总结的来讲,ssl加解密的工作和io是分开的,这样做的好处是bio(basic io)能抽象为各类的io,比如socket io,file io。你也可以定义自己的io,只需要预定义怎么读写就行。

如果比较通俗的讲ssl和basic io之间的关系(拿mem io举例),他们可能如下图所示:

1
2
3
4
5
6
7
8
9
10
11
  +------+                                    +-----+
  |......|--> read(fd) --> BIO_write(rbio) -->|.....|--> SSL_read(ssl)  --> IN
  |......|                                    |.....|
  |.sock.|                                    |.SSL.|
  |......|                                    |.....|
  |......|<-- write(fd) <-- BIO_read(wbio) <--|.....|<-- SSL_write(ssl) <-- OUT
  +------+                                    +-----+

          |                                  |       |                     |
          |<-------------------------------->|       |<------------------->|
          |         encrypted bytes          |       |  unencrypted bytes  |

socket io的处理其实就可以不考虑读写memory,此时会直接发送掉

针对读事件,数据先来到了ssl下层的basic io中,如果你使用memory io的话,你需要负责把这部分数据写入rbio中,然后才能call ssl的高阶函数,SSL_read读到解密之后的数据,然后丢给application层做处理

同理,针对一个写事件,application先通过SSL_write,加密的数据在此时会写往writeio(wbio), 同理,用户需要自己讲wbio的数据读出来,这一部分的数据已经在ssl内部做了加密(record),然后写入具体的连接中。

一些概念

1. ssl握手

如果你不用ssl的一些函数帮你处理链接,你可以简单的直接初始化ssl library,另外从接口描述上看,这个是线程安全的接口

1
2
3
4
5
6
7
#define OPENSSL_INIT_NO_LOAD_SSL_STRINGS 0
#define OPENSSL_INIT_LOAD_SSL_STRINGS 0
#define OPENSSL_INIT_SSL_DEFAULT 0

// OPENSSL_init_ssl calls |CRYPTO_library_init| and returns one.
OPENSSL_EXPORT int OPENSSL_init_ssl(uint64_t opts,
                                    const OPENSSL_INIT_SETTINGS *settings);

1.1 SSL_CTX

1.1.1 创建
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SSL contexts.
//
// |SSL_CTX| objects manage shared state and configuration between multiple TLS
// or DTLS connections. Whether the connections are TLS or DTLS is selected by
// an |SSL_METHOD| on creation.
//
// |SSL_CTX| are reference-counted and may be shared by connections across
// multiple threads. Once shared, functions which change the |SSL_CTX|'s
// configuration may not be used.


// SSL_CTX_new returns a newly-allocated |SSL_CTX| with default settings or NULL
// on error.
OPENSSL_EXPORT SSL_CTX *SSL_CTX_new(const SSL_METHOD *method);

// SSL_CTX_up_ref increments the reference count of |ctx|. It returns one.
OPENSSL_EXPORT int SSL_CTX_up_ref(SSL_CTX *ctx);

// SSL_CTX_free releases memory associated with |ctx|.
OPENSSL_EXPORT void SSL_CTX_free(SSL_CTX *ctx);

简要来讲,SSL_CTX是加密上下文,它包含了一些加密的配置,比如加密算法,证书,私钥等等。它是一个全局的对象,可以被多个连接共享。

另外常用的一些设置SSL_CTX的函数都长的非常类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SSL_CTX_set_verify configures certificate verification behavior. |mode| is
// one of the |SSL_VERIFY_*| values defined above. |callback|, if not NULL, is
// used to customize certificate verification. See the behavior of
// |X509_STORE_CTX_set_verify_cb|.
//
// The callback may use |SSL_get_ex_data_X509_STORE_CTX_idx| with
// |X509_STORE_CTX_get_ex_data| to look up the |SSL| from |store_ctx|.
OPENSSL_EXPORT void SSL_CTX_set_verify(
    SSL_CTX *ctx, int mode, int (*callback)(int ok, X509_STORE_CTX *store_ctx));

// SSL_CTX_set_cert_store sets |ctx|'s certificate store to |store|. It takes
// ownership of |store|. The store is used for certificate verification.
//
// The store is also used for the auto-chaining feature, but this is deprecated.
// See also |SSL_MODE_NO_AUTO_CHAIN|.
OPENSSL_EXPORT void SSL_CTX_set_cert_store(SSL_CTX *ctx, X509_STORE *store);

// SSL_CTX_get_cert_store returns |ctx|'s certificate store.
OPENSSL_EXPORT X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *ctx);

比如,你可以设置ssl ctx的握手协议版本, 约定能使用的tls版本范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SSL_CTX_set_min_proto_version sets the minimum protocol version for |ctx| to
// |version|. If |version| is zero, the default minimum version is used. It
// returns one on success and zero if |version| is invalid.
OPENSSL_EXPORT int SSL_CTX_set_min_proto_version(SSL_CTX *ctx,
                                                 uint16_t version);

// SSL_CTX_set_max_proto_version sets the maximum protocol version for |ctx| to
// |version|. If |version| is zero, the default maximum version is used. It
// returns one on success and zero if |version| is invalid.
OPENSSL_EXPORT int SSL_CTX_set_max_proto_version(SSL_CTX *ctx,
                                                 uint16_t version);

// SSL_CTX_get_min_proto_version returns the minimum protocol version for |ctx|
OPENSSL_EXPORT uint16_t SSL_CTX_get_min_proto_version(const SSL_CTX *ctx);

// SSL_CTX_get_max_proto_version returns the maximum protocol version for |ctx|
OPENSSL_EXPORT uint16_t SSL_CTX_get_max_proto_version(const SSL_CTX *ctx);
1.1.2 验证策略

比如,你在创建ctx之后,自己设置一下证书的验证策略, 在callback里考虑要取消掉哪些错误

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Certificate verification.
//
// SSL may authenticate either endpoint with an X.509 certificate. Typically
// this is used to authenticate the server to the client. These functions
// configure certificate verification.
//
// WARNING: By default, certificate verification errors on a client are not
// fatal. See |SSL_VERIFY_NONE| This may be configured with
// |SSL_CTX_set_verify|.
//
// By default clients are anonymous but a server may request a certificate from
// the client by setting |SSL_VERIFY_PEER|.
//
// Many of these functions use OpenSSL's legacy X.509 stack which is
// underdocumented and deprecated, but the replacement isn't ready yet. For
// now, consumers may use the existing stack or bypass it by performing
// certificate verification externally. This may be done with
// |SSL_CTX_set_cert_verify_callback| or by extracting the chain with
// |SSL_get_peer_cert_chain| after the handshake. In the future, functions will
// be added to use the SSL stack without dependency on any part of the legacy
// X.509 and ASN.1 stack.
//
// To augment certificate verification, a client may also enable OCSP stapling
// (RFC 6066) and Certificate Transparency (RFC 6962) extensions.

// SSL_VERIFY_NONE, on a client, verifies the server certificate but does not
// make errors fatal. The result may be checked with |SSL_get_verify_result|. On
// a server it does not request a client certificate. This is the default.
#define SSL_VERIFY_NONE 0x00

// SSL_VERIFY_PEER, on a client, makes server certificate errors fatal. On a
// server it requests a client certificate and makes errors fatal. However,
// anonymous clients are still allowed. See
// |SSL_VERIFY_FAIL_IF_NO_PEER_CERT|.
#define SSL_VERIFY_PEER 0x01

// SSL_VERIFY_FAIL_IF_NO_PEER_CERT configures a server to reject connections if
// the client declines to send a certificate. This flag must be used together
// with |SSL_VERIFY_PEER|, otherwise it won't work.
#define SSL_VERIFY_FAIL_IF_NO_PEER_CERT 0x02

// SSL_VERIFY_PEER_IF_NO_OBC configures a server to request a client certificate
// if and only if Channel ID is not negotiated.
#define SSL_VERIFY_PEER_IF_NO_OBC 0x04

1.1.3 添加证书

另外,还可以自定义加入一下root ca加到ctx里, 拿一下X509_STORE,然后往里add证书

1
2
3
4
5
6
7
8
9
// SSL_CTX_set_cert_store sets |ctx|'s certificate store to |store|. It takes
// ownership of |store|. The store is used for certificate verification.
//
// The store is also used for the auto-chaining feature, but this is deprecated.
// See also |SSL_MODE_NO_AUTO_CHAIN|.
OPENSSL_EXPORT void SSL_CTX_set_cert_store(SSL_CTX *ctx, X509_STORE *store);

// SSL_CTX_get_cert_store returns |ctx|'s certificate store.
OPENSSL_EXPORT X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *ctx);

具体你可能需要一下证书的储存标志,用于控制证书的验证过程。

1
int X509_STORE_set_flags(X509_STORE *stor, unsigned long flags);

当然,非移动端,可以load ca证书

1
2
3
// SSL_CTX_set_default_verify_paths loads the OpenSSL system-default trust
// anchors into |ctx|'s store. It returns one on success and zero on failure.
OPENSSL_EXPORT int SSL_CTX_set_default_verify_paths(SSL_CTX *ctx);
1.1.4 设置自定义的加密套件

ctx还能定义能使用的加密套件,加密套件字符串使用”, “做间隔的

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
 // Cipher suite configuration.
//
// OpenSSL uses a mini-language to configure cipher suites. The language
// maintains an ordered list of enabled ciphers, along with an ordered list of
// disabled but available ciphers. Initially, all ciphers are disabled with a
// default ordering. The cipher string is then interpreted as a sequence of
// directives, separated by colons, each of which modifies this state.
//
// Most directives consist of a one character or empty opcode followed by a
// selector which matches a subset of available ciphers.
//
// Available opcodes are:
//
//   The empty opcode enables and appends all matching disabled ciphers to the
//   end of the enabled list. The newly appended ciphers are ordered relative to
//   each other matching their order in the disabled list.
//
//   |-| disables all matching enabled ciphers and prepends them to the disabled
//   list, with relative order from the enabled list preserved. This means the
//   most recently disabled ciphers get highest preference relative to other
//   disabled ciphers if re-enabled.
//
//   |+| moves all matching enabled ciphers to the end of the enabled list, with
//   relative order preserved.
//
//   |!| deletes all matching ciphers, enabled or not, from either list. Deleted
//   ciphers will not matched by future operations.
//
// A selector may be a specific cipher (using either the standard or OpenSSL
// name for the cipher) or one or more rules separated by |+|. The final
// selector matches the intersection of each rule. For instance, |AESGCM+aECDSA|
// matches ECDSA-authenticated AES-GCM ciphers.
//
// Available cipher rules are:
//
//   |ALL| matches all ciphers.
//
//   |kRSA|, |kDHE|, |kECDHE|, and |kPSK| match ciphers using plain RSA, DHE,
//   ECDHE, and plain PSK key exchanges, respectively. Note that ECDHE_PSK is
//   matched by |kECDHE| and not |kPSK|.
//
//   |aRSA|, |aECDSA|, and |aPSK| match ciphers authenticated by RSA, ECDSA, and
//   a pre-shared key, respectively.
//
//   |RSA|, |DHE|, |ECDHE|, |PSK|, |ECDSA|, and |PSK| are aliases for the
//   corresponding |k*| or |a*| cipher rule. |RSA| is an alias for |kRSA|, not
//   |aRSA|.
//
//   |3DES|, |AES128|, |AES256|, |AES|, |AESGCM|, |CHACHA20| match ciphers
//   whose bulk cipher use the corresponding encryption scheme. Note that
//   |AES|, |AES128|, and |AES256| match both CBC and GCM ciphers.
//
//   |SHA1|, and its alias |SHA|, match legacy cipher suites using HMAC-SHA1.
//
// Although implemented, authentication-only ciphers match no rules and must be
// explicitly selected by name.
//
// Deprecated cipher rules:
//
//   |kEDH|, |EDH|, |kEECDH|, and |EECDH| are legacy aliases for |kDHE|, |DHE|,
//   |kECDHE|, and |ECDHE|, respectively.
//
//   |HIGH| is an alias for |ALL|.
//
//   |FIPS| is an alias for |HIGH|.
//
//   |SSLv3| and |TLSv1| match ciphers available in TLS 1.1 or earlier.
//   |TLSv1_2| matches ciphers new in TLS 1.2. This is confusing and should not
//   be used.
//
// Unknown rules are silently ignored by legacy APIs, and rejected by APIs with
// "strict" in the name, which should be preferred. Cipher lists can be long
// and it's easy to commit typos. Strict functions will also reject the use of
// spaces, semi-colons and commas as alternative separators.
//
// The special |@STRENGTH| directive will sort all enabled ciphers by strength.
//
// The |DEFAULT| directive, when appearing at the front of the string, expands
// to the default ordering of available ciphers.
//
// If configuring a server, one may also configure equal-preference groups to
// partially respect the client's preferences when
// |SSL_OP_CIPHER_SERVER_PREFERENCE| is enabled. Ciphers in an equal-preference
// group have equal priority and use the client order. This may be used to
// enforce that AEADs are preferred but select AES-GCM vs. ChaCha20-Poly1305
// based on client preferences. An equal-preference is specified with square
// brackets, combining multiple selectors separated by |. For example:
//
//   [TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256|TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256]
//
// Once an equal-preference group is used, future directives must be
// opcode-less. Inside an equal-preference group, spaces are not allowed.
//
// TLS 1.3 ciphers do not participate in this mechanism and instead have a
// built-in preference order. Functions to set cipher lists do not affect TLS
// 1.3, and functions to query the cipher list do not include TLS 1.3
// ciphers.

// SSL_DEFAULT_CIPHER_LIST is the default cipher suite configuration. It is
// substituted when a cipher string starts with 'DEFAULT'.
#define SSL_DEFAULT_CIPHER_LIST "ALL"

// SSL_CTX_set_strict_cipher_list configures the cipher list for |ctx|,
// evaluating |str| as a cipher string and returning error if |str| contains
// anything meaningless. It returns one on success and zero on failure.
OPENSSL_EXPORT int SSL_CTX_set_strict_cipher_list(SSL_CTX *ctx,
                                                  const char *str);

// SSL_CTX_set_cipher_list configures the cipher list for |ctx|, evaluating
// |str| as a cipher string. It returns one on success and zero on failure.
//
// Prefer to use |SSL_CTX_set_strict_cipher_list|. This function tolerates
// garbage inputs, unless an empty cipher list results.
OPENSSL_EXPORT int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str);

// SSL_set_strict_cipher_list configures the cipher list for |ssl|, evaluating
// |str| as a cipher string and returning error if |str| contains anything
// meaningless. It returns one on success and zero on failure.
OPENSSL_EXPORT int SSL_set_strict_cipher_list(SSL *ssl, const char *str);

1.2 ssl

ssl是一个具体的ssl链接的抽象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// SSL connections.
//
// An |SSL| object represents a single TLS or DTLS connection. Although the
// shared |SSL_CTX| is thread-safe, an |SSL| is not thread-safe and may only be
// used on one thread at a time.

// SSL_new returns a newly-allocated |SSL| using |ctx| or NULL on error. The new
// connection inherits settings from |ctx| at the time of creation. Settings may
// also be individually configured on the connection.
//
// On creation, an |SSL| is not configured to be either a client or server. Call
// |SSL_set_connect_state| or |SSL_set_accept_state| to set this.
OPENSSL_EXPORT SSL *SSL_new(SSL_CTX *ctx);

// SSL_free releases memory associated with |ssl|.
OPENSSL_EXPORT void SSL_free(SSL *ssl);

首先确定他是干什么用的, client还是server

1
2
3
4
5
// SSL_set_connect_state configures |ssl| to be a client.
OPENSSL_EXPORT void SSL_set_connect_state(SSL *ssl);

// SSL_set_accept_state configures |ssl| to be a server.
OPENSSL_EXPORT void SSL_set_accept_state(SSL *ssl);

他能定制的内容非常多,上面写的很大一部分关于ctx的,你都可以找找ssl层面有没有相关的函数

比如,协议版本,这个就可以设置

1
2
3
4
5
6
7
8
9
// SSL_set_min_proto_version sets the minimum protocol version for |ssl| to
// |version|. If |version| is zero, the default minimum version is used. It
// returns one on success and zero if |version| is invalid.
OPENSSL_EXPORT int SSL_set_min_proto_version(SSL *ssl, uint16_t version);

// SSL_set_max_proto_version sets the maximum protocol version for |ssl| to
// |version|. If |version| is zero, the default maximum version is used. It
// returns one on success and zero if |version| is invalid.
OPENSSL_EXPORT int SSL_set_max_proto_version(SSL *ssl, uint16_t version);
1.2.1 设置验证的hostname,verify name

首先区分sni和verify name,sni是带在tls握手里的,verify name是在握手后,验证证书的时候用的

1
2
3
// SSL_set_tlsext_host_name, for a client, configures |ssl| to advertise |name|
// in the server_name extension. It returns one on success and zero on error.
OPENSSL_EXPORT int SSL_set_tlsext_host_name(SSL *ssl, const char *name);

verify的cn可以设置很多个,比如你应该先拿到ssl的X509_VERIFY_PARAM,然后对这个做设置

1
2
3
4
5
6
7
8
9
10
11
// SSL_get0_param returns |ssl|'s |X509_VERIFY_PARAM| for certificate
// verification. The caller must not release the returned pointer but may call
// functions on it to configure it.
OPENSSL_EXPORT X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl);

OPENSSL_EXPORT int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param,
                                               const char *name,
                                               size_t namelen);
OPENSSL_EXPORT int X509_VERIFY_PARAM_add1_host(X509_VERIFY_PARAM *param,
                                               const char *name,
                                               size_t namelen);
1.2.2 设置证书

可以通过一段代码来具体看怎么利用bio和d2i相关的函数来这是ssl connection的证书 比如代码可以这样写

1
2
3
4
5
6
7
8
9
10
11
12
void AddCertToStore(const char* ca_buffer, size_t lenth) {
  auto bio = BIO_new_mem_buf((void*)(ca_buffer), length);
  auto cert = PEM_read_bio_X509(bio, nullptr, nullptr, nullptr);
  X509_STORE_add_cert(verify_store, cert);
  X509_free(cert);
  BIO_free(bio);
}
X509_STORE* verify_store = X509_STORE_new();

AddCertToStore(ca_buffer, length);

SSL_set0_verify_cert_store(ssl, store);

主要看怎么设置证书的container

1
2
3
4
// SSL_set0_verify_cert_store sets an |X509_STORE| that will be used
// exclusively for certificate verification and returns one. Ownership of
// |store| is transferred to the |SSL|.
OPENSSL_EXPORT int SSL_set0_verify_cert_store(SSL *ssl, X509_STORE *store);

1.3 ssl握手

1.3.1 收数据

正确设置完ctx和ssl之后,就可以开始握手了,一般从tcp的onconnect开始,ondata的时候开始receive data开始握手

在这之前,你依然需要设置一下ssl的读写数据io

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SSL_set_bio configures |ssl| to read from |rbio| and write to |wbio|. |ssl|
// takes ownership of the two |BIO|s. If |rbio| and |wbio| are the same, |ssl|
// only takes ownership of one reference.
//
// In DTLS, |rbio| must be non-blocking to properly handle timeouts and
// retransmits.
//
// If |rbio| is the same as the currently configured |BIO| for reading, that
// side is left untouched and is not freed.
//
// If |wbio| is the same as the currently configured |BIO| for writing AND |ssl|
// is not currently configured to read from and write to the same |BIO|, that
// side is left untouched and is not freed. This asymmetry is present for
// historical reasons.
//
// Due to the very complex historical behavior of this function, calling this
// function if |ssl| already has |BIO|s configured is deprecated. Prefer
// |SSL_set0_rbio| and |SSL_set0_wbio| instead.
OPENSSL_EXPORT void SSL_set_bio(SSL *ssl, BIO *rbio, BIO *wbio);

另外开启bio有一套自己的函数

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// Memory BIOs.
//
// Memory BIOs can be used as a read-only source (with |BIO_new_mem_buf|) or a
// writable sink (with |BIO_new|, |BIO_s_mem| and |BIO_mem_contents|). Data
// written to a writable, memory BIO can be recalled by reading from it.
//
// Calling |BIO_reset| on a read-only BIO resets it to the original contents.
// On a writable BIO, it clears any data.
//
// If the close flag is set to |BIO_NOCLOSE| (not the default) then the
// underlying |BUF_MEM| will not be freed when the |BIO| is freed.
//
// Memory BIOs support |BIO_gets| and |BIO_puts|.
//
// |BIO_ctrl_pending| returns the number of bytes currently stored.

// BIO_NOCLOSE and |BIO_CLOSE| can be used as symbolic arguments when a "close
// flag" is passed to a BIO function.
#define BIO_NOCLOSE 0
#define BIO_CLOSE 1

// BIO_s_mem returns a |BIO_METHOD| that uses a in-memory buffer.
OPENSSL_EXPORT const BIO_METHOD *BIO_s_mem(void);


// BIO abstracts over a file-descriptor like interface.


// Allocation and freeing.

DEFINE_STACK_OF(BIO)

// BIO_new creates a new BIO with the given method and a reference count of one.
// It returns the fresh |BIO|, or NULL on error.
OPENSSL_EXPORT BIO *BIO_new(const BIO_METHOD *method);

// BIO_free decrements the reference count of |bio|. If the reference count
// drops to zero, it calls the destroy callback, if present, on the method and
// frees |bio| itself. It then repeats that for the next BIO in the chain, if
// any.
//
// It returns one on success or zero otherwise.
OPENSSL_EXPORT int BIO_free(BIO *bio);

随后在on read event的时候尝试下handshake,这个是个持续的操作,直到握手完成(可以call很多次。。。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// SSL_do_handshake continues the current handshake. If there is none or the
// handshake has completed or False Started, it returns one. Otherwise, it
// returns <= 0. The caller should pass the value into |SSL_get_error| to
// determine how to proceed.
//
// In DTLS, the caller must drive retransmissions. Whenever |SSL_get_error|
// signals |SSL_ERROR_WANT_READ|, use |DTLSv1_get_timeout| to determine the
// current timeout. If it expires before the next retry, call
// |DTLSv1_handle_timeout|. Note that DTLS handshake retransmissions use fresh
// sequence numbers, so it is not sufficient to replay packets at the transport.
//
// TODO(davidben): Ensure 0 is only returned on transport EOF.
// https://crbug.com/466303.
OPENSSL_EXPORT int SSL_do_handshake(SSL *ssl);

随后需要做的就是在有数据来的时候做读操作, 先读到read bio里

1
2
3
// BIO_write writes |len| bytes from |data| to |bio|. It returns the number of
// bytes written or a negative number on error.
OPENSSL_EXPORT int BIO_write(BIO *bio, const void *data, int len);

如果是开始ssl握手了的话, 你应该call SSL_do_handshake继续,需要处理两个error,一个是SSL_ERROR_WANT_READ,一个是SSL_ERROR_WANT_WRITE,如果是这两个错误,ssl其实想读写io,需要你对应的做一下io操作,回调到上层,然后下次ondata的时候继续shake hand,这个时候一般SSL_do_handshake的返回值是0.

比如有的还要继续read或者write,你就得等等,如果需要写入更多数据,你就应该根据ssl的要求,从write bio里pop数据,然后发到server,继续handshake。

比如代码可以这样写

1
2
3
4
5
6
7
int res = SSL_do_handshake(ssl);
if (res != 1) {
  auto err = SSL_get_error(ssl, r);
  if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
    // continue handshake
  }
}

如果失败了,看一下证书相关的原因

1
2
3
// SSL_get_verify_result returns the result of certificate verification. It is
// either |X509_V_OK| or a |X509_V_ERR_*| value.
OPENSSL_EXPORT long SSL_get_verify_result(const SSL *ssl);

如果此时前面的操作已经完全完成了,现在应该进入到ssl读的阶段

1
2
3
4
5
6
7
8
// SSL_read reads up to |num| bytes from |ssl| into |buf|. It implicitly runs
// any pending handshakes, including renegotiations when enabled. On success, it
// returns the number of bytes read. Otherwise, it returns <= 0. The caller
// should pass the value into |SSL_get_error| to determine how to proceed.
//
// TODO(davidben): Ensure 0 is only returned on transport EOF.
// https://crbug.com/466303.
OPENSSL_EXPORT int SSL_read(SSL *ssl, void *buf, int num);

读大于0,触发ondata的事件丢给上层

有一个函数比较拥有,可以看看bio到底有没有就绪io

1
2
// BIO_pending returns the number of bytes pending to be read.
OPENSSL_EXPORT size_t BIO_pending(const BIO *bio);
1.3.2 发数据

发数据就比较随意,先ssl write

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
// SSL_write writes up to |num| bytes from |buf| into |ssl|. It implicitly runs
// any pending handshakes, including renegotiations when enabled. On success, it
// returns the number of bytes written. Otherwise, it returns <= 0. The caller
// should pass the value into |SSL_get_error| to determine how to proceed.
//
// In TLS, a non-blocking |SSL_write| differs from non-blocking |write| in that
// a failed |SSL_write| still commits to the data passed in. When retrying, the
// caller must supply the original write buffer (or a larger one containing the
// original as a prefix). By default, retries will fail if they also do not
// reuse the same |buf| pointer. This may be relaxed with
// |SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER|, but the buffer contents still must be
// unchanged.
//
// By default, in TLS, |SSL_write| will not return success until all |num| bytes
// are written. This may be relaxed with |SSL_MODE_ENABLE_PARTIAL_WRITE|. It
// allows |SSL_write| to complete with a partial result when only part of the
// input was written in a single record.
//
// In DTLS, neither |SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER| and
// |SSL_MODE_ENABLE_PARTIAL_WRITE| do anything. The caller may retry with a
// different buffer freely. A single call to |SSL_write| only ever writes a
// single record in a single packet, so |num| must be at most
// |SSL3_RT_MAX_PLAIN_LENGTH|.
//
// TODO(davidben): Ensure 0 is only returned on transport EOF.
// https://crbug.com/466303.
OPENSSL_EXPORT int SSL_write(SSL *ssl, const void *buf, int num);

然后BIO read出write io的东西,发出去就行了

1.3.3 关闭连接

关于关闭连接,其实有几个相关的接口可以call, 最相关的是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// SSL_shutdown shuts down |ssl|. It runs in two stages. First, it sends
// close_notify and returns zero or one on success or -1 on failure. Zero
// indicates that close_notify was sent, but not received, and one additionally
// indicates that the peer's close_notify had already been received.
//
// To then wait for the peer's close_notify, run |SSL_shutdown| to completion a
// second time. This returns 1 on success and -1 on failure. Application data
// is considered a fatal error at this point. To process or discard it, read
// until close_notify with |SSL_read| instead.
//
// In both cases, on failure, pass the return value into |SSL_get_error| to
// determine how to proceed.
//
// Most callers should stop at the first stage. Reading for close_notify is
// primarily used for uncommon protocols where the underlying transport is
// reused after TLS completes. Additionally, DTLS uses an unordered transport
// and is unordered, so the second stage is a no-op in DTLS.
OPENSSL_EXPORT int SSL_shutdown(SSL *ssl);

需要注意的是,shutdown针对的是已经初始化好了的ssl session,所以不要对还在握手或者初始化的tls session做操作

你可以判断一下,用

1
2
3
// SSL_in_init returns one if |ssl| has a pending handshake and zero
// otherwise.
OPENSSL_EXPORT int SSL_in_init(const SSL *ssl);

但是你也可以控制怎么shutdown,比如有几个函数可以控制你发送的具体细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// SSL_CTX_set_quiet_shutdown sets quiet shutdown on |ctx| to |mode|. If
// enabled, |SSL_shutdown| will not send a close_notify alert or wait for one
// from the peer. It will instead synchronously return one.
OPENSSL_EXPORT void SSL_CTX_set_quiet_shutdown(SSL_CTX *ctx, int mode);

// SSL_CTX_get_quiet_shutdown returns whether quiet shutdown is enabled for
// |ctx|.
OPENSSL_EXPORT int SSL_CTX_get_quiet_shutdown(const SSL_CTX *ctx);

// SSL_set_quiet_shutdown sets quiet shutdown on |ssl| to |mode|. If enabled,
// |SSL_shutdown| will not send a close_notify alert or wait for one from the
// peer. It will instead synchronously return one.
OPENSSL_EXPORT void SSL_set_quiet_shutdown(SSL *ssl, int mode);

// SSL_get_quiet_shutdown returns whether quiet shutdown is enabled for
// |ssl|.
OPENSSL_EXPORT int SSL_get_quiet_shutdown(const SSL *ssl);

这些不会导致connection的close,只是在tls层面shutdown,具体怎么设置mode

1
2
3
4
5
6
7
#define SSL_SENT_SHUTDOWN 1
#define SSL_RECEIVED_SHUTDOWN 2

// SSL_get_shutdown returns a bitmask with a subset of |SSL_SENT_SHUTDOWN| and
// |SSL_RECEIVED_SHUTDOWN| to query whether close_notify was sent or received,
// respectively.
OPENSSL_EXPORT int SSL_get_shutdown(const SSL *ssl);

安全的shutdown之后可以call相关的free函数

1
2
3
4
// SSL_free releases memory associated with |ssl|.
OPENSSL_EXPORT void SSL_free(SSL *ssl);
// SSL_CTX_free releases memory associated with |ctx|.
OPENSSL_EXPORT void SSL_CTX_free(SSL_CTX *ctx);

另外记得看一下接口有没有所有权转移的,比如bio的,X509_STORE,这种就不要重复free了。

REF

  1. What are OpenSSL BIOs?
  2. handle SSL_shutdown
  3. tls1.3的介绍
This post is licensed under CC BY 4.0 by the author.

Trending Tags