jli  Linuxx86_641.10.3v1.10.30b4590a5507d3f3046e5bafc007cacbbfc9b310b8E:oOpenSSLI1M8tCьX80/opt/julia/packages/OpenSSL/hXs2T/src/OpenSSL.jlCA5oi~~_dΣBitFlagsj2 EY8pDates:* 8P. K5CRC32c\y.jmуj K5 FileWatchingXzsy`{,zmуh& K5LibdluVW59˗,mу-" K5LoggingT{VhUXM=mуrU" K5MmapP~:xg,Omу|' K5NetworkOptionsC0YW,mуʠ, K5SHAQ<$!<%mу1 K5 Serialization [)*k1mу-G K5Sockets1V$ bdސݗmуYBY K5UnicodeP>I>Nrmуeszo K5 LinearAlgebraSm7̏mуuux K5 OpenBLAS_jll[(Śb6EcQ FmуDux K5libblastrampoline_jllLSۆ }lxӠmу^} K5MarkdownZPn7z`smу/Ed~ K5Printfg^cX׸QDmу;h K5Random_ɢ?\Ymу? K5TarOi>աmу!t, K5DatesEY8pj2 mуX K5FuturebS;3{I xVMmуsD K5InteractiveUtilsWL ~@'ZmуVg K5LibGit2Z[&RPTv3EКRmу8J K5 LibGit2_jll YXg}]$mуD K5 MbedTLS_jllAX 3ȡ_mу- K5 LibSSH2_jlloTZk)߆> 28 minor = vn >> 20 & 0xff patch = vn >> 4 & 0xff return VersionNumber(major, minor, patch) end @static if version_number() ≥ v"3" # In OpenSSL v3, macros redirect these symbols const SSL_get_peer_certificate = :SSL_get1_peer_certificate const EVP_PKEY_base_id = :EVP_PKEY_get_base_id const EVP_CIPHER_key_length = :EVP_CIPHER_get_key_length const EVP_CIPHER_iv_length = :EVP_CIPHER_get_iv_length const EVP_CIPHER_block_size = :EVP_CIPHER_get_block_size const EVP_CIPHER_CTX_block_size = :EVP_CIPHER_CTX_get_block_size const EVP_CIPHER_CTX_key_length = :EVP_CIPHER_CTX_get_key_length const EVP_CIPHER_CTX_iv_length = :EVP_CIPHER_CTX_get_iv_length else const SSL_get_peer_certificate = :SSL_get_peer_certificate const EVP_PKEY_base_id = :EVP_PKEY_base_id const EVP_CIPHER_key_length = :EVP_CIPHER_key_length const EVP_CIPHER_iv_length = :EVP_CIPHER_iv_length const EVP_CIPHER_block_size = :EVP_CIPHER_block_size const EVP_CIPHER_CTX_block_size = :EVP_CIPHER_CTX_block_size const EVP_CIPHER_CTX_key_length = :EVP_CIPHER_CTX_key_length const EVP_CIPHER_CTX_iv_length = :EVP_CIPHER_CTX_iv_length end # Locate oss-modules, which contains legacy shared library function _ossl_modules_path() @static if Sys.iswindows() bin_dir = dirname(OpenSSL_jll.libssl_path) lib_dir = joinpath(dirname(bin_dir), "lib") if Sys.WORD_SIZE == 64 return joinpath(lib_dir * "64", "ossl-modules") else return joinpath(lib_dir, "ossl-modules") end else return joinpath(dirname(OpenSSL_jll.libssl), "ossl-modules") end end """ ossl_provider_set_default_search_path([libctx], [path]) Set the default search path for providers. If no arguments are given, the global context will be configured for the ossl-modules directory in the OpenSSL_jll artifact. This is called with no arguments in OpenSSL.jl `__init__` when OpenSSL v3 is used. !!! compat "OpenSSL v3" `ossl_provider_set_default_search_path` is only available with version 3 of the OpenSSL_jll """ function ossl_provider_set_default_search_path(libctx = C_NULL, path = _ossl_modules_path()) result = ccall( (:OSSL_PROVIDER_set_default_search_path, libcrypto), Cint, (Ptr{Nothing}, Cstring), libctx, path ) if result == 0 throw(OpenSSLError()) end return result end """ load_provider([libctx], provider_name) Load a provider. If libctx is omitted, the provider will be loaded into the global context. !!! compat "OpenSSL v3" `load_provider` is only available with version 3 of the OpenSSL_jll """ function load_provider(libctx, provider_name) result = ccall( (:OSSL_PROVIDER_load, libcrypto), Ptr{Nothing}, (Ptr{Nothing}, Cstring), libctx, provider_name ) if result == C_NULL throw(OpenSSLError()) end return nothing end load_provider(provider_name) = load_provider(C_NULL, provider_name) """ load_legacy_provider() Load the legacy provider. This loads legacy ciphers such as Blowfish. See https://www.openssl.org/docs/man3.0/man7/OSSL_PROVIDER-legacy.html !!! compat "OpenSSL v3" `load_legacy_provider` is only available with version 3 of the OpenSSL_jll """ load_legacy_provider() = load_provider(C_NULL, "legacy") """ Random bytes. """ function random_bytes!(rand_data::Vector{UInt8}) GC.@preserve rand_data begin if ccall( (:RAND_bytes, libcrypto), Cint, (Ptr{UInt8}, Cint), pointer(rand_data), length(rand_data)) != 1 throw(OpenSSLError()) end end end function random_bytes(length) rand_data = Vector{UInt8}(undef, length) random_bytes!(rand_data) return rand_data end """ Big number context. """ mutable struct BigNumContext bn_ctx::Ptr{Cvoid} function BigNumContext() big_num_contex = ccall( (:BN_CTX_secure_new, libcrypto), Ptr{Cvoid}, ()) if big_num_contex == C_NULL throw(OpenSSLError()) end big_num_contex = new(big_num_contex) finalizer(free, big_num_contex) return big_num_contex end end function free(big_num_contex::BigNumContext) ccall( (:BN_CTX_free, libcrypto), Cvoid, (BigNumContext,), big_num_contex) big_num_contex.bn_ctx = C_NULL return nothing end """ Big number, multiprecision integer arithmetics. """ mutable struct BigNum bn::Ptr{Cvoid} function BigNum(bn::Ptr{Cvoid}) big_num = new(bn) finalizer(free, big_num) return big_num end function BigNum() bn = ccall( (:BN_new, libcrypto), Ptr{Cvoid}, ()) if bn == C_NULL throw(OpenSSLError()) end big_num = BigNum(bn) return big_num end BigNum(value::UInt8) = BigNum(UInt64(value)) BigNum(value::UInt32) = BigNum(UInt64(value)) function BigNum(value::UInt64) big_num = BigNum() if ccall( (:BN_set_word, libcrypto), Cint, (BigNum, UInt64), big_num, value) != 1 throw(OpenSSLError()) end return big_num end end function free(big_num::BigNum) ccall( (:BN_free, libcrypto), Cvoid, (BigNum,), big_num) big_num.bn = C_NULL return nothing end function Base.:(==)(big_num_1::BigNum, big_num_2::BigNum) result = ccall( (:BN_cmp, libcrypto), Cint, (BigNum, BigNum), big_num_1, big_num_2) if result == -2 throw(OpenSSLError()) end return result == 0 end """ BigNum does not support up ref. Duplicate the big number instead. """ function up_ref(big_num::BigNum)::Ptr{Cvoid} big_num = ccall( (:BN_dup, libcrypto), Ptr{Cvoid}, (BigNum,), big_num) if big_num == C_NULL throw(OpenSSLError()) end return big_num end function Base.:+(a::BigNum, b::BigNum)::BigNum r = BigNum() if ccall( (:BN_add, libcrypto), Cint, (BigNum, BigNum, BigNum), r, a, b) != 1 throw(OpenSSLError()) end return r end function Base.:-(a::BigNum, b::BigNum)::BigNum r = BigNum() if ccall( (:BN_sub, libcrypto), Cint, (BigNum, BigNum, BigNum), r, a, b) != 1 throw(OpenSSLError()) end return r end function Base.:*(a::BigNum, b::BigNum)::BigNum r = BigNum() c = BigNumContext() if ccall( (:BN_mul, libcrypto), Cint, (BigNum, BigNum, BigNum, BigNumContext), r, a, b, c) != 1 throw(OpenSSLError()) end finalize(c) return r end function Base.:/(a::BigNum, b::BigNum)::BigNum dv = BigNum() rm = BigNum() c = BigNumContext() if ccall( (:BN_div, libcrypto), Cint, (BigNum, BigNum, BigNum, BigNum, BigNumContext), dv, rm, a, b, c) != 1 throw(OpenSSLError()) end finalize(rm) finalize(c) return dv end function Base.:%(a::BigNum, b::BigNum)::BigNum dv = BigNum() rm = BigNum() c = BigNumContext() if ccall( (:BN_div, libcrypto), Cint, (BigNum, BigNum, BigNum, BigNum, BigNumContext), dv, rm, a, b, c) != 1 throw(OpenSSLError()) end finalize(dv) finalize(c) return rm end """ EVP_CIPHER. """ mutable struct EvpCipher evp_cipher::Ptr{Cvoid} end get_block_size(evp_cipher::EvpCipher)::Int32 = ccall( (EVP_CIPHER_block_size, libcrypto), Int32, (EvpCipher,), evp_cipher) get_key_length(evp_cipher::EvpCipher)::Int32 = ccall( (EVP_CIPHER_key_length, libcrypto), Int32, (EvpCipher,), evp_cipher) get_init_vector_length(evp_cipher::EvpCipher)::Int32 = ccall( (EVP_CIPHER_iv_length, libcrypto), Int32, (EvpCipher,), evp_cipher) function Base.getproperty(evp_cipher::EvpCipher, name::Symbol) if name === :block_size return get_block_size(evp_cipher) elseif name === :key_length return get_key_length(evp_cipher) elseif name === :init_vector_length return get_init_vector_length(evp_cipher) else # fallback to getfield return getfield(evp_cipher, name) end end """ Basic Block Cipher Modes: - ECB Electronic Code Block - CBC Cipher Block Chaining - CFB Cipher Feedback - OFB Output Feedback """ """ Blowfish encryption algorithm in CBC, ECB, CFB and OFB modes. """ EvpBlowFishCBC()::EvpCipher = EvpCipher(ccall((:EVP_bf_cbc, libcrypto), Ptr{Cvoid}, ())) EvpBlowFishECB()::EvpCipher = EvpCipher(ccall((:EVP_bf_ecb, libcrypto), Ptr{Cvoid}, ())) EvpBlowFishCFB()::EvpCipher = EvpCipher(ccall((:EVP_bf_cfb, libcrypto), Ptr{Cvoid}, ())) EvpBlowFishOFB()::EvpCipher = EvpCipher(ccall((:EVP_bf_ofb, libcrypto), Ptr{Cvoid}, ())) """ AES with a 128-bit key in CBC, ECB, CFB and OFB modes. """ EvpAES128CBC()::EvpCipher = EvpCipher(ccall((:EVP_aes_128_cbc, libcrypto), Ptr{Cvoid}, ())) EvpAES128ECB()::EvpCipher = EvpCipher(ccall((:EVP_aes_128_ecb, libcrypto), Ptr{Cvoid}, ())) EvpAES128CFB()::EvpCipher = EvpCipher(ccall((:EVP_aes_128_cfb, libcrypto), Ptr{Cvoid}, ())) EvpAES128OFB()::EvpCipher = EvpCipher(ccall((:EVP_aes_128_ofb, libcrypto), Ptr{Cvoid}, ())) """ Null cipher, does nothing. """ EvpEncNull()::EvpCipher = EvpCipher(ccall((:EVP_enc_null, libcrypto), Ptr{Cvoid}, ())) mutable struct EvpCipherContext evp_cipher_ctx::Ptr{Cvoid} function EvpCipherContext(evp_cipher_ctx::Ptr{Cvoid}) evp_cipher_ctx = new(evp_cipher_ctx) finalizer(free, evp_cipher_ctx) return evp_cipher_ctx end function EvpCipherContext() evp_cipher_ctx = ccall( (:EVP_CIPHER_CTX_new, libcrypto), Ptr{Cvoid}, ()) if evp_cipher_ctx == C_NULL throw(OpenSSLError()) end return EvpCipherContext(evp_cipher_ctx) end end function free(evp_cipher_ctx::EvpCipherContext) ccall( (:EVP_CIPHER_CTX_free, libcrypto), Cvoid, (EvpCipherContext,), evp_cipher_ctx) evp_cipher_ctx.evp_cipher_ctx = C_NULL return nothing end function decrypt_init( evp_cipher_ctx::EvpCipherContext, evp_cipher::EvpCipher, symetric_key::Vector{UInt8}, init_vector::Vector{UInt8}) # Initialize decryption context. GC.@preserve symetric_key init_vector begin if length(symetric_key) == get_key_length(evp_cipher) # Use default key length. if ccall( (:EVP_DecryptInit_ex, libcrypto), Cint, (EvpCipherContext, EvpCipher, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), evp_cipher_ctx, evp_cipher, C_NULL, pointer(symetric_key), pointer(init_vector)) != 1 throw(OpenSSLError()) end else if ccall( (:EVP_DecryptInit_ex, libcrypto), Cint, (EvpCipherContext, EvpCipher, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), evp_cipher_ctx, evp_cipher, C_NULL, pointer(symetric_key), pointer(init_vector)) != 1 throw(OpenSSLError()) end if ccall( (:EVP_CIPHER_CTX_set_key_length, libcrypto), Cint, (EvpCipherContext, Cint), evp_cipher_ctx, length(symetric_key)) != 1 throw(OpenSSLError()) end if ccall( (:EVP_DecryptInit_ex, libcrypto), Cint, (EvpCipherContext, EvpCipher, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), evp_cipher_ctx, evp_cipher, C_NULL, pointer(symetric_key), pointer(init_vector)) != 1 throw(OpenSSLError()) end end end end function encrypt_init( evp_cipher_ctx::EvpCipherContext, evp_cipher::EvpCipher, symetric_key::Vector{UInt8}, init_vector::Vector{UInt8}) # Initialize encryption context. GC.@preserve symetric_key init_vector begin if length(symetric_key) == get_key_length(evp_cipher) # Use default key length. if ccall( (:EVP_EncryptInit_ex, libcrypto), Cint, (EvpCipherContext, EvpCipher, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), evp_cipher_ctx, evp_cipher, C_NULL, pointer(symetric_key), pointer(init_vector)) != 1 throw(OpenSSLError()) end else if ccall( (:EVP_EncryptInit_ex, libcrypto), Cint, (EvpCipherContext, EvpCipher, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), evp_cipher_ctx, evp_cipher, C_NULL, pointer(symetric_key), pointer(init_vector)) != 1 throw(OpenSSLError()) end if ccall( (:EVP_CIPHER_CTX_set_key_length, libcrypto), Cint, (EvpCipherContext, Cint), evp_cipher_ctx, length(symetric_key)) != 1 throw(OpenSSLError()) end if ccall( (:EVP_EncryptInit_ex, libcrypto), Cint, (EvpCipherContext, EvpCipher, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), evp_cipher_ctx, evp_cipher, C_NULL, pointer(symetric_key), pointer(init_vector)) != 1 throw(OpenSSLError()) end end end end function cipher_update(evp_cipher_ctx::EvpCipherContext, in_data::Vector{UInt8})::Vector{UInt8} in_length = length(in_data) block_size = get_block_size(evp_cipher_ctx) enc_length = Int((in_length + block_size - 1) / block_size * block_size) out_data = Vector{UInt8}(undef, enc_length) out_length = Ref{UInt32}(0) GC.@preserve in_data out_data out_length begin if ccall( (:EVP_CipherUpdate, libcrypto), Cint, (EvpCipherContext, Ptr{UInt8}, Ptr{Int32}, Ptr{UInt8}, Cint), evp_cipher_ctx, pointer(out_data), pointer_from_objref(out_length), pointer(in_data), in_length) != 1 throw(OpenSSLError()) end end resize!(out_data, out_length.x) return out_data end function cipher_final(evp_cipher_ctx::EvpCipherContext)::Vector{UInt8} block_size = get_block_size(evp_cipher_ctx) out_data = Vector{UInt8}(undef, block_size) out_length = Ref{Int32}(0) GC.@preserve out_data out_length begin if ccall( (:EVP_CipherFinal_ex, libcrypto), Cint, (EvpCipherContext, Ptr{UInt8}, Ptr{Int32}), evp_cipher_ctx, pointer(out_data), pointer_from_objref(out_length)) != 1 throw(OpenSSLError()) end end resize!(out_data, out_length.x) return out_data end function cipher(evp_cipher_ctx::EvpCipherContext, in_io::IO, out_io::IO) while !eof(in_io) available_bytes = bytesavailable(in_io) in_data = read(in_io, available_bytes) write(out_io, cipher_update(evp_cipher_ctx, in_data)) end write(out_io, cipher_final(evp_cipher_ctx)) return nothing end get_block_size(evp_cipher_ctx::EvpCipherContext)::Int32 = ccall( (EVP_CIPHER_CTX_block_size, libcrypto), Int32, (EvpCipherContext,), evp_cipher_ctx) get_key_length(evp_cipher_ctx::EvpCipherContext)::Int32 = ccall( (EVP_CIPHER_CTX_key_length, libcrypto), Int32, (EvpCipherContext,), evp_cipher_ctx) get_init_vector_length(evp_cipher_ctx::EvpCipherContext)::Int32 = ccall( (EVP_CIPHER_CTX_iv_length, libcrypto), Int32, (EvpCipherContext,), evp_cipher_ctx) function Base.getproperty(evp_cipher_ctx::EvpCipherContext, name::Symbol) if name === :block_size return get_block_size(evp_cipher_ctx) elseif name === :key_length return get_key_length(evp_cipher_ctx) elseif name === :init_vector_length return get_init_vector_length(evp_cipher_ctx) else # fallback to getfield return getfield(evp_cipher_ctx, name) end end """ EVP Message Digest. """ mutable struct EvpDigest evp_md::Ptr{Cvoid} end EvpMDNull()::EvpDigest = EvpDigest(ccall((:EVP_md_null, libcrypto), Ptr{Cvoid}, ())) EvpMD2()::EvpDigest = EvpDigest(ccall((:EVP_md2, libcrypto), Ptr{Cvoid}, ())) EvpMD5()::EvpDigest = EvpDigest(ccall((:EVP_md5, libcrypto), Ptr{Cvoid}, ())) EvpSHA1()::EvpDigest = EvpDigest(ccall((:EVP_sha1, libcrypto), Ptr{Cvoid}, ())) EvpSHA224()::EvpDigest = EvpDigest(ccall((:EVP_sha224, libcrypto), Ptr{Cvoid}, ())) EvpSHA256()::EvpDigest = EvpDigest(ccall((:EVP_sha256, libcrypto), Ptr{Cvoid}, ())) EvpSHA384()::EvpDigest = EvpDigest(ccall((:EVP_sha384, libcrypto), Ptr{Cvoid}, ())) EvpSHA512()::EvpDigest = EvpDigest(ccall((:EVP_sha512, libcrypto), Ptr{Cvoid}, ())) EvpDSS1()::EvpDigest = EvpDigest(ccall((:EVP_dss1, libcrypto), Ptr{Cvoid}, ())) """ EVP Message Digest Context. """ mutable struct EvpDigestContext evp_md_ctx::Ptr{Cvoid} function EvpDigestContext() evp_digest_ctx = ccall( (:EVP_MD_CTX_new, libcrypto), Ptr{Cvoid}, ()) if evp_digest_ctx == C_NULL throw(OpenSSLError()) end evp_digest_ctx = new(evp_digest_ctx) finalizer(free, evp_digest_ctx) return evp_digest_ctx end end function free(evp_digest_ctx::EvpDigestContext) ccall( (:EVP_MD_CTX_free, libcrypto), Cvoid, (EvpDigestContext,), evp_digest_ctx) evp_digest_ctx.evp_md_ctx = C_NULL return nothing end function digest_init(evp_digest_ctx::EvpDigestContext, evp_digest::EvpDigest) if ccall( (:EVP_DigestInit_ex, libcrypto), Cint, (EvpDigestContext, EvpDigest, Ptr{Cvoid}), evp_digest_ctx, evp_digest, C_NULL) != 1 throw(OpenSSLError()) end end function digest_update(evp_digest_ctx::EvpDigestContext, in_data::Vector{UInt8}) GC.@preserve in_data begin if ccall( (:EVP_DigestUpdate, libcrypto), Cint, (EvpDigestContext, Ptr{UInt8}, Csize_t), evp_digest_ctx, pointer(in_data), length(in_data)) != 1 throw(OpenSSLError()) end end end function digest_final(evp_digest_ctx::EvpDigestContext)::Vector{UInt8} out_data = Vector{UInt8}(undef, EVP_MAX_MD_SIZE) out_length = Ref{UInt32}(0) GC.@preserve out_data out_length begin if ccall( (:EVP_DigestFinal_ex, libcrypto), Cint, (EvpDigestContext, Ptr{UInt8}, Ptr{UInt32}), evp_digest_ctx, pointer(out_data), pointer_from_objref(out_length)) != 1 throw(OpenSSLError()) end end resize!(out_data, out_length.x) return out_data end """ Computes the message digest (hash). """ function digest(evp_digest::EvpDigest, io::IO) md_ctx = EvpDigestContext() digest_init(md_ctx, evp_digest) while !eof(io) available_bytes = bytesavailable(io) in_data = read(io, available_bytes) digest_update(md_ctx, in_data) end result = digest_final(md_ctx) finalize(md_ctx) return result end """ RSA structure. The RSA structure consists of several BIGNUM components. It can contain public as well as private RSA keys. """ mutable struct RSA rsa::Ptr{Cvoid} function RSA() rsa = ccall( (:RSA_new, libcrypto), Ptr{Cvoid}, ()) if rsa == C_NULL throw(OpenSSLError()) end rsa = new(rsa) finalizer(free, rsa) return rsa end end function free(rsa::RSA) ccall( (:RSA_free, libcrypto), Cvoid, (RSA,), rsa) rsa.rsa = C_NULL return nothing end """ Generate RSA key pair. """ function rsa_generate_key(; bits::Int32=Int32(2048))::RSA rsa = RSA() big_num = BigNum(UInt64(RSA_F4)) if ccall( (:RSA_generate_key_ex, libcrypto), Cint, (RSA, Cint, BigNum, Ptr{Cvoid}), rsa, bits, big_num, C_NULL) != 1 throw(OpenSSLError()) end return rsa end """ DSA structure. """ mutable struct DSA dsa::Ptr{Cvoid} function DSA() dsa = ccall( (:DSA_new, libcrypto), Ptr{Cvoid}, ()) if dsa == C_NULL throw(OpenSSLError()) end dsa = new(dsa) finalizer(free, dsa) return dsa end end function free(dsa::DSA) ccall( (:DSA_free, libcrypto), Cvoid, (DSA,), dsa) dsa.dsa = C_NULL return nothing end """ Generate DSA key pair. """ function dsa_generate_key(; bits::Int32=Int32(1024))::DSA dsa = DSA() if ccall( (:DSA_generate_parameters_ex, libcrypto), Cint, (DSA, Cint, Ptr{UInt8}, Cint, Ptr{Cint}, Ptr{Culong}, Ptr{Cvoid}), dsa, bits, C_NULL, 1, C_NULL, C_NULL, C_NULL) != 1 throw(OpenSSLError()) end return dsa end """ OpenSSL BIOMethod. """ mutable struct BIOMethod bio_method::Ptr{Cvoid} BIOMethod(bio_method::Ptr{Cvoid}) = new(bio_method) function BIOMethod(bio_type::String) bio_method_index = ccall( (:BIO_get_new_index, libcrypto), Cint, ()) if bio_method_index == -1 throw(OpenSSLError()) end bio_method = ccall( (:BIO_meth_new, libcrypto), Ptr{Cvoid}, (Cint, Cstring), bio_method_index, bio_type) if bio_method == C_NULL throw(OpenSSLError()) end bio_method = new(bio_method) finalizer(free, bio_method) if ccall( (:BIO_meth_set_create, libcrypto), Cint, (BIOMethod, Ptr{Cvoid}), bio_method, BIO_STREAM_CALLBACKS.x.on_bio_create_ptr) != 1 throw(OpenSSLError()) end if ccall( (:BIO_meth_set_destroy, libcrypto), Cint, (BIOMethod, Ptr{Cvoid}), bio_method, BIO_STREAM_CALLBACKS.x.on_bio_destroy_ptr) != 1 throw(OpenSSLError()) end if ccall( (:BIO_meth_set_read, libcrypto), Cint, (BIOMethod, Ptr{Cvoid}), bio_method, BIO_STREAM_CALLBACKS.x.on_bio_read_ptr) != 1 throw(OpenSSLError()) end if ccall( (:BIO_meth_set_write, libcrypto), Cint, (BIOMethod, Ptr{Cvoid}), bio_method, BIO_STREAM_CALLBACKS.x.on_bio_write_ptr) != 1 throw(OpenSSLError()) end if ccall( (:BIO_meth_set_puts, libcrypto), Cint, (BIOMethod, Ptr{Cvoid}), bio_method, BIO_STREAM_CALLBACKS.x.on_bio_puts_ptr) != 1 throw(OpenSSLError()) end if ccall( (:BIO_meth_set_ctrl, libcrypto), Cint, (BIOMethod, Ptr{Cvoid}), bio_method, BIO_STREAM_CALLBACKS.x.on_bio_ctrl_ptr) != 1 throw(OpenSSLError()) end return bio_method end end """ Creates a file descriptor BIO method. """ function BIOMethod_fd()::BIOMethod bio_method = ccall( (:BIO_s_fd, libcrypto), Ptr{Cvoid}, ()) return BIOMethod(bio_method) end """ Creates a memory BIO method. """ function BIOMethodMemory()::BIOMethod bio_method = ccall( (:BIO_s_mem, libcrypto), Ptr{Cvoid}, ()) return BIOMethod(bio_method) end function BIOMethodSecureMemory()::BIOMethod bio_method = ccall( (:BIO_s_secmem, libcrypto), Ptr{Cvoid}, ()) return BIOMethod(bio_method) end function free(bio_method::BIOMethod) ccall( (:BIO_meth_free, libcrypto), Cvoid, (BIOMethod,), bio_method) bio_method.bio_method = C_NULL return nothing end """ BIO. """ mutable struct BIO bio::Ptr{Cvoid} BIO(bio::Ptr{Cvoid}) = new(bio) """ Creates a BIO object using IO stream method. The BIO object is not registered with the finalizer. """ function BIO(data=nothing; finalize::Bool=true) bio = ccall( (:BIO_new, libcrypto), Ptr{Cvoid}, (BIOMethod,), BIO_STREAM_METHOD.x) if bio == C_NULL throw(OpenSSLError()) end bio = new(bio) finalize && finalizer(free, bio) # note that `data` must be held as a reference somewhere else # since it is not referenced by the BIO directly # e.g. in SSLStream, we keep the `io` reference that is passed to # the read/write BIOs ccall( (:BIO_set_data, libcrypto), Cvoid, (BIO, Ptr{Cvoid}), bio, data === nothing ? C_NULL : pointer_from_objref(data)) # Set BIO as non-blocking ccall( (:BIO_ctrl, libcrypto), Cint, (BIO, Cint, Cint, Ptr{Cvoid}), bio, 102, 1, C_NULL) # Mark BIO as initalized. ccall( (:BIO_set_init, libcrypto), Cvoid, (BIO, Cint), bio, 1) ccall( (:BIO_set_shutdown, libcrypto), Cvoid, (BIO, Cint), bio, 0) return bio end """ Creates BIO for given BIOMethod. """ function BIO(bio_method::BIOMethod) bio = ccall( (:BIO_new, libcrypto), Ptr{Cvoid}, (BIOMethod,), bio_method) if bio == C_NULL throw(OpenSSLError()) end bio = new(bio) finalizer(free, bio) return bio end end function free(bio::BIO) ccall( (:BIO_free, libcrypto), Cvoid, (BIO,), bio) bio.bio = C_NULL return nothing end clear(bio::BIO) = bio.bio """ Returns the BIO type. """ function bio_type(bio::BIO)::BIOType bio_type = ccall( (:BIO_method_type, libcrypto), Cint, (BIO,), bio) return BIOType(bio_type) end """ Returns internal BIO memory. """ function bio_get_mem_data(bio::BIO) if bio_type(bio) != BIO_TYPE_MEM throw(ArgumentError("Expecting BIO_TYPE_MEM bio.")) end # Get BIOs memory data. # define BIO_get_mem_data(b,pp) BIO_ctrl(b,BIO_CTRL_INFO,0,(char *)(pp)) mem_ptr = Ref{Ptr{UInt8}}(0) mem_data = GC.@preserve mem_ptr begin result = ccall( (:BIO_ctrl, libcrypto), Clong, (BIO, BIOCtrl, Clong, Ptr{Ptr{Cvoid}}), bio, BIO_CTRL_INFO, 0, pointer_from_objref(mem_ptr)) if mem_ptr.x != C_NULL return unsafe_wrap(Vector{UInt8}, mem_ptr.x, result; own=false) else return Vector{UInt8}() end end return mem_data end """ BIO write. """ ## throw error here function Base.unsafe_write(bio::BIO, out_buffer::Ptr{UInt8}, out_length::Int) result = ccall( (:BIO_write, libcrypto), Cint, (BIO, Ptr{Cvoid}, Cint), bio, out_buffer, out_length) if result < 0 throw(OpenSSLError()) end end Base.write(bio::BIO, out_data) = return unsafe_write(bio, pointer(out_data), length(out_data)) """ ASN1_TIME. """ mutable struct Asn1Time asn1_time::Ptr{Cvoid} Asn1Time(asn1_time::Ptr{Cvoid}) = new(asn1_time) Asn1Time() = Asn1Time(0) Asn1Time(date_time::DateTime) = Asn1Time(Int64(floor(datetime2unix(date_time)))) function Asn1Time(unix_time::Int) asn1_time = ccall( (:ASN1_TIME_set, libcrypto), Ptr{Cvoid}, (Ptr{Cvoid}, Int64), C_NULL, unix_time) if asn1_time == C_NULL throw(OpenSSLError()) end asn1_time = new(asn1_time) finalizer(free, asn1_time) return asn1_time end end function free(asn1_time::Asn1Time) ccall( (:ASN1_STRING_free, libcrypto), Cvoid, (Asn1Time,), asn1_time) asn1_time.asn1_time = C_NULL return nothing end function Dates.adjust(asn1_time::Asn1Time, seconds::Second) adj_asn1_time = ccall( (:X509_gmtime_adj, libcrypto), Ptr{Cvoid}, (Asn1Time, Int64), asn1_time, seconds.value) if adj_asn1_time == C_NULL throw(OpenSSLError()) end if (adj_asn1_time != asn1_time.asn1_time) free(asn1_time) asn1_time.asn1_time = adj_asn1_time end end Dates.adjust(asn1_time::Asn1Time, days::Day) = Dates.adjust(asn1_time, Second(days)) Dates.adjust(asn1_time::Asn1Time, years::Year) = Dates.adjust(asn1_time, Day(365 * years.value)) """ EVP_PKEY, EVP Public Key interface. """ mutable struct EvpPKey evp_pkey::Ptr{Cvoid} function EvpPKey(evp_pkey::Ptr{Cvoid})::EvpPKey evp_pkey = new(evp_pkey) finalizer(free, evp_pkey) return evp_pkey end function EvpPKey()::EvpPKey evp_pkey = ccall( (:EVP_PKEY_new, libcrypto), Ptr{Cvoid}, ()) if evp_pkey == C_NULL throw(OpenSSLError()) end return EvpPKey(evp_pkey) end function EvpPKey(rsa::RSA)::EvpPKey evp_pkey = EvpPKey() if ccall( (:EVP_PKEY_assign, libcrypto), Cint, (EvpPKey, Cint, RSA), evp_pkey, EVP_PKEY_RSA, rsa) != 1 throw(OpenSSLError()) end rsa.rsa = C_NULL return evp_pkey end function EvpPKey(dsa::DSA)::EvpPKey evp_pkey = EvpPKey() if ccall( (:EVP_PKEY_assign, libcrypto), Cint, (EvpPKey, Cint, DSA), evp_pkey, EVP_PKEY_DSA, dsa) != 1 throw(OpenSSLError()) end dsa.dsa = C_NULL return evp_pkey end """ Creates a EvpPKey from PEM string. """ function EvpPKey(in_string::AbstractString)::EvpPKey # Create a BIO and write the PEM string. bio = BIO(BIOMethodMemory()) unsafe_write(bio, pointer(in_string), length(in_string)) evp_pkey = ccall( (:PEM_read_bio_PrivateKey, libcrypto), Ptr{Cvoid}, (BIO, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), bio, C_NULL, C_NULL, C_NULL) if evp_pkey == C_NULL throw(OpenSSLError()) end free(bio) return EvpPKey(evp_pkey) end end function free(evp_pkey::EvpPKey) ccall( (:EVP_PKEY_free, libcrypto), Cvoid, (EvpPKey,), evp_pkey) evp_pkey.evp_pkey = C_NULL return nothing end function Base.:(==)(evp_pkey_1::EvpPKey, evp_pkey_2::EvpPKey) result = ccall( (:EVP_PKEY_cmp, libcrypto), Cint, (EvpPKey, EvpPKey), evp_pkey_1, evp_pkey_2) if result == -2 throw(OpenSSLError()) end return result == 1 end function Base.write(io::IO, evp_pkey::EvpPKey, evp_cipher::EvpCipher=EvpCipher(C_NULL)) bio = BIO(io) GC.@preserve io begin if ccall( (:PEM_write_bio_PrivateKey, libcrypto), Cint, (BIO, EvpPKey, EvpCipher, Ptr{Cvoid}, Cint, Cint, Ptr{Cvoid}), bio, evp_pkey, evp_cipher, C_NULL, 0, 0, C_NULL) != 1 throw(OpenSSLError()) end end end function get_key_type(evp_pkey::EvpPKey)::EvpPKeyType pkey_type = ccall( (EVP_PKEY_base_id, libcrypto), EvpPKeyType, (EvpPKey,), evp_pkey) return pkey_type end function Base.getproperty(evp_pkey::EvpPKey, name::Symbol) if name === :key_type return get_key_type(evp_pkey) else # fallback to getfield return getfield(evp_pkey, name) end end """ Stack Of. """ mutable struct StackOf{T} sk::Ptr{Cvoid} function StackOf{T}(sk::Ptr{Cvoid}) where {T} stack_of = new(sk) finalizer(free, stack_of) return stack_of end function StackOf{T}() where {T} sk = ccall( (:OPENSSL_sk_new_null, libcrypto), Ptr{Cvoid}, ()) if sk == C_NULL throw(OpenSSLError()) end return StackOf{T}(sk) end end function free(stack_of::StackOf{T}) where {T} ccall( (:OPENSSL_sk_free, libcrypto), Cvoid, (StackOf{T},), stack_of) stack_of.sk = C_NULL return nothing end function Base.push!(stack_of::StackOf{T}, element::T) where {T} count = ccall( (:OPENSSL_sk_push, libcrypto), Cint, (StackOf{T}, Ptr{Cvoid}), stack_of, up_ref(element)) if count == 0 throw(OpenSSLError()) end return count end function Base.pop!(stack_of::StackOf{T}) where {T} ptr = ccall( (:OPENSSL_sk_pop, libcrypto), Ptr{Cvoid}, (StackOf{T},), stack_of) return T(ptr) end function Base.length(stack_of::StackOf{T}) where {T} return ccall( (:OPENSSL_sk_num, libcrypto), Cint, (StackOf{T},), stack_of) end """ X509 Name. """ mutable struct X509Name x509_name::Ptr{Cvoid} function X509Name(x509_name::Ptr{Cvoid}) x509_name = new(x509_name) finalizer(free, x509_name) return x509_name end function X509Name() x509_name = ccall( (:X509_NAME_new, libcrypto), Ptr{Cvoid}, ()) if x509_name == C_NULL throw(OpenSSLError()) end x509_name = X509Name(x509_name) return x509_name end end function free(x509_name::X509Name) ccall( (:X509_NAME_free, libcrypto), Cvoid, (X509Name,), x509_name) x509_name.x509_name = C_NULL return nothing end function Base.:(==)(x509_name_1::X509Name, x509_name_2::X509Name) result = ccall( (:X509_NAME_cmp, libcrypto), Cint, (X509Name, X509Name), x509_name_1, x509_name_2) if result == -2 throw(OpenSSLError()) end return result == 0 end """ X509Name to string. """ function Base.String(x509_name::X509Name)::String name_ptr = ccall( (:X509_NAME_oneline, libcrypto), Cstring, (X509Name, Ptr{UInt8}, Cint), x509_name, C_NULL, 0) if name_ptr == C_NULL throw(OpenSSLError()) end str = unsafe_string(name_ptr) ccall( (:CRYPTO_free, libcrypto), Cvoid, (Cstring,), name_ptr) return str end function add_entry(x509_name::X509Name, field::String, value::String) if ccall( (:X509_NAME_add_entry_by_txt, libcrypto), Cint, (X509Name, Cstring, Cint, Cstring, Cint, Cint, Cint), x509_name, field, MBSTRING_ASC, value, -1, -1, 0) != 1 throw(OpenSSLError()) end return nothing end """ X509 Attribute. """ mutable struct X509Attribute x509_attr::Ptr{Cvoid} function X509Attribute(x509_attr::Ptr{Cvoid}) x509_attr = new(x509_attr) finalizer(free, x509_attr) return x509_attr end function X509Attribute() x509_attr = ccall( (:X509_ATTRIBUTE_new, libcrypto), Ptr{Cvoid}, ()) if x509_attr == C_NULL throw(OpenSSLError()) end x509_attr = X509Attribute(x509_attr) return x509_attr end end function free(x509_attr::X509Attribute) ccall( (:X509_ATTRIBUTE_free, libcrypto), Cvoid, (X509Attribute,), x509_attr) x509_attr.x509_attr = C_NULL return nothing end """ X509Attribute does not support up ref. Duplicate the attribute instead. """ function up_ref(x509_attr::X509Attribute)::Ptr{Cvoid} x509_attr = ccall( (:X509_ATTRIBUTE_dup, libcrypto), Ptr{Cvoid}, (X509Attribute,), x509_attr) if x509_attr == C_NULL throw(OpenSSLError()) end return x509_attr end """ X509_EXTENSION """ mutable struct X509Extension x509_ext::Ptr{Cvoid} function X509Extension(x509_ext::Ptr{Cvoid}) x509_ext = new(x509_ext) finalizer(free, x509_ext) return x509_ext end function X509Extension(name::String, value::String) x509_ext = ccall( (:X509V3_EXT_conf, libcrypto), Ptr{Cvoid}, (Ptr{Cvoid}, Ptr{Cvoid}, Cstring, Cstring), C_NULL, C_NULL, name, value) if x509_ext == C_NULL throw(OpenSSLError()) end return X509Extension(x509_ext) end end function free(x509_ext::X509Extension) ccall( (:X509_EXTENSION_free, libcrypto), Cvoid, (X509Extension,), x509_ext) x509_ext.x509_ext = C_NULL return nothing end """ X509Extension does not support up ref. Duplicate the extension instead. """ function up_ref(x509_ext::X509Extension)::Ptr{Cvoid} x509_ext = ccall( (:X509_EXTENSION_dup, libcrypto), Ptr{Cvoid}, (X509Extension,), x509_ext) if x509_ext == C_NULL throw(OpenSSLError()) end return x509_ext end """ X509 Certificate. """ mutable struct X509Certificate x509::Ptr{Cvoid} function X509Certificate() x509 = ccall( (:X509_new, libcrypto), Ptr{Cvoid}, ()) if x509 == C_NULL throw(OpenSSLError()) end return X509Certificate(x509) end function X509Certificate(x509::Ptr{Cvoid}) x509_cert = new(x509) finalizer(free, x509_cert) return x509_cert end """ Creates a X509 certificate from PEM string. """ function X509Certificate(in_string::AbstractString)::X509Certificate # Create a BIO and write the PEM string. bio = BIO(BIOMethodMemory()) unsafe_write(bio, pointer(in_string), length(in_string)) x509 = ccall( (:PEM_read_bio_X509, libcrypto), Ptr{Cvoid}, (BIO, Ptr{Cvoid}, Ptr{Cvoid}, Ptr{Cvoid}), bio, C_NULL, C_NULL, C_NULL) if x509 == C_NULL throw(OpenSSLError()) end free(bio) return X509Certificate(x509) end end function free(x509_cert::X509Certificate) ccall( (:X509_free, libcrypto), Cvoid, (X509Certificate,), x509_cert) x509_cert.x509 = C_NULL return nothing end function up_ref(x509_cert::X509Certificate)::Ptr{Cvoid} _ = ccall( (:X509_up_ref, libcrypto), Cint, (X509Certificate,), x509_cert) return x509_cert.x509 end function Base.:(==)(x509_cert_1::X509Certificate, x509_cert_2::X509Certificate) result = ccall( (:X509_cmp, libcrypto), Cint, (X509Certificate, X509Certificate), x509_cert_1, x509_cert_2) if result == -2 throw(OpenSSLError()) end return result == 0 end function Base.write(io::IO, x509_cert::X509Certificate) bio = BIO(io) GC.@preserve io begin if ccall( (:PEM_write_bio_X509, libcrypto), Cint, (BIO, X509Certificate), bio, x509_cert) != 1 throw(OpenSSLError()) end end end function sign_certificate(x509_cert::X509Certificate, evp_pkey::EvpPKey) evp_md = ccall( (:EVP_sha256, libcrypto), Ptr{Cvoid}, ()) if ccall( (:X509_sign, libcrypto), Cint, (X509Certificate, EvpPKey, Ptr{Cvoid}), x509_cert, evp_pkey, evp_md) == 0 throw(OpenSSLError()) end if ccall( (:X509_verify, libcrypto), Cint, (X509Certificate, EvpPKey), x509_cert, evp_pkey) != 1 throw(OpenSSLError()) end end function add_extension(x509_cert::X509Certificate, x509_ext::X509Extension) if ccall( (:X509_add_ext, libcrypto), Cint, (X509Certificate, X509Extension, Cint), x509_cert, x509_ext, -1) != 1 throw(OpenSSLError()) end end function get_subject_name(x509_cert::X509Certificate)::X509Name x509_name = ccall( (:X509_get_subject_name, libcrypto), Ptr{Cvoid}, (X509Certificate,), x509_cert) # Duplicate x509_name as it is an internal pointer and must not be freed. x509_name = ccall( (:X509_NAME_dup, libcrypto), Ptr{Cvoid}, (Ptr{Cvoid},), x509_name) if x509_name == C_NULL throw(OpenSSLError()) end return X509Name(x509_name) end function set_subject_name(x509_cert::X509Certificate, x509_name::X509Name) if ccall( (:X509_set_subject_name, libcrypto), Cint, (X509Certificate, X509Name), x509_cert, x509_name) != 1 throw(OpenSSLError()) end end function get_issuer_name(x509_cert::X509Certificate)::X509Name x509_name = ccall( (:X509_get_issuer_name, libcrypto), Ptr{Cvoid}, (X509Certificate,), x509_cert) # Duplicate x509_name as it is an internal pointer and must not be freed. x509_name = ccall( (:X509_NAME_dup, libcrypto), Ptr{Cvoid}, (Ptr{Cvoid},), x509_name) if x509_name == C_NULL throw(OpenSSLError()) end return X509Name(x509_name) end function set_issuer_name(x509_cert::X509Certificate, x509_name::X509Name) if ccall( (:X509_set_issuer_name, libcrypto), Cint, (X509Certificate, X509Name), x509_cert, x509_name) != 1 throw(OpenSSLError()) end end function get_time_not_before(x509_cert::X509Certificate)::Asn1Time asn1_time = ccall( (:X509_getm_notBefore, libcrypto), Ptr{Cvoid}, (X509Certificate,), x509_cert) if asn1_time == C_NULL throw(OpenSSLError()) end asn1_time = Asn1Time(asn1_time) return asn1_time end function set_time_not_before(x509_cert::X509Certificate, asn1_time::Asn1Time) if ccall( (:X509_set1_notBefore, libcrypto), Cint, (X509Certificate, Asn1Time), x509_cert, asn1_time) != 1 throw(OpenSSLError()) end end function get_time_not_after(x509_cert::X509Certificate)::Asn1Time asn1_time = ccall( (:X509_getm_notAfter, libcrypto), Ptr{Cvoid}, (X509Certificate,), x509_cert) if asn1_time == C_NULL throw(OpenSSLError()) end asn1_time = Asn1Time(asn1_time) return asn1_time end function set_time_not_after(x509_cert::X509Certificate, asn1_time::Asn1Time) if ccall( (:X509_set1_notAfter, libcrypto), Cint, (X509Certificate, Asn1Time), x509_cert, asn1_time) != 1 throw(OpenSSLError()) end end function get_version(x509_cert::X509Certificate)::Int version = ccall( (:X509_get_version, libcrypto), Clong, (X509Certificate,), x509_cert) return Int(version) end function set_version(x509_cert::X509Certificate, version::Int) if ccall( (:X509_set_version, libcrypto), Cint, (X509Certificate, Cint), x509_cert, version) != 1 throw(OpenSSLError()) end end function get_public_key(x509_cert::X509Certificate)::EvpPKey evp_pkey = ccall( (:X509_get_pubkey, libcrypto), Ptr{Cvoid}, (X509Certificate,), x509_cert) if evp_pkey == C_NULL throw(OpenSSLError()) end return EvpPKey(evp_pkey) end function set_public_key(x509_cert::X509Certificate, evp_pkey::EvpPKey) if ccall( (:X509_set_pubkey, libcrypto), Cint, (X509Certificate, EvpPKey), x509_cert, evp_pkey) != 1 throw(OpenSSLError()) end end function Base.getproperty(x509_cert::X509Certificate, name::Symbol) if name === :subject_name return get_subject_name(x509_cert) elseif name === :issuer_name return get_issuer_name(x509_cert) elseif name === :time_not_before return get_time_not_before(x509_cert) elseif name === :time_not_after return get_time_not_after(x509_cert) elseif name === :version return get_version(x509_cert) elseif name === :public_key return get_public_key(x509_cert) else # fallback to getfield return getfield(x509_cert, name) end end function Base.setproperty!(x509_cert::X509Certificate, name::Symbol, value) if name === :subject_name set_subject_name(x509_cert, value) elseif name === :issuer_name set_issuer_name(x509_cert, value) elseif name === :time_not_before set_time_not_before(x509_cert, value) elseif name === :time_not_after set_time_not_after(x509_cert, value) elseif name === :version set_version(x509_cert, value) elseif name === :public_key set_public_key(x509_cert, value) else # fallback to setfield setfield!(x509_cert, name, value) end end """ X509 Request. """ mutable struct X509Request x509_req::Ptr{Cvoid} function X509Request() x509_req = ccall( (:X509_REQ_new, libcrypto), Ptr{Cvoid}, ()) if x509_req == C_NULL throw(OpenSSLError()) end return X509Request(x509_req) end function X509Request(x509::Ptr{Cvoid}) x509_req = new(x509) finalizer(free, x509_req) return x509_req end end function free(x509_req::X509Request) ccall( (:X509_REQ_free, libcrypto), Cvoid, (X509Request,), x509_req) x509_req.x509_req = C_NULL return nothing end function Base.write(io::IO, x509_req::X509Request) bio = BIO(io) GC.@preserve io begin if ccall( (:PEM_write_bio_X509_REQ, libcrypto), Cint, (BIO, X509Request), bio, x509_req) != 1 throw(OpenSSLError()) end end end function add_extensions(x509_req::X509Request, x509_exts::StackOf{X509Extension}) if ccall( (:X509_REQ_add_extensions, libcrypto), Cint, (X509Request, StackOf{X509Extension}), x509_req, x509_exts) != 1 throw(OpenSSLError()) end end function sign_request(x509_req::X509Request, evp_pkey::EvpPKey) evp_md = ccall( (:EVP_sha256, libcrypto), Ptr{Cvoid}, ()) if ccall( (:X509_REQ_set_pubkey, libcrypto), Cint, (X509Request, EvpPKey), x509_req, evp_pkey) != 1 throw(OpenSSLError()) end if ccall( (:X509_REQ_sign, libcrypto), Cint, (X509Request, EvpPKey, Ptr{Cvoid}), x509_req, evp_pkey, evp_md) == 0 throw(OpenSSLError()) end if ccall( (:X509_REQ_verify, libcrypto), Cint, (X509Request, EvpPKey), x509_req, evp_pkey) != 1 throw(OpenSSLError()) end end function get_subject_name(x509_req::X509Request)::X509Name x509_name = ccall( (:X509_REQ_get_subject_name, libcrypto), Ptr{Cvoid}, (X509Request,), x509_req) # Duplicate x509_name as it is an internal pointer and must not be freed. x509_name = ccall( (:X509_NAME_dup, libcrypto), Ptr{Cvoid}, (Ptr{Cvoid},), x509_name) if x509_name == C_NULL throw(OpenSSLError()) end return X509Name(x509_name) end function set_subject_name(x509_req::X509Request, x509_name::X509Name) if ccall( (:X509_REQ_set_subject_name, libcrypto), Cint, (X509Request, X509Name), x509_req, x509_name) != 1 throw(OpenSSLError()) end end function get_public_key(x509_req::X509Request)::EvpPKey evp_pkey = ccall( (:X509_REQ_get_pubkey, libcrypto), Ptr{Cvoid}, (X509Request,), x509_req) if evp_pkey == C_NULL throw(OpenSSLError()) end return EvpPKey(evp_pkey) end function set_public_key(x509_req::X509Request, evp_pkey::EvpPKey) if ccall( (:X509_REQ_set_pubkey, libcrypto), Cint, (X509Request, EvpPKey), x509_req, evp_pkey) != 1 throw(OpenSSLError()) end end function get_extensions(x509_req::X509Request) sk = ccall( (:X509_REQ_get_extensions, libcrypto), Ptr{Cvoid}, (X509Request,), x509_req) if sk == C_NULL throw(OpenSSLError()) end return StackOf{X509Extension}(sk) end function Base.getproperty(x509_req::X509Request, name::Symbol) if name === :subject_name return get_subject_name(x509_req) elseif name === :public_key return get_public_key(x509_req) elseif name === :extensions return get_extensions(x509_req) else # fallback to getfield return getfield(x509_req, name) end end function Base.setproperty!(x509_req::X509Request, name::Symbol, value) if name === :subject_name set_subject_name(x509_req, value) elseif name === :public_key set_public_key(x509_req, value) else # fallback to setfield setfield!(x509_req, name, value) end end """ X509 Store. """ mutable struct X509Store x509_store::Ptr{Cvoid} function X509Store(x509_store::Ptr{Cvoid}) x509_store = new(x509_store) finalizer(free, x509_store) return x509_store end function X509Store() x509_store = ccall( (:X509_STORE_new, libcrypto), Ptr{Cvoid}, ()) if x509_store == C_NULL throw(OpenSSLError()) end return X509Store(x509_store) end end function free(x509_store::X509Store) ccall( (:X509_STORE_free, libcrypto), Cvoid, (X509Store,), x509_store) x509_store.x509_store = C_NULL return nothing end function add_cert(x509_store::X509Store, x509_cert::X509Certificate) if ccall( (:X509_STORE_add_cert, libcrypto), Cint, (X509Store, X509Certificate), x509_store, x509_cert) != 1 throw(OpenSSLError()) end end """ PKCS12 - Public Key Cryptography Standard #12 """ mutable struct P12Object pkcs12::Ptr{Cvoid} function P12Object() pkcs12 = ccall( (:PKCS12_new, libcrypto), Ptr{Cvoid}, ()) if pkcs12 == C_NULL throw(OpenSSLError()) end return P12Object(pkcs12) end function P12Object(pkcs12::Ptr{Cvoid}) p12_object = new(pkcs12) finalizer(free, p12_object) return p12_object end function P12Object(evp_pkey::EvpPKey, x509_cert::X509Certificate) pkcs12 = ccall( (:PKCS12_create, libcrypto), Ptr{Cvoid}, (Cstring, Cstring, EvpPKey, X509Certificate, Ptr{Cvoid}, Cint, Cint, Cint, Cint, Cint), C_NULL, C_NULL, evp_pkey, x509_cert, C_NULL, 0, 0, 0, 0, 0) if pkcs12 == C_NULL throw(OpenSSLError()) end return P12Object(pkcs12) end end function free(p12_object::P12Object) ccall( (:PKCS12_free, libcrypto), Cvoid, (P12Object,), p12_object) p12_object.pkcs12 = C_NULL return nothing end function Base.write(io::IO, p12_object::P12Object) bio = BIO(io) GC.@preserve io begin if ccall( (:i2d_PKCS12_bio, libcrypto), Cint, (BIO, P12Object), bio, p12_object) != 1 throw(OpenSSLError()) end end end function unpack(p12_object::P12Object) evp_pkey::EvpPKey = EvpPKey(C_NULL) x509_cert::X509Certificate = X509Certificate(C_NULL) x509_ca_stack::StackOf{X509Certificate} = StackOf{X509Certificate}(C_NULL) if ccall( (:PKCS12_parse, libcrypto), Cint, (P12Object, Cstring, Ref{EvpPKey}, Ref{X509Certificate}, Ref{StackOf{X509Certificate}}), p12_object, C_NULL, evp_pkey, x509_cert, x509_ca_stack) != 1 throw(OpenSSLError()) end return evp_pkey, x509_cert, x509_ca_stack end """ Crypto Init. Initialize OpenSSL library. """ mutable struct OpenSSLInit function OpenSSLInit() opts = UInt64( OPENSSL_INIT_LOAD_CRYPTO_STRINGS | OPENSSL_INIT_ADD_ALL_CIPHERS | OPENSSL_INIT_ADD_ALL_DIGESTS | OPENSSL_INIT_ASYNC) if ccall( (:OPENSSL_init_crypto, libcrypto), Cint, (UInt64, Ptr{Cvoid}), opts, C_NULL) != 1 throw(OpenSSLError()) end if ccall( (:OPENSSL_init_ssl, libssl), Cint, (UInt64, Ptr{Cvoid}), OPENSSL_INIT_LOAD_SSL_STRINGS, C_NULL) != 1 throw(OpenSSLError()) end return new() end end function Base.String(big_num::BigNum) bio = BIO(BIOMethodMemory()) write(bio, "0x") if ccall( (:BN_print, libcrypto), Cint, (BIO, BigNum), bio, big_num) != 1 throw(OpenSSLError()) end result = String(bio_get_mem_data(bio)) free(bio) return result end function Base.String(asn1_time::Asn1Time) if asn1_time.asn1_time == C_NULL return "C_NULL" end bio = BIO(BIOMethodMemory()) if ccall( (:ASN1_TIME_print, libcrypto), Cint, (BIO, Asn1Time), bio, asn1_time) != 1 throw(OpenSSLError()) end result = String(bio_get_mem_data(bio)) free(bio) return result end function Base.String(x509_cert::X509Certificate) io = IOBuffer() println(io, """ subject_name: $(x509_cert.subject_name) issuer_name: $(x509_cert.issuer_name) time_not_before: $(x509_cert.time_not_before) time_not_after: $(x509_cert.time_not_after)""") return String(take!(io)) end function Base.String(x509_ext::X509Extension) if x509_ext.x509_ext == C_NULL return "C_NULL" end io = IOBuffer() bio = BIO(BIOMethodMemory()) _ = ccall( (:X509V3_EXT_print, libcrypto), Cint, (BIO, X509Extension, Cint, Ptr{Cvoid}), bio, x509_ext, 0, C_NULL) update_tls_error_state() result = String(bio_get_mem_data(bio)) free(bio) return result end function Base.String(x509_cert::X509Request) io = IOBuffer() println(io, """subject_name: $(x509_cert.subject_name)""") return String(take!(io)) end function Base.String(evp_pkey::EvpPKey) io = IOBuffer() bio = BIO(BIOMethodMemory()) _ = ccall( (:EVP_PKEY_print_public, libcrypto), Cint, (BIO, EvpPKey, Cint, Ptr{Cvoid}), bio, evp_pkey, 0, C_NULL) _ = ccall( (:EVP_PKEY_print_private, libcrypto), Cint, (BIO, EvpPKey, Cint, Ptr{Cvoid}), bio, evp_pkey, 0, C_NULL) _ = ccall( (:EVP_PKEY_print_params, libcrypto), Cint, (BIO, EvpPKey, Cint, Ptr{Cvoid}), bio, evp_pkey, 0, C_NULL) update_tls_error_state() result = String(bio_get_mem_data(bio)) free(bio) return result end Base.show(io::IO, big_num::BigNum) = write(io, String(big_num)) Base.show(io::IO, asn1_time::Asn1Time) = write(io, String(asn1_time)) Base.show(io::IO, x509_name::X509Name) = write(io, String(x509_name)) Base.show(io::IO, x509_cert::X509Certificate) = write(io, String(x509_cert)) Base.show(io::IO, x509_ext::X509Extension) = write(io, String(x509_ext)) Base.show(io::IO, x509_req::X509Request) = write(io, String(x509_req)) Base.show(io::IO, evp_pkey::EvpPKey) = write(io, String(evp_pkey)) """ Error handling. """ function get_error()::String # Create memory BIO bio = BIO(BIOMethodMemory()) local error_msg::String # Check existing error messages stored in task TLS. if haskey(task_local_storage(), :openssl_err) # Copy existing error from task TLS. tls_msg = task_local_storage(:openssl_err) delete!(task_local_storage(), :openssl_err) # Clear the error queue, print the error messages to the memory BIO. ccall( (:ERR_print_errors, libcrypto), Cvoid, (BIO,), bio) bio_msg = String(bio_get_mem_data(bio)) error_msg = "$(tls_msg) : $(bio_msg)" else # Clear the error queue, print the error messages to the memory BIO. ccall( (:ERR_print_errors, libcrypto), Cvoid, (BIO,), bio) error_msg = String(bio_get_mem_data(bio)) end # Read the formatted error messages from the memory BIO. # Ensure the queue is clear (if ERR_print_errors fails). clear_errors!() # Free bio. free(bio) return error_msg end function clear_errors!() ccall( (:ERR_clear_error, libcrypto), Cvoid, ()) end function OpenSSLError(ret::Integer) ptr = ccall( (:ERR_error_string, libcrypto), Ptr{UInt8}, (Culong, Ptr{UInt8}), Base.bitcast(Culong, Clong(ret)), C_NULL ) return OpenSSLError(unsafe_string(ptr)) end """ Copy and clear OpenSSL error queue to the task TLS . """ function update_tls_error_state() # Check if there are errors in OpenSSL error queue. if ccall( (:ERR_peek_error, libcrypto), Culong, ()) != 0 # Clear OpenSSL queue and store the errors in the Task TLS. error_str = get_error() task_local_storage(:openssl_err, error_str) end end include("ssl.jl") const OPEN_SSL_INIT = Ref{OpenSSLInit}() const BIO_STREAM_CALLBACKS = Ref{BIOStreamCallbacks}() const BIO_STREAM_METHOD = Ref{BIOMethod}() """ Initialize module. """ function __init__() OPEN_SSL_INIT.x = OpenSSLInit() BIO_STREAM_CALLBACKS.x = BIOStreamCallbacks() BIO_STREAM_METHOD.x = BIOMethod("BIO_STREAM_METHOD") # Set the openssl provider search path if version_number() ≥ v"3" ossl_provider_set_default_search_path() end return end end # OpenSSL module ,/opt/julia/packages/OpenSSL/hXs2T/src/ssl.jlS""" BIO Stream callbacks. """ """ Called to initialize new BIO Stream object. """ on_bio_stream_create(bio::BIO) = Cint(1) on_bio_stream_destroy(bio::BIO)::Cint = Cint(0) function bio_get_data(bio::BIO) data = ccall( (:BIO_get_data, libcrypto), Ptr{Cvoid}, (BIO,), bio) return unsafe_pointer_to_objref(data) end const BIO_FLAGS_SHOULD_RETRY = 0x08 const BIO_FLAGS_READ = 0x01 const BIO_FLAGS_WRITE = 0x02 const BIO_FLAGS_IO_SPECIAL = 0x04 function bio_set_flags(bio::BIO, flags) return ccall( (:BIO_set_flags, libcrypto), Cint, (BIO, Cint), bio, flags) end bio_set_read_retry(bio::BIO) = bio_set_flags(bio, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY) bio_clear_flags(bio::BIO) = bio_set_flags(bio, 0x00) function on_bio_stream_read(bio::BIO, out::Ptr{Cchar}, outlen::Cint) try bio_clear_flags(bio) io = bio_get_data(bio)::TCPSocket n = bytesavailable(io) if n == 0 bio_set_read_retry(bio) return Cint(0) end unsafe_read(io, out, min(UInt(n), outlen)) return Cint(min(n, outlen)) catch e # we don't want to throw a Julia exception from a C callback return Cint(0) end end function on_bio_stream_write(bio::BIO, in::Ptr{Cchar}, inlen::Cint)::Cint try io = bio_get_data(bio)::TCPSocket written = unsafe_write(io, in, inlen) return Cint(written) catch e # we don't want to throw a Julia exception from a C callback return Cint(0) end end on_bio_stream_puts(bio::BIO, in::Ptr{Cchar})::Cint = Cint(0) on_bio_stream_ctrl(bio::BIO, cmd::BIOCtrl, num::Clong, ptr::Ptr{Cvoid}) = Clong(1) """ BIO Stream callbacks. """ struct BIOStreamCallbacks on_bio_create_ptr::Ptr{Nothing} on_bio_destroy_ptr::Ptr{Nothing} on_bio_read_ptr::Ptr{Nothing} on_bio_write_ptr::Ptr{Nothing} on_bio_puts_ptr::Ptr{Nothing} on_bio_ctrl_ptr::Ptr{Nothing} function BIOStreamCallbacks() on_bio_create_ptr = @cfunction on_bio_stream_create Cint (BIO,) on_bio_destroy_ptr = @cfunction on_bio_stream_destroy Cint (BIO,) on_bio_read_ptr = @cfunction on_bio_stream_read Cint (BIO, Ptr{Cchar}, Cint) on_bio_write_ptr = @cfunction on_bio_stream_write Cint (BIO, Ptr{Cchar}, Cint) on_bio_puts_ptr = @cfunction on_bio_stream_puts Cint (BIO, Ptr{Cchar}) on_bio_ctrl_ptr = @cfunction on_bio_stream_ctrl Clong (BIO, BIOCtrl, Clong, Ptr{Cvoid}) return new( on_bio_create_ptr, on_bio_destroy_ptr, on_bio_read_ptr, on_bio_write_ptr, on_bio_puts_ptr, on_bio_ctrl_ptr) end end """ SSLMethod. TLSClientMethod. """ mutable struct SSLMethod ssl_method::Ptr{Cvoid} end function TLSClientMethod() ssl_method = ccall( (:TLS_client_method, libssl), Ptr{Cvoid}, ()) if ssl_method == C_NULL throw(OpenSSLError()) end return SSLMethod(ssl_method) end function TLSServerMethod() ssl_method = ccall( (:TLS_server_method, libssl), Ptr{Cvoid}, ()) if ssl_method == C_NULL throw(OpenSSLError()) end return SSLMethod(ssl_method) end const SSL_MODE_AUTO_RETRY = 0x00000004 """ This is the global context structure which is created by a server or client once per program life-time and which holds mainly default values for the SSL structures which are later created for the connections. """ mutable struct SSLContext ssl_ctx::Ptr{Cvoid} function SSLContext(ssl_method::SSLMethod, verify_file::String=MozillaCACerts_jll.cacert) ssl_ctx = ccall( (:SSL_CTX_new, libssl), Ptr{Cvoid}, (SSLMethod,), ssl_method) if ssl_ctx == C_NULL throw(OpenSSLError()) end ssl_context = new(ssl_ctx) finalizer(free, ssl_context) # set auto retry mode ccall( (:SSL_CTX_ctrl, libssl), Cint, (SSLContext, Cint, Clong, Ptr{Cvoid}), ssl_context, 33, SSL_MODE_AUTO_RETRY, C_NULL) if !isempty(verify_file) @assert ccall( (:SSL_CTX_load_verify_locations, libssl), Cint, (SSLContext, Ptr{Cchar}, Ptr{Cchar}), ssl_context, verify_file, C_NULL) == 1 end return ssl_context end end function ca_chain!(ssl_context::SSLContext, cacert::String) ccall( (:SSL_CTX_load_verify_locations, libssl), Cint, (SSLContext, Ptr{Cchar}, Ptr{Cchar}), ssl_context, cacert, C_NULL) end function free(ssl_context::SSLContext) ssl_context.ssl_ctx == C_NULL && return ccall( (:SSL_CTX_free, libssl), Cvoid, (SSLContext,), ssl_context) ssl_context.ssl_ctx = C_NULL return end """ Sets the (external) protocol behaviour of the SSL library. """ function ssl_set_options(ssl_context::SSLContext, options::SSLOptions)::SSLOptions return ccall( (:SSL_CTX_set_options, libssl), SSLOptions, (SSLContext, SSLOptions), ssl_context, options) end """ Configures TLS ALPN (Application-Layer Protocol Negotiation). """ function ssl_set_alpn(ssl_context::SSLContext, protocol_list::String) if ccall( (:SSL_CTX_set_alpn_protos, libssl), Cint, (SSLContext, Ptr{UInt8}, UInt32), ssl_context, pointer(protocol_list), length(protocol_list)) != 0 throw(OpenSSLError()) end end """ Sets minimum supported protocol version for SSLContext. """ function ssl_set_min_protocol_version(ssl_context::SSLContext, version::TlsVersion) if ccall( (:SSL_CTX_ctrl, libssl), Cint, (SSLContext, SSLControlCommand, TlsVersion, Ptr{Cvoid}), ssl_context, SSL_CTRL_SET_MIN_PROTO_VERSION, version, C_NULL) != 1 throw(OpenSSLError()) end end # TODO # int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str); """ Configures available TLSv1.3 cipher suites. """ function ssl_set_ciphersuites(ssl_context::SSLContext, cipher_suites::String) if ccall( (:SSL_CTX_set_ciphersuites, libssl), Cint, (SSLContext, Cstring), ssl_context, cipher_suites) != 1 throw(OpenSSLError()) end end function ssl_use_certificate(ssl_context::SSLContext, x509_cert::X509Certificate) if ccall( (:SSL_CTX_use_certificate, libssl), Cint, (SSLContext, X509Certificate), ssl_context, x509_cert) != 1 throw(OpenSSLError()) end end function ssl_use_private_key(ssl_context::SSLContext, evp_pkey::EvpPKey) if ccall( (:SSL_CTX_use_PrivateKey, libssl), Cint, (SSLContext, EvpPKey), ssl_context, evp_pkey) != 1 throw(OpenSSLError()) end end """ SSL structure for a connection. """ mutable struct SSL ssl::Ptr{Cvoid} function SSL(ssl_context::SSLContext, read_bio::BIO, write_bio::BIO)::SSL ssl = ccall( (:SSL_new, libssl), Ptr{Cvoid}, (SSLContext,), ssl_context) if ssl == C_NULL throw(OpenSSLError()) end ssl = new(ssl) ccall( (:SSL_set_bio, libssl), Cvoid, (SSL, BIO, BIO), ssl, read_bio, write_bio) return ssl end end function free(ssl::SSL) ssl.ssl == C_NULL && return ccall( (:SSL_free, libssl), Cvoid, (SSL,), ssl) ssl.ssl = C_NULL return end function ssl_set_host(ssl::SSL, host) if (ret = ccall( (:SSL_set1_host, libssl), Cint, (SSL, Cstring), ssl, host)) != 1 throw(OpenSSLError(ret)) end end function ssl_connect(ssl::SSL) return ccall( (:SSL_connect, libssl), Cint, (SSL,), ssl) end function ssl_accept(ssl::SSL) if (ret = ccall( (:SSL_accept, libssl), Cint, (SSL,), ssl)) != 1 throw(OpenSSLError(ret)) end ccall( (:SSL_set_read_ahead, libssl), Cvoid, (SSL, Cint), ssl, Int32(1)) return nothing end """ Shut down a TLS/SSL connection. """ function ssl_disconnect(ssl::SSL) ccall( (:SSL_shutdown, libssl), Cint, (SSL,), ssl) return nothing end function get_error(ssl::SSL, ret::Cint)::SSLErrorCode return ccall( (:SSL_get_error, libssl), SSLErrorCode, (SSL, Cint), ssl, ret) end """ SSLStream. """ mutable struct SSLStream <: IO ssl::SSL ssl_context::SSLContext rbio::BIO wbio::BIO io::TCPSocket # used in `eof` where we want the call to `eof` on the underlying # socket and the SSL_peek call that processes bytes to be seen # as one "operation" eoflock::ReentrantLock # this lock guards operations accessing our .ssl object and after acquiring # the lock, *MUST* check if .closed is true before proceeding # also this guards against 2 threads trying to # call `read` or `write` at the same time as per the thread in # https://mailing.openssl.users.narkive.com/HeNGlNAJ/openssl-and-multithreaded-programs lock::ReentrantLock readbytes::Base.RefValue{Csize_t} writebytes::Base.RefValue{Csize_t} peekbuf::Base.RefValue{UInt8} peekbytes::Base.RefValue{Csize_t} closed::Bool function SSLStream(ssl_context::SSLContext, io::TCPSocket) # Create a read and write BIOs. bio_read::BIO = BIO(io; finalize=false) bio_write::BIO = BIO(io; finalize=false) ssl = SSL(ssl_context, bio_read, bio_write) return new(ssl, ssl_context, bio_read, bio_write, io, ReentrantLock(), ReentrantLock(), Ref{Csize_t}(0), Ref{Csize_t}(0), Ref{UInt8}(0x00), Ref{Csize_t}(0), false) end end SSLStream(tcp::TCPSocket) = SSLStream(SSLContext(OpenSSL.TLSClientMethod()), tcp) # backwards compat Base.getproperty(ssl::SSLStream, nm::Symbol) = nm === :bio_read_stream ? ssl : getfield(ssl, nm) Base.isreadable(ssl::SSLStream)::Bool = isopen(ssl) && isreadable(ssl.io) Base.isopen(ssl::SSLStream)::Bool = Base.@lock(ssl.lock, !ssl.closed) Base.iswritable(ssl::SSLStream)::Bool = isopen(ssl) && isopen(ssl.io) @noinline throwio(op) = throw(Base.IOError("$op requires ssl to be open", 0)) # this is a macro, but should be a function, but closures are stupid slow # we use this to standardize the error handling for all of the SSL_*_ex functions macro geterror(ssl, op, expr) esc(quote # lock our SSLStream while we clear errors # make a ccall, then check the error queue Base.@lock ssl.lock begin # check that SSL is still open before ccall $ssl.closed && throwio($op) # clear the current error queue before openssl ccall clear_errors!() # do the ccall _ret = $expr # we want to return one of our SSL return codes, regardless of error # SSL_peek_ex, SSL_write_ex, SSL_connect, and SSL_read_ex all return 1 on success if _ret == 1 ret = SSL_ERROR_NONE else err = get_error($ssl.ssl, _ret) if err == SSL_ERROR_ZERO_RETURN # the peer sent a close_notify, so no more reading is possible close($ssl, false) throw(Base.IOError("unexpected EOF", 0)) elseif err == SSL_ERROR_NONE ret = SSL_ERROR_NONE elseif err == SSL_ERROR_WANT_READ # we need to read more data from the underlying socket ret = SSL_ERROR_WANT_READ elseif err == SSL_ERROR_WANT_WRITE # we need to write more data to the underlying socket # we don't expect to ever see this since we set up our SSL # to do auto TLS (re)negotiation ret = SSL_ERROR_WANT_WRITE else # this is usually some other kind of error, like a protocol error # or OS-level IO error, just close the SSL connection and throw # notably, the openssl docs say we should *not* call ssl_disconnect # in this case, hence the `false` arg to close close($ssl, false) throw(Base.IOError(OpenSSLError(err).msg, 0)) end end ret end end) end function Base.unsafe_write(ssl::SSLStream, in_buffer::Ptr{UInt8}, in_length::UInt) nwritten = 0 while nwritten < in_length ret = @geterror ssl :unsafe_write ccall( (:SSL_write_ex, libssl), Cint, (SSL, Ptr{Cvoid}, Cint, Ptr{Csize_t}), ssl.ssl, in_buffer, in_length, ssl.writebytes ) if ret == SSL_ERROR_NONE nwritten += ssl.writebytes[] elseif ret == SSL_ERROR_WANT_WRITE flush(ssl.io) elseif ret == SSL_ERROR_WANT_READ # this means write is waiting for more data from the underlying socket # so call eof on the socket to wait for more bytes to come in eof(ssl.io) && throw(EOFError()) end end return Base.bitcast(Int, in_length) end function Sockets.connect(ssl::SSLStream; require_ssl_verification::Bool=true) while true ret = @geterror ssl :connect ssl_connect(ssl.ssl) if ret == SSL_ERROR_NONE break elseif ret == SSL_ERROR_WANT_READ # this means connect is waiting for more data from the underlying socket # so call eof on the socket to wait for more bytes to come in eof(ssl.io) && throw(EOFError()) else throw(Base.IOError(OpenSSLError(ret).msg, 0)) end end # Check the certificate. if require_ssl_verification Base.@lock ssl.lock begin ssl.closed && throwio(:verify_result) if (ret = ccall( (:SSL_get_verify_result, libssl), Cint, (SSL,), ssl.ssl)) != 0 throw(OpenSSLError(unsafe_string(ccall( (:X509_verify_cert_error_string, libcrypto), Ptr{UInt8}, (Cint,), ret)))) end end # get peer certificate cert = get_peer_certificate(ssl) cert === nothing && throw(OpenSSLError("No peer certificate")) end # set read ahead; this is a recommended optimization when we can guarantee # that an SSL connection will only ever be read from sequentially, which we do # by not doing any internal buffering Base.@lock ssl.lock begin ssl.closed && throwio(:read_ahead) ccall( (:SSL_set_read_ahead, libssl), Cvoid, (SSL, Cint), ssl.ssl, Cint(1)) end return end const SSL_CTRL_SET_TLSEXT_HOSTNAME = 55 const TLSEXT_NAMETYPE_host_name = 0 function hostname!(ssl::SSLStream, host) Base.@lock ssl.lock begin ssl.closed && throwio(:hostname) if (ret = ccall( (:SSL_ctrl, libssl), Cint, (SSL, Cint, Clong, Cstring), ssl.ssl, SSL_CTRL_SET_TLSEXT_HOSTNAME, TLSEXT_NAMETYPE_host_name, host)) != 1 throw(OpenSSLError(get_error())) end ssl_set_host(ssl.ssl, host) end end function Sockets.accept(ssl::SSLStream) ssl_accept(ssl.ssl) end """ Read from the SSL stream. """ function Base.unsafe_read(ssl::SSLStream, buf::Ptr{UInt8}, nbytes::UInt) nread = 0 readbytes = ssl.readbytes while nread < nbytes ret = @geterror ssl :unsafe_read ccall( (:SSL_read_ex, libssl), Cint, (SSL, Ptr{UInt8}, Csize_t, Ptr{Csize_t}), ssl.ssl, buf + nread, nbytes - nread, readbytes ) if ret == SSL_ERROR_NONE nread += Base.bitcast(Int, readbytes[]) elseif ret == SSL_ERROR_WANT_READ # this means write is waiting for more data from the underlying socket # so call eof on the socket to wait for more bytes to come in eof(ssl.io) && throw(EOFError()) elseif ret == SSL_ERROR_WANT_WRITE flush(ssl.io) end end return nread end function Base.readavailable(ssl::SSLStream) N = bytesavailable(ssl) buf = Vector{UInt8}(undef, N) n = GC.@preserve buf unsafe_read(ssl, pointer(buf), N) return resize!(buf, n) end # returns the # of bytes that can be read immediately via unsafe_read # i.e. # of processed, decrypted bytes available function Base.bytesavailable(ssl::SSLStream) Base.@lock ssl.lock begin ssl.closed && return 0 return Int(ccall( (:SSL_pending, libssl), Cint, (SSL,), ssl.ssl)) end end # returns whether there are _any_ bytes buffered, processed # or unprocessed, in the SSL stream function haspending(ssl::SSLStream) Base.@lock ssl.lock begin ssl.closed && return false return 1 == ccall( (:SSL_has_pending, libssl), Cint, (SSL,), ssl.ssl) end end function Base.eof(ssl::SSLStream)::Bool bytesavailable(ssl) > 0 && return false while isopen(ssl) # note that care needs to be taken here to avoid a potential bad # race condition; for SSLStream, we have to manage the state of # the underlying socket having available bytes *and* whether they've # been processed in the ssl layer, so we want to treat the receiving and processing # of bytes as a single operation; in other words, bytesavailable returns # > 0 when bytes have been received *and* processed and we don't want # racing tasks to get stuck in between. We also don't really care whether # tasks are blocked calling eof on the socket or waiting on eoflock, so # we avoid the races and keep things orderly by only allowing one task # to make the eof call and kick off byte processing at a time. Base.@lock ssl.eoflock begin # check condition now that we have eoflock since another task may have # succeeded in getting bytes processed isopen(ssl) || return true bytesavailable(ssl) > 0 && return false # no processed bytes available, check if there are unprocessed bytes if !haspending(ssl) # no unprocessed bytes, call eof to get more unprocessed if eof(ssl.io) && !haspending(ssl) # if eof and there are no pending, then we are eof return true end end # at this point, we know there are at least unprocessed bytes # buffered, so we call SSL_peek to get the next record processed, # which still might not result in bytesavailable > 0 ret = @geterror ssl :peek ccall( (:SSL_peek_ex, libssl), Cint, (SSL, Ptr{UInt8}, Cint, Ptr{Csize_t}), ssl.ssl, ssl.peekbuf, 1, ssl.peekbytes ) if ret == SSL_ERROR_NONE return false elseif ret == SSL_ERROR_WANT_WRITE flush(ssl.io) elseif ret == SSL_ERROR_WANT_READ # if we get WANT_READ back, that means there were pending bytes # to be processed, but not a full record, so we need to wait # for additional bytes to come in before we can process eof(ssl.io) end end end bytesavailable(ssl) > 0 && return false return !isopen(ssl) end """ Close SSL stream. """ function Base.close(ssl::SSLStream, shutdown::Bool=true) close_socket = false Base.@lock ssl.lock begin ssl.closed && return ssl.closed = true close_socket = true # Ignore the disconnect result. shutdown && ssl_disconnect(ssl.ssl) free(ssl.ssl) end if close_socket # close underlying io; because closing a TCPSocket may block # we do it outside holding the ssl.lock try Base.close(ssl.io) catch e e isa Base.IOError || rethrow() end end return end """ Gets the X509 certificate of the peer. """ function get_peer_certificate(ssl::SSLStream)::Option{X509Certificate} Base.@lock ssl.lock begin ssl.closed && throwio(:get_peer_certificate) x509 = ccall( (SSL_get_peer_certificate, libssl), Ptr{Cvoid}, (SSL,), ssl.ssl) if x509 != C_NULL return X509Certificate(x509) else return nothing end end end ~R`