Android Boring SSL
- 前期设置
- SSLContext.getInstance(“TLS”)
- SSLContext.init()
- SSLContext.getSocketFactory()
- SSLSocketFactory.createSocket()
- NativeSsl.newInstance()
- BioWrapper 的创建
- ConscryptEngineSocket.startHandshake()
- TLS协商
- state_start_connect(TLS)
- state_enter_early_data(TLS)
- state_read_server_hello(TLS)
- state_tls13(TLS)
- state_read_hello_retry_request(0, TLSv1.3 start)
- state_read_server_hello(2)
- state_read_encrypted_extensions(3)
- state_read_certificate_request(4)
- state_read_server_certificate(5)
- state_read_server_certificate_verify(6)
- state_read_server_finished(8)
- state_send_end_of_early_data(9)
- state_send_client_encrypted_extensions(10)
- state_send_client_certificate(11)
- state_send_client_certificate_verify(12)
- state_complete_second_flight(13)
- state_done(14, TLSv1.3 end)
- state_finish_client_handshake(TLS)
- 加密与解密
- SSLOutputStream.write() 时的加密
- SSLInputStream.read() 时的解密
前期设置
SSLContext.getInstance(“TLS”)
首先通过SSLContext.getInstance("TLS")获取SSLContext, 此时的SSLContext中的SSLContextSpi是OpenSSLContextImpl, 在OpenSSLContextImpl构造时, 构造了ClientSessionContext并记录在OpenSSLContextImpl.clientSessionContext. 而在ClientSessionContext构造时, 其父类AbstractSessionContext的构造函数中, 调用了底层的NativeCrypto.SSL_CTX_new()创建了SSL_CTX并记录在OpenSSLContextImpl.super.sslCtxNativePointer中(主要步骤):
-
调用
TLS_with_buffers_method()获取SSL_METHOD:static const SSL_METHOD kMethod = {0,&kTLSProtocolMethod,&ssl_noop_x509_method,}; -
通过
SSL_CTX_new()创建SSL_CTX并进行一系列设置, 其中,SSL_CTX.method设置为kMethod:-
SSL_CTX.method设置为kTLSProtocolMethod(类型为SSL_PROTOCOL_METHOD) -
SSL_CTX.x509_method设置为ssl_noop_x509_method(类型为SSL_X509_METHOD) -
其中:
kTLSProtocolMethod的定义:static const SSL_PROTOCOL_METHOD kTLSProtocolMethod = {false /* is_dtls */,tls_new, // 初始化 SSL, 创建 SSL_HANDSHAKEtls_free,tls_get_message, // PEEK 一个 SSLMessagetls_next_message, // 读取一个 SSLMessagetls_has_unprocessed_handshake_data,tls_open_handshake,tls_open_change_cipher_spec,tls_open_app_data,tls_write_app_data, // 加密明文的 app 数据并写到输出tls_dispatch_alert,tls_init_message, // 初始化一个 SSLMessagetls_finish_message,// 销毁一个 SSLMessagetls_add_message, // 将 SSLMessage 添加到输出tls_add_change_cipher_spec,tls_flush_flight, // 冲刷未发送的数据到 BIOtls_on_handshake_complete,tls_set_read_state, // 设置 SSL.s3->aead_write_ctxtls_set_write_state, // 设置 SSL.s3->aead_read_ctx };该数据结构设计的方法将会经常出现在后文当中
-
-
通过
SSL_CTX_set_info_callback()设置监听SSL连接状态对应的回调函数为:info_callback, 其调用的上层接口为:ConscryptEngine.onSSLStateChange() -
通过
SSL_CTX_set_cert_cb()设置证书回调为:cert_cb, 其调用的上层接口为:ConscryptEngine.clientCertificateRequested() -> NativeSsl.chooseClientCertificate()
SSLContext.init()
上层调用SSLContext.init() -> OpenSSLContextImpl.engineInit(), 此时创建了SSLParametersImpl并记录了上层传递的X509KeyManager和X509TrustManager.
创建SSLParametersImpl时, 其clientSessionContext成员(类型是ClientSessionContext)
SSLContext.getSocketFactory()
上层调用SSLContext.getSocketFactory()获取SSLSocketFactory(实际是OpenSSLSocketFactoryImpl), OpenSSLSocketFactoryImpl保存了SSLParametersImpl
SSLSocketFactory.createSocket()
上层调用SSLSocketFactory(实际是OpenSSLSocketFactoryImpl)的createSocket()方法, 经过Platform.createEngineSocket()创建Java8EngineSocket(其父类是ConscryptEngineSocket), 其父类的newEngine方法创建ConscryptEngine, 此时将SSLParametersImpl克隆一份, 并传入. ConscryptEngine在构造时, 将通过newSsl() -> NativeSsl.newInstance()实例化NativeSsl, SSLParametersImpl将保存在NativeSsl.parameters, 其基本引用关系:
ConscryptEngineSocket.engine: Java8EngineSocket
ConscryptEngineSocket.engine.ssl: NativeSsl
ConscryptEngineSocket.engine.ssl.parameters: SSLParametersImpl
NativeSsl.newInstance()
- 通过
parameters.getSessionContext()获取了ClientSessionContext结构体地址 - 调用
ClientSessionContext.newSsl(), 传入ClientSessionContext, 创建SSL结构体, 其底层调用为NativeCrypto.SSL_new(), 此处底层调用到:NativeCrypto_SSL_new():- 通过
to_SSL_CTX()还原指针ssl_ctx_address为:SSL_CTX - 将
SSL_CTX传入SSL_new()以创建SSL结构体 - 通过
SSL_set_custom_verify()设置SSL的回调函数为:cert_verify_callback(), 该方法负责完成对服务端证书的验证, 在cert_verify_callback()中, 回调上层的NativeCrypto.verifyCertificateChain()
- 通过
SSL结构体被传递给NativeCrypto.SSL_new(), 保存到NativeSsl.ssl, 从顶层向下:ConscryptEngineSocket.engine.ssl.ssl
BioWrapper 的创建
这里注意下 ConscryptEngine.networkBio(), 它的类型是BioWrapper, 是很重要的角色, 它负责处理所有 “加密” 的数据, 通过NativeSsl.newBio()创建, 更底层的方法是:NativeCrypto.SSL_BIO_new():
- 通过
BIO_new_bio_pair()创建一对BIO:internal_bio和network_bio, 此时创建的BIO.mehtod被设置为methods_biop, 因此BIO.mehtod->bread被设置为bio_read, 而BIO.mehtod->bwrite被设置为:bio_write - 通过
SSL_set_bio()将internal_bio配置给了SSL, 保存在(SSL.wbio和SSL.rbio) - 将
network_bio引用在BioWrapper.bio
ConscryptEngine.networkBio.bio非常重要, 它将在后文经常被提到.
ConscryptEngineSocket.startHandshake()
上层调用ConscryptEngineSocket.startHandshake()开始执行MTLS协商:
- 创建
SSLInputStream并保存到ConscryptEngineSocket.in, 在此过程中- 通过
ByteBuffer.allocateDirect分配与NativeSsl交互的DirectByteBuffer, 此处的 buffer 对于 JNI 来说是没有拷贝成本的.DirectByteBuffer保存到ConscryptEngineSocket.in.fromEngine - 通过
ByteBuffer.allocate()分配与SocketInputStream进行通信的的HeapByteBuffer,HeapByteBuffer保存到ConscryptEngineSocket.in.fromSocket
- 通过
- 创建
SSLOutputStream并保存到ConscryptEngineSocket.out, 在此过程中:- 通过
ByteBuffer.allocate()分配与SocketOutputStream进行通信的的HeapByteBuffer,HeapByteBuffer保存到ConscryptEngineSocket.out.target
- 通过
- 调用
SSLInputStream.processDataFromSocket()获取数据, 调用readFromSocket()从SocketInputStream读取数据, 然后调用ConscryptEngine.unwrap()处理读取到的数据, 细节见解密小节
TLS协商
TLS协商的主循环:
NativeCrypto_ENGINE_SSL_read_direct() [libjavacrypto.so] ->::SSL_read() [libssl.so] ->ssl_read_impl() ->::SSL_do_handshake() ->bssl::ssl_run_handshake()
该函数是一个循环, 该循环依次调用SSL_HANDSHAKE.ssl->do_handshake方法, 对于客户端, 该方法为ssl_client_handshake(), 在该方法中, 初始状态为state_start_connect()
state_start_connect(TLS)
state_start_connect对应的执行函数为do_start_connect():
- 初始化
SSL.s3结构体 - 调用
ssl_setup_key_shares()初始化 keys_shares - 调用
ssl_setup_extension_permutation()初始化扩展 - 调用
ssl_encrypt_client_hello()加密 client_hello - 调用
ssl_add_client_hello()将 client_hello 附加到消息池(SSL.s3->write_buffer中) - 返回状态为:
ssl_hs_flush
如果该状态顺利执行结束, 则向上返回给ssl_run_handshake()的结果是ssl_hs_flush, 因此ssl_run_handshake()继续执行SSL_HANDSHAKE.ssl->method->flush_flight方法, 该方法为kTLSProtocolMethod.flush_flight即tls_flush_flight(), 进一步调用ssl_write_buffer_flush() -> tls_write_buffer_flush() -> BIO_write(), 将ssl.s3->write_buffer数据写入到SSL.wbio的BIO中, 如前文所述, 该BIO即internal_bio, 其关联的对象为network_bio, 至此上层就可以通过NativeCrypto.ENGINE_SSL_read_BIO_direct()读取到数 BoringSSL 需要发送的数据了.
state_enter_early_data(TLS)
执行do_enter_early_data(), 基本没有做太多事情, 判断SSL_HANDSHAKE.early_data_offered为空就直接切换状态到state_read_server_hello
state_read_server_hello(TLS)
执行 do_read_server_hello(), 并读取TLS数据, 调用ssl_parse_server_hello()解析协议头, 获取版本号, 本文的案例显然是MTLSv1.3, 则切换状态到 state_tls13
state_tls13(TLS)
执行do_tls13() -> tls13_client_handshake(), 该函数也是一个循环, 如果SSL_HANDSHAKE.tls13_state不为state_done就循环执行后续的状态切换, 初始状态为: state_read_hello_retry_request
state_read_hello_retry_request(0, TLSv1.3 start)
调用do_read_hello_retry_request():
-
调用
parse_server_hello_tls13()解析消息头 -
调用
SSL_get_cipher_by_value()获取服务器要求的算法:SSL_CIPHER, 本文的案例是TLS1_3_RFC_AES_128_GCM_SHA256, 协议 id 为 4865, 也就是:static constexpr SSL_CIPHER kCiphers[] = {// Cipher 1301{TLS1_3_RFC_AES_128_GCM_SHA256,"TLS_AES_128_GCM_SHA256",TLS1_3_CK_AES_128_GCM_SHA256,SSL_kGENERIC,SSL_aGENERIC,SSL_AES128GCM,SSL_AEAD,SSL_HANDSHAKE_MAC_SHA256,},... }; -
将
SSL_CIPHER保存到SSL_HANDSHAKE.new_cipher -
切换状态到
state_read_server_hello
state_read_server_hello(2)
调用do_read_server_hello(), 该函数做了很多工作:
- 复制并保存
server_random - 将
SSL_HANDSHAKE.new_cipher保存到SSK_HANDSHAKE.new_session->cipher - 调用
ssl_ext_key_share_parse_serverhello()从server_hello中解析出dhe_secret - 调用
tls13_advance_key_schedule()派生秘钥并保存到SSL_HANDSHAKE.secret_ - 调用
tls13_derive_handshake_secrets()派生秘钥并保存到SSL_HANDSHAKE.client_handshake_secret_ - 调用
tls13_set_traffic_key()将SSL_HANDSHAKE.client_handshake_secret_设置到SSL:- 调用
ssl_cipher_get_evp_aead()确定EVP_AEAD, 该结构体主要为了确定秘钥长度 - 调用
ssl_session_get_digest()获取EVP_MD用于秘钥派生 - 以"key"作为
label调用hkdf_expand_label()派生秘钥 - 以"iv"作为
label调用hkdf_expand_label()派生初始向量 - 调用
SSLAEADContext::Create()并传入SSL,Cipher, 秘钥,iv, 创建SSLAEADContext - 调用
SSL.method->set_read_state即tls_set_read_state()设置SSLAEADContext到SSL_HANDSHAKE.ssl->s3->aead_read_ctx - 保存
SSL_HANDSHAKE.client_handshake_secret_到SSL.s3->read_traffic_secret - 调用
SSL.method->set_write_state即tls_set_write_state()设置SSLAEADContext到SSL_HANDSHAKE.ssl->s3->aead_write_ctx - 保存
SSL_HANDSHAKE.client_handshake_secret_到SSL.s3->write_traffic_secret
- 调用
从此刻开始, 与服务器的数据往来都使用: SSL_HANDSHAKE.client_handshake_secret_ 加密
state_read_encrypted_extensions(3)
state_read_certificate_request(4)
调用do_read_certificate_request()
state_read_server_certificate(5)
调用do_read_server_certificate() -> tls13_process_certificate(), 从服务器返回的消息解析证书信息, 将证书保存到SSL_HANDSHAKE.new_session->certs
state_read_server_certificate_verify(6)
调用do_read_server_certificate_verify() -> ssl_verify_peer_cert() -> SSL_HANDSHAKE.ssl->config->custom_verify_callback, 根据前文, SSL_HANDSHAKE.ssl->config->custom_verify_callback已经被SSL_set_custom_verify()设置为:cert_verify_callback():
-
调用
SSL_get0_peer_certificates()获取上文刚解析到的服务器证书链:SSL_HANDSHAKE.new_session->certs -
调用上层:
ConscryptEngine.verifyCertificateChain() -> Platform.checkServerTrusted() -> X509Trustmanager.checkServerTrusted(), 其中ConscryptEngine继承了NativeCrypto.SSLHandshakeCallbacks,X509Trustmanager.checkServerTrusted()为用户自定义的服务器证书验证方法.
state_read_server_finished(8)
调用do_read_server_finished():
- 调用
tls13_advance_key_schedule()更新加密算法 - 调用
tls13_derive_application_secrets() -> derive_secret("CLIENT_TRAFFIC_SECRET_0")派生交换凭据的秘钥, 保存到SSL_HANDSHAKE.client_traffic_secret_0_
注意, 此时的SSL_HANDSHAKE.client_traffic_secret_0_并没有作为会话秘钥, 这是因为还没有发送本地的凭据(这里我们只讨论 MTLSv1.3 )
state_send_end_of_early_data(9)
state_send_client_encrypted_extensions(10)
state_send_client_certificate(11)
调用do_send_client_certificate() -> SSL_HANDSHAKE.ssl->cert->cert_cb, 该回调被SSL_CTX_set_cert_cb()设置为external/conscrypt/common/src/jni/main/cpp/conscrypt/native_crypto.cc::cert_cb()将在如下环节被调用:
bssl::ssl_run_handshake() ->bssl::ssl_client_handshake() ->bssl::tls13_client_handshake() ->bssl::do_send_client_certificate() ->cert_cb() [libjavacrypto.so]
如上文cert_cb()调用的上层接口为: ConscryptEngine.clientCertificateRequested() -> NativeSsl.chooseClientCertificate(), 其首先调用ConscryptEngineSocket.chooseClientAlias()选取本地的证书别名, 而ConscryptEngineSocket.chooseClientAlias()实际上是调用了X509KeyManager.chooseClientAlias(), 然后通过setCertificate()设置本地的证书&密钥, 参数为证书与秘钥的别名:
-
从
SSLParametersImpl中获取X509KeyManager -
从
X509KeyManager中获取PrivateKey, 这是上层应用自定义的 -
从
X509KeyManager中获取所有可用的X509Certificate, 即X509Certificate[] -
从
PrivateKey转换到OpenSSLKey, 这里调用:OpenSSLKey.fromPrivateKeyForTLSStackOnly() -> fromECPrivateKeyForTLSStackOnly() -> OpenSSLECPrivateKey.wrapJCAPrivateKeyForTLSStackOnly(), 首先调用OpenSSLECGroupContext.getInstance()从ECParameterSpec获取OpenSSLECGroupContext. 创建OpenSSLKey, 调用NativeCrypto_getECPrivateKeyWrapper()产生底层的EVP_PKEY结构体, 传递OpenSSLECGroupContext作为参数:-
上层传递了秘钥的 Group 信息, 通过
fromContextObject<EC_GROUP>()转换参数groupRef转换到EC_GROUP,- 通过
ensure_engine_globals() -> init_engine_globals()初始化g_rsa_method和g_ecdsa_method:- 通过
ENGINE_new()初始化g_engine - 通过
ENGINE_set_RSA_method配置g_rsa_method给g_engine - 通过
ENGINE_set_ECDSA_method配置g_ecdsa_method给g_engine
- 通过
- 通过
-
通过
EC_KEY_new_method()创建EC_KEY结构体 -
通过
EC_KEY_set_group()配置EC_KEY的Group信息 -
构造
KeyExData结构体, 将上层传递的javaKey的jobject对象保存到刚刚创建的KeyExData.private_key字段 -
通过
EC_KEY_set_ex_data()配置KeyExData结构体到EC_KEY -
通过
EVP_PKEY_new()创建EVP_PKEY结构体 -
通过
EVP_PKEY_assign_EC_KEY()将EC_KEY结构体分配给EVP_PKEY结构体 -
创建的
EVP_PKEY结构体保存到OpenSSLKey.ctx中, 所以后续将OpenSSLKey传递给底层时, 获取ctx即可
可以看到,OpenSSLKey.ctx即EVP_PKEY, 其中:-
EVP_PKEY.ameth被设置为ec_asn1_methconst EVP_PKEY_ASN1_METHOD ec_asn1_meth = {EVP_PKEY_EC,// 1.2.840.10045.2.1{0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01},7,&ec_pkey_meth,eckey_pub_decode, // 从 DER 中解码 EC-PublicKeyeckey_pub_encode, // 编码 EC-PublicKey 到 DEReckey_pub_cmp, // 比较两个公钥是否相同eckey_priv_decode, // 从 DER 解码 EC-PrivateKeyeckey_priv_encode, // 编码 EC-PrivateKey 到 DEReckey_set1_tls_encodedpoint,eckey_get1_tls_encodedpoint,eckey_opaque,int_ec_size,ec_bits,ec_missing_parameters,ec_copy_parameters, // 拷贝 ECC 秘钥参数ec_cmp_parameters, // 比较 ECC 秘钥参数int_ec_free, };其中
ec_pkey_meth的类型为EVP_PKEY_METHOD, 作为EVP_PKEY的操作方法, 记录在EVP_PKEY_CTX.pmethconst EVP_PKEY_METHOD ec_pkey_meth = {EVP_PKEY_EC,pkey_ec_init,pkey_ec_copy,pkey_ec_cleanup,pkey_ec_keygen, // 生成 ECC 秘钥pkey_ec_sign, // 使用 EC-PrivateKey 执行签名pkey_ec_verify, // 使用 EC-PublicKey 验证签名pkey_ec_derive, // 使用 EC-PrivateKey 进行 ECDHpkey_ec_paramgen,pkey_ec_ctrl, }; -
EVP_PKEY.pkey被设置为EC_KEY,其中:EC_KEY.ecdsa_meth被设置为上文提及的:g_ecdsa_method, 其中:EC_KEY.ecdsa_meth->sign为EcdsaMethodSign()EC_KEY.ecdsa_meth->flags为ECDSA_FLAG_OPAQUE
EC_KEY.ex_data.sk被设置为上文体积的:KeyExData结构体
-
-
-
调用
NativeCrypto.setLocalCertsAndPrivateKey()设置上文获取的X509Certificate(转换到byte[][])和OpenSSLKey到底层 -
接下来看
NativeCrypto.setLocalCertsAndPrivateKey, 改函数对应:NativeCrypto_setLocalCertsAndPrivateKey():- 获取
SSL结构体 - 通过
fromContextObject()获取上层的EVP_PKEY结构体(从上文提及的OpenSSLKey.ctx中获取) - 依次获取上层传递的证书数据到
CRYPTO_BUFFER结构体 - 通过
SSL_set_chain_and_key() -> cert_set_chain_and_key():- 通过
check_leaf_cert_and_privkey()检查私钥与证书是否配对, 这里需要注意的是, 其中的一个检查步骤为ssl_compare_public_and_private_key(), 该函数中做了如下逻辑:
这里因为if (EVP_PKEY_is_opaque(privkey)) {// We cannot check an opaque private key and have to trust that it// matches.return true;}EVP_PKEY.ameth->pkey_opaque为ec_asn1_meth.pkey_opaque也就是eckey_opaque该函数通过eckey_opaque()判断EVP_PKEY.pkey也就是EC_KEY中的ECDSA_METHOD.flags是否设置了ECDSA_FLAG_OPAQUE, 上文提到EC_KEY.ecdsa_meth.flags已经设置了ECDSA_FLAG_OPAQUE, 所以这里不会报错- 将
std::vector<CRYPTO_BUFFER*>的证书信息和EVP_PKEY的私钥信息配置给SSL结构体, 这里: SSL.config->cert->chain保存数个CRYPTO_BUFFERSSL.config->cert->privatekey保存EVP_PKEYSSL.config->cert->key_method设置为空
- 将
- 通过
- 获取
state_send_client_certificate_verify(12)
执行do_send_client_certificate_verify() -> ssl_private_key_sign()中:
- 执行
setup_ctx() -> EVP_DigestSignInit() -> do_sigver_init() -> EVP_PKEY_CTX_new(), 因为EVP_PKEY.ameth->pkey_method为ec_asn1_meth.pkey_method, 即ec_pkey_meth, 因此创建的EVP_PKEY_CTX.pmeth为ec_pkey_meth, 创建的EVP_MD_CTX在do_sigver_init()中设置到EVP_MD_CTX.pctx - 执行
EVP_DigestSign() -> EVP_DigestSignFinal() -> EVP_PKEY_sign() -> EVP_PKEY.pmeth->sign, 即上一步骤提及的ec_pkey_meth.sign即pkey_ec_sign(), 该方法先 从EVP_PKEY获取EC_KEY, 然后调用ECDSA_sign(), 传入EC_KEY, 如果设置了EC_KEY.ecdsa_meth->sign方法, 则调用, 如上文, 这里调用EcdsaMethodSign():- 通过
EcKeyGetKey() -> EC_KEY_get_ex_data从EC_KEY中获取KeyExData结构体中的PrivateKey对象对应的jobject - 调用
ecSignDigestWithPrivateKey(), 并将上一步骤获取的PrivateKey对象以及签名数据做为参数, 对应的上层调用为:CryptoUpcalls.ecSignDigestWithPrivateKey() -> signDigestWithPrivateKey():- 首先通过
Signature.getInstance()获取默认签名算法, 然后调用Signature.initSign()设置PrivateKey, 执行Signature.update()更新签名数据后, 执行Signature.sign()进行签名. 这里的Signature和上层自定义的PrivateKey是配套的, 这部分内容, 在后续介绍Security.Provider时再介绍.
- 首先通过
- 通过
state_complete_second_flight(13)
调用do_complete_second_flight():
- 调用
tls13_add_finished()发送协商结束消息 - 调用
tls13_set_traffic_key()设置SSL_HANDSHAKE.client_traffic_secret_0_为新的会话密钥
state_done(14, TLSv1.3 end)
state_finish_client_handshake(TLS)
调用do_finish_client_handshake(), 这里将SSL.s3->established_session设置为SSL.session
加密与解密
SSLOutputStream.write() 时的加密
上层调用: SSLOutputStream.write() -> writeInternal() -> ConscryptEngine.wrap(), ConscryptEngine.wrap()的语义是: 明文到加密数据的: 加密, 分两个步骤:
- 首先是
writePlaintextData() -> writePlaintextDataHeap() -> writePlaintextDataDirect()将数据往NativeSsl写, 更底层的操作是NativeCrypto.ENGINE_SSL_write_direct() - 然后是
readEncryptedData() -> readEncryptedDataDirect()从BioWrapper读取数据, 更底层的操作是NativeCrypto.ENGINE_SSL_read_BIO_direct()
从这里也可以总结出: 对于加密数据, 统一由 BioWrapper传递, 对于明文数据, 统一通过NativeSsl直接传递, 这里的BioWrapper也是后续 MTLS 协商结束后的 AES 加密通道.
NativeCrypto.ENGINE_SSL_write_direct() -> NativeCrypto_ENGINE_SSL_write_direct() [libjavacrypto.so] -> ::SSL_write() [libssl.so]
此处, SSL.method->write_app_data为kTLSProtocolMethod中的tls_write_app_data(), 因此
::SSL_write() [libssl.so] -> bssl::tls_write_app_data() ->bssl::do_tls_write()
此处分两个步骤:
- 首先处理数据的加密:
bssl::do_tls_write() -> bssl::tls_seal_record() -> bssl::tls_seal_scatter_record() -> bssl::do_seal_record(), 此处SSL.s3->aead_write_ctx为:SSLAEADContext, 因此:bssl::SSLAEADContext::SealScatter()被调用, 这里的ctx_为EVP_AEAD_CTX, 而EVP_AEAD_CTX.aead为EVP_aead_aes_256_gcm_tls13, 因此EVP_AEAD_CTX.aead->seal_scatter为aead_aes_gcm_tls13_seal_scatter() [libcrypto.so], 这里有硬件加速的部分, 简单列出调用的堆栈, 具体细节本文不做讨论:
EVP_AEAD_CTX_seal_scatter() [libcrypto.so] ->aead_aes_gcm_tls13_seal_scatter() ->aead_aes_gcm_seal_scatter() ->aead_aes_gcm_seal_scatter_impl() ->CRYPTO_gcm128_encrypt()
- 然后处理数据的发送:
bssl::do_tls_write() -> bssl::tls_seal_record() -> bssl::tls_seal_scatter_record() -> bssl::ssl_write_buffer_flush() -> tls_write_buffer_flush() -> BIO_write(), 将ssl.s3->write_buffer数据写入到SSL.wbio的BIO中, 如前文所述, 该BIO即internal_bio, 其关联的对象为network_bio, 至此上层就可以通过NativeCrypto.ENGINE_SSL_read_BIO_direct()读取到数 BoringSSL 需要发送的数据了.
NativeCrypto.ENGINE_SSL_read_BIO_direct() ->NativeCrypto_ENGINE_SSL_read_BIO_direct() [libjavacrypto.so] -> ::BIO_read() ->bio_read() [external/boringssl/src/crypto/bio/pair.c]
这里从network_bio读取internal_bio的加密数据.
SSLInputStream.read() 时的解密
上层调用: SSLInputStream.read() -> readUntilDataAvailable() -> processDataFromSocket() -> ConscryptEngine.unwrap(), ConscryptEngine.unwrap()的语义是: 加密数据到明文的: 解密, ,unwrap()分两个步骤进行:
* 首先是writeEncryptedData() -> writeEncryptedDataHeap() -> writeEncryptedDataDirect()将数据往BioWrapper写, 更底层的操作是NativeCrypto.ENGINE_SSL_write_BIO_direct()
* 然后是readPlaintextData() -> readPlaintextDataDirect()从NativeSsl读取数据, 更底层的操作是NativeCrypto.ENGINE_SSL_read_direct()
NativeCrypto.ENGINE_SSL_write_BIO_direct() ->NativeCrypto_ENGINE_SSL_write_BIO_direct() [libjavacrypto.so] -> ::BIO_write() ->bio_write() [external/boringssl/src/crypto/bio/pair.c]
这里通过network_bio写入加密数据到internal_bio
NativeCrypto.ENGINE_SSL_read_direct() ->NativeCrypto_ENGINE_SSL_read_direct() [libjavacrypto.so] -> ::SSL_read() ->::SSL_peek() ->ssl_read_impl()
这里开始分两部分:
- 首先处理数据的读取, 从
BIO中读取上层写入的加密数据
ssl_read_impl() ->bssl::ssl_handle_open_record() ->bssl::ssl_read_buffer_extend_to() ->tls_read_buffer_extend_to() ->BIO_read() [libcrypto.so] ->bio_read()
- 然后处理数据的解密,
ssl_read_impl() -> tls_open_app_data() -> tls_open_record(), 此处SSL.s3->aead_read_ctx为:SSLAEADContext, 因此:bssl::SSLAEADContext::Open() -> EVP_AEAD_CTX_open() -> EVP_AEAD_CTX_open_gather()被调用, 这里的ctx_为EVP_AEAD_CTX, 而EVP_AEAD_CTX.aead为EVP_aead_aes_256_gcm_tls13, 因此EVP_AEAD_CTX.aead->open_gather为aead_aes_gcm_open_gather() [libcrypto.so], 这里有硬件加速的部分, 简单列出调用的堆栈, 具体细节本文不做讨论:
bssl::tls_open_record() ->bssl::SSLAEADContext::Open() ->EVP_AEAD_CTX_open() ->EVP_AEAD_CTX_open_gather() ->aead_aes_gcm_open_gather_impl() ->CRYPTO_gcm128_decrypt_ctr32()
相关文章:
Android Boring SSL
前期设置 SSLContext.getInstance(“TLS”)SSLContext.init()SSLContext.getSocketFactory()SSLSocketFactory.createSocket()NativeSsl.newInstance()BioWrapper 的创建ConscryptEngineSocket.startHandshake() TLS协商 state_start_connect(TLS)state_enter_early_data(TLS)s…...
中国人民大学与加拿大女王大学金融硕士项目:开启你的金融精英之路
在全球化的今天,金融行业的发展日新月异,对金融人才的需求也日益增长。为了满足这一需求,中国人民大学与加拿大女王大学联合推出了金融硕士项目,旨在培养具有国际视野、专业素养和创新能力的金融精英。 这一开创性的项目将两大世…...
HashSet编程小案例,控制生日和姓名。重写HashCode
Java编程: 定义员工Employee类,该类包含:private成员属性name,sal,birthday(MyDate类型), 其中birthday为MyDate类型(属性包括:year,month,day), 要求&…...
虚幻阴影整理
虚拟阴影贴图(VSM)是一种全新的阴影贴图方法,可以提供稳定的高分辨率阴影。通过与虚幻引擎5的Nanite虚拟几何体、Lumen全局光照和反射以及世界分区功能结合使用,它能够实现电影级的品质效果,为大型开放场景提供光照。 …...
MySQL数据库(一)
数据库 —— 基础 1. 数据库 DataBase 数据库管理系统 2. SQL语言2.1 DDL数据定义语言2.1.1 数据库基础操作2.1.2 数据表基础操作2.1.3 字段基础操作 2.2 DML表记录管理2.2.1 插入数据INSERT2.2.2 更新数据UPDATE2.2.3 删除数据DELETE 3. SQL数据类型3.1 数值类型3.1.1 整数类型…...
C++11 新特性
C11 新特性 C11 新特性统一的列表初始化声明auto 关键字decltype 关键字nullptr 关键字 关键字 using使用 using 在子类中引用基类的成员使用 using 关键字定义类型别名 范围-based for 循环右值引用和移动语义左值引用和右值引用右值引用使用场景和意义完美转发 lambda表达式移…...
排查手机应用app微信登录问题不跳转失败原因汇总及其解决方案
经过最近我发的文章,我个人觉得解决了不少小问题,因为最近很小白的问题已经没有人私聊问我了,我总结了一下排查手机应用app微信登录问题不跳转失败的原因汇总及其解决方案在这篇文章中,分析微信登录不跳转的原因,并提供解决方案。希望通过这篇文章,能够帮助大家顺利解决这…...
软考高级系统架构设计师系列之:数学与经济管理
软考高级系统架构设计师系列之:数学与经济管理 一、数学与经济管理二、图论应用-最小生成树三、图论应用-最短路径四、图论应用-网络与最大流量五、运筹方法-线性规划六、运筹方法-动态规划七、运筹方法-转移矩阵八、运筹方法-排队论九、运筹方法-决策-不确定决策十、运筹方法…...
基于Scrapyd与Gerapy部署scrapy爬虫方案【可用于分布式爬虫部署】
scrapyd部署爬虫 Scrapyd 是一个基于 Scrapy 的开源项目,它提供了一个简单的方式来部署、运行和监控 Scrapy 爬虫。它是一个用于集成 Scrapy 爬虫到分布式架构中的工具,允许您在分布式环境中运行爬虫,并提供了一组 Web API,用于管…...
ST-SSL:基于自监督学习的交通流预测模型
文章信息 文章题为“Spatio-Temporal Self-Supervised Learning for Traffic Flow Prediction”,是一篇发表于The Thirty-Seventh AAAI Conference on Artificial Intelligence (AAAI-23)的一篇论文。该论文主要针对交通流预测任务,结合自监督学习&#…...
如何处理C++中的字符串编码和国际化?
在C中处理字符串编码和国际化的常用方式如下: 字符串编码: 使用UTF-8编码:UTF-8是一种可变字节长度的编码方式,广泛用于表示 Unicode 字符。C中的字符串类型std::string默认使用的是UTF-8编码。可以通过使用宽字符类型std::wstrin…...
DH48WK 温控器参数设置
北京东昊力伟科技有限责任公司 温控仪、温度控制器 产品特点: 可外接温度传感器Pt100、Cu50、K、E、J、N、T、R、S、B兼容输入;PID控制输出、位式控制输出、继电器报警输出;控温能满足设定温度值的0.2℃;既可用于加热控制、也可…...
【文档智能】多模态预训练模型及相关数据集汇总
前言 大模型时代,在现实场景中或者企业私域数据中,大多数数据都以文档的形式存在,如何更好的解析获取文档数据显得尤为重要。文档智能也从以前的目标检测(版面分析)阶段转向多模态预训练阶段,本文将介绍目…...
超全整理,性能测试——数据库索引问题定位+分析(详细)
目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 1、数据库服务器添…...
44springboot摄影跟拍预定管理系统
大家好✌!我是CZ淡陌。一名专注以理论为基础实战为主的技术博主,将再这里为大家分享优质的实战项目,本人在Java毕业设计领域有多年的经验,陆续会更新更多优质的Java实战项目,希望你能有所收获,少走一些弯路…...
Flink之窗口触发机制及自定义Trigger的使用
1 窗口触发机制 窗口计算的触发机制都是由Trigger类决定的,Flink中为各类内置的WindowsAssigner都设计了对应的默认Trigger. 层次结构如下: Trigger ProcessingTimeoutTriggerEventTimeTriggerCountTriggerDeltaTriggerNeverTrigger in GlobalWindowsContinuousEventTimeTrigge…...
蓝牙资讯|2024年智能家居新趋势,蓝牙助力智能家居发展
2024年将迎来变革,智能家居趋势不仅会影响我们的生活空间,还会提高我们的生活质量,让我们有更多时间享受属于自己的时光。 2024年智能家居新趋势 趋势一:多功能科技 2024年预示着多功能技术的趋势,创新将成为焦点。混…...
机器学习 | Python实现GA-XGBoost遗传算法优化极限梯度提升树特征分类模型调参
机器学习 | Python实现GA-XGBoost遗传算法优化极限梯度提升树特征分类 目录 机器学习 | Python实现GA-XGBoost遗传算法优化极限梯度提升树特征分类基本介绍模型描述程序设计参考资料基本介绍 XGBoost的核心算法思想基本就是: 不断地添加树,不断地进行特征分裂来生长一棵树,每…...
手部关键点检测3:Pytorch实现手部关键点检测(手部姿势估计)含训练代码和数据集
手部关键点检测3:Pytorch实现手部关键点检测(手部姿势估计)含训练代码和数据集 目录 手部关键点检测3:Pytorch实现手部关键点检测(手部姿势估计)含训练代码和数据集 1. 前言 2.手部关键点检测(手部姿势估计)方法 (1)Top-Down(自上而下)方法 (2)Bot…...
服务日志性能调优,由log引出的巨坑
只有被线上服务问题毒打过的人才明白日志有多重要! 谁赞成,谁反对?如果你深有同感,那恭喜你是个社会人了:) 日志对程序的重要性不言而喻,轻巧、简单、无需费脑,程序代码中随处可见…...
【杂谈】-递归进化:人工智能的自我改进与监管挑战
递归进化:人工智能的自我改进与监管挑战 文章目录 递归进化:人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管?3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
大型活动交通拥堵治理的视觉算法应用
大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动(如演唱会、马拉松赛事、高考中考等)期间,城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例,暖城商圈曾因观众集中离场导致周边…...
【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
Redis数据倾斜问题解决
Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中,部分节点存储的数据量或访问量远高于其他节点,导致这些节点负载过高,影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...
Spring数据访问模块设计
前面我们已经完成了IoC和web模块的设计,聪明的码友立马就知道了,该到数据访问模块了,要不就这俩玩个6啊,查库势在必行,至此,它来了。 一、核心设计理念 1、痛点在哪 应用离不开数据(数据库、No…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
