diff --git a/src/OpenSSL.jl b/src/OpenSSL.jl index 91176a3..b74229f 100644 --- a/src/OpenSSL.jl +++ b/src/OpenSSL.jl @@ -210,6 +210,18 @@ const BIO_TYPE_SOURCE_SINK = 0x0400 # BIO_TYPE_START = 128) +""" + These are used in the following macros and are passed to BIO_ctrl(). +""" +#@enum(BIOFlags::Cint, +# +#); +const BIO_FLAGS_SHOULD_RETRY = 0x08 +const BIO_FLAGS_READ = 0x01 +const BIO_FLAGS_WRITE = 0x02 +const BIO_FLAGS_IO_SPECIAL = 0x04 + + # Some values are reserved until OpenSSL 3.0.0 because they were previously # included in SSL_OP_ALL in a 1.1.x release. @bitflag SSLOptions::Culong begin @@ -1407,85 +1419,6 @@ 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 """ @@ -1540,61 +1473,6 @@ mutable struct BIO 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. """ @@ -1674,7 +1552,6 @@ end """ BIO write. """ -## throw error here function Base.unsafe_write(bio::BIO, out_buffer::Ptr{UInt8}, out_length::Int) result = ccall( (:BIO_write, libcrypto), @@ -1690,6 +1567,296 @@ end Base.write(bio::BIO, out_data) = return unsafe_write(bio, pointer(out_data), length(out_data)) +""" + BIOStream. + BIO with IO. +""" +mutable struct BIOStream{T<:IO} + bio::Ptr{Cvoid} + + get_bio_stream_method(::T) where {T<:IO} = BIO_STREAM_METHOD_IO.x + + get_bio_stream_method(::TCPSocket) = BIO_STREAM_METHOD_TCPSOCKET.x + + """ + Creates a BIOStream object using IO stream method. + The BIOStream object is not registered with the finalizer. + """ + function BIOStream(io::T; finalize::Bool=true) where {T<:IO} + bio = ccall( + (:BIO_new, libcrypto), + Ptr{Cvoid}, + (BIOMethod,), + get_bio_stream_method(io)) + if bio == C_NULL + throw(OpenSSLError()) + end + + bio_stream = new{T}(bio) + finalize && finalizer(free, bio_stream) + + # note that `io` 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, + (BIOStream{T}, Ptr{Cvoid}), + bio_stream, + pointer_from_objref(io)) + + # Set BIO as non-blocking + ccall( + (:BIO_ctrl, libcrypto), + Cint, + (BIOStream{T}, Cint, Cint, Ptr{Cvoid}), + bio_stream, + 102, + 1, + C_NULL) + + # Mark BIO as initalized. + ccall( + (:BIO_set_init, libcrypto), + Cvoid, + (BIOStream{T}, Cint), + bio_stream, + 1) + + ccall( + (:BIO_set_shutdown, libcrypto), + Cvoid, + (BIOStream{T}, Cint), + bio_stream, + 0) + + return bio_stream + end +end + +function free(bio_stream::BIOStream{T}) where {T<:IO} + ccall( + (:BIO_free, libcrypto), + Cvoid, + (BIOStream{T},), + bio_stream) + + bio_stream.bio = C_NULL + return nothing +end + +function bio_stream_get_io(bio_stream::BIOStream{T})::T where {T<:IO} + data = ccall( + (:BIO_get_data, libcrypto), + Ptr{Cvoid}, + (BIOStream{T},), + bio_stream) + return unsafe_pointer_to_objref(data)::T +end + +""" + BIO write. +""" +function Base.unsafe_write(bio_stream::BIOStream{T}, out_buffer::Ptr{UInt8}, out_length::Int) where {T<:IO} + result = ccall( + (:BIO_write, libcrypto), + Cint, + (BIOStream{T}, Ptr{Cvoid}, Cint), + bio_stream, + out_buffer, + out_length) + if result < 0 + throw(OpenSSLError()) + end +end + +Base.write(bio_stream::BIOStream{T}, out_data) where {T<:IO} = return unsafe_write(bio_stream, pointer(out_data), length(out_data)) + +""" + BIOStream callbacks. +""" + +""" + Called to initialize new BIO Stream object. +""" +on_bio_stream_create(::BIOStream{T}) where {T<:IO} = Cint(1) +on_bio_stream_destroy(::BIOStream{T}) where {T<:IO} = Cint(0) + +function bio_set_flags(bio_stream::BIOStream{T}, flags) where {T<:IO} + return ccall( + (:BIO_set_flags, libcrypto), + Cint, + (BIOStream{T}, Cint), + bio_stream, flags) +end + +bio_stream_set_read_retry(bio_stream::BIOStream{T}) where {T<:IO} = bio_set_flags(bio_stream, BIO_FLAGS_READ | BIO_FLAGS_SHOULD_RETRY) +bio_stream_clear_flags(bio_stream::BIOStream{T}) where {T<:IO} = bio_set_flags(bio_stream, 0x00) + +""" +static int fd_read(BIO *b, char *out, int outl) +{ + int ret = 0; + + if (out != NULL) { + clear_sys_error(); + ret = UP_read(b->num, out, outl); + BIO_clear_retry_flags(b); + if (ret <= 0) { + if (BIO_fd_should_retry(ret)) + BIO_set_retry_read(b); + else if (ret == 0) + b->flags |= BIO_FLAGS_IN_EOF; + } + } + return ret; +} +""" + +function on_bio_stream_read(bio_stream::BIOStream{T}, out::Ptr{Cchar}, outlen::Cint) where {T<:IO} + try + bio_stream_clear_flags(bio_stream) + io::T = bio_stream_get_io(bio_stream) + + n = bytesavailable(io) + if n == 0 + bio_stream_set_read_retry(bio_stream) + return Cint(0) + end + + bytes_to_read::Cint = min(UInt(n), outlen) + unsafe_read(io, out, bytes_to_read) + return bytes_to_read + catch e + # we don't want to throw a Julia exception from a C callback + return Cint(-1) + end +end + +function on_bio_stream_write(bio_stream::BIOStream{T}, in::Ptr{Cchar}, inlen::Cint)::Cint where {T<:IO} + try + io::T = bio_stream_get_io(bio_stream) + 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(-1) + end +end + +on_bio_stream_puts(bio_stream::BIOStream{T}, in::Ptr{Cchar}) where {T<:IO} = Cint(0) + +on_bio_stream_ctrl(bio_stream::BIOStream{T}, cmd::BIOCtrl, num::Clong, ptr::Ptr{Cvoid}) where {T<:IO} = Clong(1) + +""" + BIO Stream callbacks. +""" +struct BIOStreamCallbacks{T<:IO} + 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{T}() where {T<:IO} + on_bio_create_ptr = @cfunction on_bio_stream_create Cint (BIOStream{T},) + on_bio_destroy_ptr = @cfunction on_bio_stream_destroy Cint (BIOStream{T},) + on_bio_read_ptr = @cfunction on_bio_stream_read Cint (BIOStream{T}, Ptr{Cchar}, Cint) + on_bio_write_ptr = @cfunction on_bio_stream_write Cint (BIOStream{T}, Ptr{Cchar}, Cint) + on_bio_puts_ptr = @cfunction on_bio_stream_puts Cint (BIOStream{T}, Ptr{Cchar}) + on_bio_ctrl_ptr = @cfunction on_bio_stream_ctrl Clong (BIOStream{T}, 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 + +function BIOMethod(bio_type::String, bio_stream_callbacks::BIOStreamCallbacks{T}) where {T<:IO} + 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 = BIOMethod(bio_method) + finalizer(free, bio_method) + + if ccall( + (:BIO_meth_set_create, libcrypto), + Cint, + (BIOMethod, Ptr{Cvoid}), + bio_method, + bio_stream_callbacks.on_bio_create_ptr) != 1 + throw(OpenSSLError()) + end + + if ccall( + (:BIO_meth_set_destroy, libcrypto), + Cint, + (BIOMethod, Ptr{Cvoid}), + bio_method, + bio_stream_callbacks.on_bio_destroy_ptr) != 1 + throw(OpenSSLError()) + end + + if ccall( + (:BIO_meth_set_read, libcrypto), + Cint, + (BIOMethod, Ptr{Cvoid}), + bio_method, + bio_stream_callbacks.on_bio_read_ptr) != 1 + throw(OpenSSLError()) + end + + if ccall( + (:BIO_meth_set_write, libcrypto), + Cint, + (BIOMethod, Ptr{Cvoid}), + bio_method, + bio_stream_callbacks.on_bio_write_ptr) != 1 + throw(OpenSSLError()) + end + + if ccall( + (:BIO_meth_set_puts, libcrypto), + Cint, + (BIOMethod, Ptr{Cvoid}), + bio_method, + bio_stream_callbacks.on_bio_puts_ptr) != 1 + throw(OpenSSLError()) + end + + if ccall( + (:BIO_meth_set_ctrl, libcrypto), + Cint, + (BIOMethod, Ptr{Cvoid}), + bio_method, + bio_stream_callbacks.on_bio_ctrl_ptr) != 1 + throw(OpenSSLError()) + end + + return bio_method +end + """ ASN1_TIME. """ @@ -1861,24 +2028,24 @@ function Base.:(==)(evp_pkey_1::EvpPKey, evp_pkey_2::EvpPKey) return result == 1 end -function Base.write(io::IO, evp_pkey::EvpPKey, evp_cipher::EvpCipher=EvpCipher(C_NULL)) - bio = BIO(io) +function Base.write(io::T, evp_pkey::EvpPKey, evp_cipher::EvpCipher=EvpCipher(C_NULL)) where {T<:IO} + bio_stream = BIOStream(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 + if ccall( + (:PEM_write_bio_PrivateKey, libcrypto), + Cint, + (BIOStream{T}, EvpPKey, EvpCipher, Ptr{Cvoid}, Cint, Cint, Ptr{Cvoid}), + bio_stream, + evp_pkey, + evp_cipher, + C_NULL, + 0, + 0, + C_NULL) != 1 + throw(OpenSSLError()) end + + free(bio_stream) end function get_key_type(evp_pkey::EvpPKey)::EvpPKeyType @@ -2267,19 +2434,19 @@ function Base.:(==)(x509_cert_1::X509Certificate, x509_cert_2::X509Certificate) return result == 0 end -function Base.write(io::IO, x509_cert::X509Certificate) - bio = BIO(io) +function Base.write(io::T, x509_cert::X509Certificate) where {T<:IO} + bio_stream = BIOStream(io) - GC.@preserve io begin - if ccall( - (:PEM_write_bio_X509, libcrypto), - Cint, - (BIO, X509Certificate), - bio, - x509_cert) != 1 - throw(OpenSSLError()) - end + if ccall( + (:PEM_write_bio_X509, libcrypto), + Cint, + (BIOStream{T}, X509Certificate), + bio_stream, + x509_cert) != 1 + throw(OpenSSLError()) end + + free(bio_stream) end function sign_certificate(x509_cert::X509Certificate, evp_pkey::EvpPKey) @@ -2554,18 +2721,18 @@ function free(x509_req::X509Request) end function Base.write(io::IO, x509_req::X509Request) - bio = BIO(io) + bio_stream = BIOStream(io) - GC.@preserve io begin - if ccall( - (:PEM_write_bio_X509_REQ, libcrypto), - Cint, - (BIO, X509Request), - bio, - x509_req) != 1 - throw(OpenSSLError()) - end + if ccall( + (:PEM_write_bio_X509_REQ, libcrypto), + Cint, + (BIO, X509Request), + bio_stream, + x509_req) != 1 + throw(OpenSSLError()) end + + free(bio_stream) end function add_extensions(x509_req::X509Request, x509_exts::StackOf{X509Extension}) @@ -2944,8 +3111,6 @@ function Base.String(x509_ext::X509Extension) return "C_NULL" end - io = IOBuffer() - bio = BIO(BIOMethodMemory()) _ = ccall( @@ -3109,16 +3274,20 @@ end include("ssl.jl") const OPEN_SSL_INIT = Ref{OpenSSLInit}() -const BIO_STREAM_CALLBACKS = Ref{BIOStreamCallbacks}() -const BIO_STREAM_METHOD = Ref{BIOMethod}() +const BIO_STREAM_CALLBACKS_IO = Ref{BIOStreamCallbacks{IO}}() +const BIO_STREAM_CALLBACKS_TCPSOCKET = Ref{BIOStreamCallbacks{TCPSocket}}() +const BIO_STREAM_METHOD_IO = Ref{BIOMethod}() +const BIO_STREAM_METHOD_TCPSOCKET = Ref{BIOMethod}() """ Initialize module. """ function __init__() OPEN_SSL_INIT.x = OpenSSLInit() - BIO_STREAM_CALLBACKS.x = BIOStreamCallbacks() - BIO_STREAM_METHOD.x = BIOMethod("BIO_STREAM_METHOD") + BIO_STREAM_CALLBACKS_IO.x = BIOStreamCallbacks{IO}() + BIO_STREAM_CALLBACKS_TCPSOCKET.x = BIOStreamCallbacks{TCPSocket}() + BIO_STREAM_METHOD_IO.x = BIOMethod("BIO_STREAM_METHOD_IO", BIO_STREAM_CALLBACKS_IO.x) + BIO_STREAM_METHOD_TCPSOCKET.x = BIOMethod("BIO_STREAM_METHOD_TCPSOCKET", BIO_STREAM_CALLBACKS_TCPSOCKET.x) # Set the openssl provider search path if version_number() ≥ v"3" diff --git a/src/ssl.jl b/src/ssl.jl index 3f94309..67525b4 100644 --- a/src/ssl.jl +++ b/src/ssl.jl @@ -1,98 +1,3 @@ -""" - 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. @@ -277,7 +182,7 @@ end mutable struct SSL ssl::Ptr{Cvoid} - function SSL(ssl_context::SSLContext, read_bio::BIO, write_bio::BIO)::SSL + function SSL(ssl_context::SSLContext, read_bio_stream::BIOStream{T}, write_bio_stream::BIOStream{T})::SSL where {T<:IO} ssl = ccall( (:SSL_new, libssl), Ptr{Cvoid}, @@ -292,10 +197,10 @@ mutable struct SSL ccall( (:SSL_set_bio, libssl), Cvoid, - (SSL, BIO, BIO), + (SSL, BIOStream{T}, BIOStream{T}), ssl, - read_bio, - write_bio) + read_bio_stream, + write_bio_stream) return ssl end @@ -332,32 +237,23 @@ function ssl_connect(ssl::SSL) end function ssl_accept(ssl::SSL) - if (ret = ccall( + return 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 + ssl) end """ Shut down a TLS/SSL connection. """ function ssl_disconnect(ssl::SSL) - ccall( + _ = ccall( (:SSL_shutdown, libssl), Cint, (SSL,), ssl) + return nothing end @@ -376,8 +272,8 @@ end mutable struct SSLStream <: IO ssl::SSL ssl_context::SSLContext - rbio::BIO - wbio::BIO + rbio::BIOStream{TCPSocket} + wbio::BIOStream{TCPSocket} 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 @@ -397,8 +293,8 @@ mutable struct SSLStream <: IO 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) + bio_read = BIOStream(io; finalize=false) + bio_write = BIOStream(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 @@ -420,7 +316,7 @@ 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 + lock(ssl.lock) do # check that SSL is still open before ccall $ssl.closed && throwio($op) # clear the current error queue before openssl ccall @@ -533,6 +429,7 @@ function Sockets.connect(ssl::SSLStream; require_ssl_verification::Bool=true) ssl.ssl, Cint(1)) end + return end @@ -553,8 +450,30 @@ function hostname!(ssl::SSLStream, host) end end +""" + Accept SSL session connection request from a client application. +""" function Sockets.accept(ssl::SSLStream) - ssl_accept(ssl.ssl) + while true + ret = @geterror ssl :accept ssl_accept(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 + + ccall( + (:SSL_set_read_ahead, libssl), + Cvoid, + (SSL, Cint), + ssl.ssl, + Int32(1)) end """ @@ -573,6 +492,7 @@ function Base.unsafe_read(ssl::SSLStream, buf::Ptr{UInt8}, nbytes::UInt) nbytes - nread, readbytes ) + if ret == SSL_ERROR_NONE nread += Base.bitcast(Int, readbytes[]) elseif ret == SSL_ERROR_WANT_READ diff --git a/test/http_helpers.jl b/test/http_helpers.jl index 11cc713..6de2ff1 100644 --- a/test/http_helpers.jl +++ b/test/http_helpers.jl @@ -1,6 +1,7 @@ using Dates using OpenSSL using Sockets +using Test function test_server() x509_certificate = X509Certificate() @@ -21,41 +22,45 @@ function test_server() sign_certificate(x509_certificate, evp_pkey) - server_socket = listen(5000) - try - accepted_socket = accept(server_socket) + # Create and configure server SSLContext. + ssl_ctx = OpenSSL.SSLContext(OpenSSL.TLSServerMethod()) + _ = OpenSSL.ssl_set_options(ssl_ctx, OpenSSL.SSL_OP_NO_COMPRESSION) + + OpenSSL.ssl_set_ciphersuites(ssl_ctx, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256") + OpenSSL.ssl_use_certificate(ssl_ctx, x509_certificate) + OpenSSL.ssl_use_private_key(ssl_ctx, evp_pkey) - # Create and configure server SSLContext. - ssl_ctx = OpenSSL.SSLContext(OpenSSL.TLSServerMethod()) - _ = OpenSSL.ssl_set_options(ssl_ctx, OpenSSL.SSL_OP_NO_COMPRESSION) + server_socket = listen(5000) + accepted_socket = accept(server_socket) - OpenSSL.ssl_set_ciphersuites(ssl_ctx, "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256") - OpenSSL.ssl_use_certificate(ssl_ctx, x509_certificate) - OpenSSL.ssl_use_private_key(ssl_ctx, evp_pkey) + ssl = SSLStream(ssl_ctx, accepted_socket) - ssl = SSLStream(ssl_ctx, accepted_socket) + OpenSSL.accept(ssl) - OpenSSL.accept(ssl) + # wait for the request, as we are using `readavailable` + # we need to make sure there is a data in the buffer. + while bytesavailable(ssl) == 0 + eof(ssl) + end - @test !eof(ssl) - request = readavailable(ssl) - reply = "reply: $(String(request))" + request = readavailable(ssl) + reply = "reply: $(String(request))" - # eof(ssl) will block + # eof(ssl) will block - # Verify the are no more bytes available in the stream. - @test bytesavailable(ssl) == 0 + # Verify the are no more bytes available in the stream. + @test bytesavailable(ssl) == 0 - write(ssl, reply) + unsafe_write(ssl, pointer(reply), length(reply)) - try - close(ssl) - catch - end - finalize(ssl_ctx) - finally - close(server_socket) + # Wait for the client confirmation then disconnect. + while bytesavailable(ssl) == 0 + eof(ssl) end + + close(ssl) + finalize(ssl_ctx) + return nothing end @@ -63,13 +68,12 @@ function test_client() tcp_stream = connect(5000) ssl_ctx = OpenSSL.SSLContext(OpenSSL.TLSClientMethod()) - ssl_options = OpenSSL.ssl_set_options(ssl_ctx, OpenSSL.SSL_OP_NO_COMPRESSION) + _ = OpenSSL.ssl_set_options(ssl_ctx, OpenSSL.SSL_OP_NO_COMPRESSION) # Create SSL stream. ssl = SSLStream(ssl_ctx, tcp_stream) - #TODO expose connect - OpenSSL.connect(ssl) + connect(ssl; require_ssl_verification = false) # Verify the server certificate. x509_server_cert = OpenSSL.get_peer_certificate(ssl) @@ -80,19 +84,21 @@ function test_client() request_str = "GET / HTTP/1.1\r\nHost: localhost\r\nUser-Agent: curl\r\nAccept: */*\r\n\r\nRequest_body." written = unsafe_write(ssl, pointer(request_str), length(request_str)) - - sleep(1) - @test !eof(ssl) @test length(request_str) == written + # wait for the response. + while bytesavailable(ssl) == 0 + eof(ssl) + end + response_str = String(readavailable(ssl)) - @test response_str == "reply: $request_str" + @test response_str == "reply: $(request_str)" + @show response_str - try - close(ssl) - catch - end + # Send a message again, that is the information for the server to disonnect. + written = unsafe_write(ssl, pointer(request_str), length(request_str)) + + close(ssl) finalize(ssl_ctx) - return nothing end diff --git a/test/runtests.jl b/test/runtests.jl index 96d4449..ee9c38d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -24,12 +24,19 @@ end # Verifies calling into OpenSSL library. @testset "OpenSSL" begin - @test OpenSSL.BIO_STREAM_CALLBACKS.x.on_bio_create_ptr != C_NULL - @test OpenSSL.BIO_STREAM_CALLBACKS.x.on_bio_destroy_ptr != C_NULL - @test OpenSSL.BIO_STREAM_CALLBACKS.x.on_bio_read_ptr != C_NULL - @test OpenSSL.BIO_STREAM_CALLBACKS.x.on_bio_write_ptr != C_NULL - @test OpenSSL.BIO_STREAM_CALLBACKS.x.on_bio_puts_ptr != C_NULL - @test OpenSSL.BIO_STREAM_CALLBACKS.x.on_bio_ctrl_ptr != C_NULL + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_create_ptr != C_NULL + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_destroy_ptr != C_NULL + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_read_ptr != C_NULL + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_write_ptr != C_NULL + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_puts_ptr != C_NULL + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_ctrl_ptr != C_NULL + + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_create_ptr != OpenSSL.BIO_STREAM_CALLBACKS_TCPSOCKET.x.on_bio_create_ptr + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_destroy_ptr != OpenSSL.BIO_STREAM_CALLBACKS_TCPSOCKET.x.on_bio_destroy_ptr + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_read_ptr != OpenSSL.BIO_STREAM_CALLBACKS_TCPSOCKET.x.on_bio_read_ptr + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_write_ptr != OpenSSL.BIO_STREAM_CALLBACKS_TCPSOCKET.x.on_bio_write_ptr + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_puts_ptr != OpenSSL.BIO_STREAM_CALLBACKS_TCPSOCKET.x.on_bio_puts_ptr + @test OpenSSL.BIO_STREAM_CALLBACKS_IO.x.on_bio_ctrl_ptr != OpenSSL.BIO_STREAM_CALLBACKS_TCPSOCKET.x.on_bio_ctrl_ptr end @testset "RandomBytes" begin @@ -199,6 +206,7 @@ end sleep(2) write(io, readavailable(ssl)) response = String(take!(io)) + @test startswith(response, "HTTP/1.1 200 OK\r\n") sleep(2) @test isempty(readavailable(ssl)) @@ -285,18 +293,10 @@ end sign_certificate(x509_certificate, evp_pkey) - port, server = Sockets.listenany(10000) - iob = connect(port) - sob = accept(server) - local cert_pem - try - write(iob, x509_certificate) - cert_pem = String(readavailable(sob)) - finally - close(iob) - close(sob) - close(server) - end + iob = IOBuffer() + write(iob, x509_certificate) + + cert_pem = String(take!(iob)) x509_certificate2 = X509Certificate(cert_pem) @@ -575,18 +575,10 @@ end @testset "SerializePrivateKey" begin evp_pkey = EvpPKey(rsa_generate_key()) - port, server = Sockets.listenany(10000) - iob = connect(port) - sob = accept(server) - local pkey_pem - try - write(iob, evp_pkey) - pkey_pem = String(readavailable(sob)) - finally - close(iob) - close(sob) - close(server) - end + iob = IOBuffer() + write(iob, evp_pkey) + + pkey_pem = String(take!(iob)) @test startswith(pkey_pem, "-----BEGIN PRIVATE KEY-----") @@ -610,10 +602,9 @@ end @testset "SSLServer" begin server_task = @async test_server() client_task = @async test_client() - if isdefined(Base, :errormonitor) - errormonitor(server_task) - errormonitor(client_task) - end + + wait(server_task) + wait(client_task) end @testset "VersionNumber" begin