Skip to content

Commit 5093681

Browse files
authored
OpenSSL v3 support (#22)
* Initial draft of OpenSSL v3 support * Fix version_number() on Windows * Rearrange code for loading * Attempt to shim for Windows * Use libcrypto, symbols are not reexported on Windows * Introduce _ossl_modules_path * Fix typo, WORD to WORD_SIZE * Fix tests to use libcrypto for version number check * Cleanup * Add docstring for version * Check version_number against regex extraction * Add test for OpenSSL v1.1 * Test OpenSSL v1.1 * Relax version number testing for OpenSSL v1.1 * Also run IntegrationTest with OpenSSL v1.1 * Split integration tests * Pin OpenSSL v1.1 in CI
1 parent 90454ab commit 5093681

File tree

7 files changed

+267
-26
lines changed

7 files changed

+267
-26
lines changed

.github/workflows/CI.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,24 @@ jobs:
5757
${{ runner.os }}-
5858
- uses: julia-actions/julia-buildpkg@v1
5959
- uses: julia-actions/julia-runtest@v1
60+
- name: Test with OpenSSL v1.1
61+
shell: julia --project=openssl_1_1 {0}
62+
run: |
63+
using Pkg
64+
try
65+
spec = PackageSpec(name="OpenSSL_jll", version="1.1")
66+
Pkg.add(spec)
67+
Pkg.pin(spec)
68+
Pkg.develop(PackageSpec(path="."))
69+
Pkg.test("OpenSSL") # resolver may fail with test time deps
70+
catch err
71+
err isa Pkg.Resolve.ResolverError || rethrow()
72+
# If we can't resolve that means this is incompatible by SemVer and this is fine;
73+
# it means we marked this as a breaking change, so we don't need to worry about
74+
# mistakenly introducing a breaking change, as we have intentionally made one
75+
@info "Not compatible with this release. No problem." exception=err
76+
exit(0) # Exit immediately as a success.
77+
end
6078
- uses: julia-actions/julia-processcoverage@v1
6179
- uses: codecov/codecov-action@v1
6280
with:

.github/workflows/IntegrationTest.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
arch: x64
3232
- uses: julia-actions/julia-buildpkg@latest
3333
- name: Clone Downstream
34-
uses: actions/checkout@v2
34+
uses: actions/checkout@v3
3535
with:
3636
repository: ${{ matrix.package.user }}/${{ matrix.package.repo }}
3737
path: downstream
@@ -51,4 +51,4 @@ jobs:
5151
# mistakenly introducing a breaking change, as we have intentionally made one
5252
@info "Not compatible with this release. No problem." exception=err
5353
exit(0) # Exit immediately as a success.
54-
end
54+
end
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: IntegrationTestOpenSSL_v1_1
2+
on:
3+
push:
4+
branches: [main]
5+
tags: [v*]
6+
pull_request:
7+
8+
concurrency:
9+
# Skip intermediate builds: always.
10+
# Cancel intermediate builds: only if it is a pull request build.
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
13+
14+
jobs:
15+
test:
16+
name: ${{ matrix.package.repo }}
17+
runs-on: ${{ matrix.os }}
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
julia-version: [1]
22+
os: [ubuntu-latest]
23+
package:
24+
- {user: JuliaWeb, repo: HTTP.jl}
25+
26+
steps:
27+
- uses: actions/checkout@v2
28+
- uses: julia-actions/setup-julia@v1
29+
with:
30+
version: ${{ matrix.julia-version }}
31+
arch: x64
32+
- uses: julia-actions/julia-buildpkg@latest
33+
- name: Clone Downstream
34+
uses: actions/checkout@v3
35+
with:
36+
repository: ${{ matrix.package.user }}/${{ matrix.package.repo }}
37+
path: downstream
38+
- name: Load this and run the downstream tests with OpenSSL v1.1
39+
shell: julia --project=downstream {0}
40+
run: |
41+
using Pkg
42+
try
43+
# Force downstream tests to use this PR's version of the package.
44+
spec = PackageSpec(name="OpenSSL_jll", version="1.1")
45+
Pkg.add(spec)
46+
Pkg.pin(spec)
47+
Pkg.develop(PackageSpec(path=".")) # resolver may fail with main deps
48+
Pkg.update()
49+
Pkg.test() # resolver may fail with test time deps
50+
catch err
51+
err isa Pkg.Resolve.ResolverError || rethrow()
52+
# If we can't resolve that means this is incompatible by SemVer and this is fine;
53+
# it means we marked this as a breaking change, so we don't need to worry about
54+
# mistakenly introducing a breaking change, as we have intentionally made one
55+
@info "Not compatible with this release. No problem." exception=err
56+
exit(0) # Exit immediately as a success.
57+
end

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ OpenSSL_jll = "458c3c95-2e84-50aa-8efc-19380b2a3a95"
1111
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
1212

1313
[compat]
14-
julia = "1.6"
1514
BitFlags = "0.1"
1615
MozillaCACerts_jll = ">= 2020"
17-
OpenSSL_jll = "1.1"
16+
OpenSSL_jll = "1.1, 3.0"
17+
julia = "1.6"
1818

1919
[extras]
2020
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

src/OpenSSL.jl

Lines changed: 148 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,141 @@ struct OpenSSLError <: Exception
466466
OpenSSLError(x::SSLErrorCode) = new(string(x))
467467
end
468468

469+
"""
470+
version(; [version_type::OpenSSLVersion])
471+
472+
Obtain the version as a `String`. See [`OpenSSLVersion`](@ref) to select
473+
the version type.
474+
475+
See also [`version_number`](@ref) to obtain a `VersionNumber`.
476+
"""
477+
function version(; version_type::OpenSSLVersion=OPENSSL_VERSION)::String
478+
version = ccall(
479+
(:OpenSSL_version, libcrypto),
480+
Cstring,
481+
(Cint,),
482+
version_type)
483+
484+
return unsafe_string(version)
485+
end
486+
487+
"""
488+
version_number()::VersionNumber
489+
490+
Return the version number of the OpenSSL C library.
491+
This uses `OpenSSL_version_num()`.
492+
"""
493+
function version_number()
494+
# This works on OpenSSL v1.1
495+
vn = ccall((:OpenSSL_version_num, libcrypto), Culong, ())
496+
497+
# 0xMNN00PP0L
498+
# M: major
499+
# NN: minor
500+
# PP: patch
501+
major = vn >> 28
502+
minor = vn >> 20 & 0xff
503+
patch = vn >> 4 & 0xff
504+
505+
return VersionNumber(major, minor, patch)
506+
end
507+
508+
@static if version_number() v"3"
509+
# In OpenSSL v3, macros redirect these symbols
510+
const SSL_get_peer_certificate = :SSL_get1_peer_certificate
511+
const EVP_PKEY_base_id = :EVP_PKEY_get_base_id
512+
const EVP_CIPHER_key_length = :EVP_CIPHER_get_key_length
513+
const EVP_CIPHER_iv_length = :EVP_CIPHER_get_iv_length
514+
const EVP_CIPHER_block_size = :EVP_CIPHER_get_block_size
515+
const EVP_CIPHER_CTX_block_size = :EVP_CIPHER_CTX_get_block_size
516+
const EVP_CIPHER_CTX_key_length = :EVP_CIPHER_CTX_get_key_length
517+
const EVP_CIPHER_CTX_iv_length = :EVP_CIPHER_CTX_get_iv_length
518+
else
519+
const SSL_get_peer_certificate = :SSL_get_peer_certificate
520+
const EVP_PKEY_base_id = :EVP_PKEY_base_id
521+
const EVP_CIPHER_key_length = :EVP_CIPHER_key_length
522+
const EVP_CIPHER_iv_length = :EVP_CIPHER_iv_length
523+
const EVP_CIPHER_block_size = :EVP_CIPHER_block_size
524+
const EVP_CIPHER_CTX_block_size = :EVP_CIPHER_CTX_block_size
525+
const EVP_CIPHER_CTX_key_length = :EVP_CIPHER_CTX_key_length
526+
const EVP_CIPHER_CTX_iv_length = :EVP_CIPHER_CTX_iv_length
527+
end
528+
529+
# Locate oss-modules, which contains legacy shared library
530+
function _ossl_modules_path()
531+
@static if Sys.iswindows()
532+
bin_dir = dirname(OpenSSL_jll.libssl_path)
533+
lib_dir = joinpath(dirname(bin_dir), "lib")
534+
if Sys.WORD_SIZE == 64
535+
return joinpath(lib_dir * "64", "ossl-modules")
536+
else
537+
return joinpath(lib_dir, "ossl-modules")
538+
end
539+
else
540+
return joinpath(dirname(OpenSSL_jll.libssl), "ossl-modules")
541+
end
542+
end
543+
544+
"""
545+
ossl_provider_set_default_search_path([libctx], [path])
546+
547+
Set the default search path for providers. If no arguments are given,
548+
the global context will be configured for the ossl-modules directory
549+
in the OpenSSL_jll artifact.
550+
551+
This is called with no arguments in OpenSSL.jl `__init__` when
552+
OpenSSL v3 is used.
553+
554+
!!! compat "OpenSSL v3" `ossl_provider_set_default_search_path` is only available with version 3 of the OpenSSL_jll
555+
"""
556+
function ossl_provider_set_default_search_path(libctx = C_NULL, path = _ossl_modules_path())
557+
result = ccall(
558+
(:OSSL_PROVIDER_set_default_search_path, libcrypto),
559+
Cint,
560+
(Ptr{Nothing}, Cstring),
561+
libctx,
562+
path
563+
)
564+
if result == 0
565+
throw(OpenSSLError())
566+
end
567+
return result
568+
end
569+
570+
"""
571+
load_provider([libctx], provider_name)
572+
573+
Load a provider. If libctx is omitted, the provider will be loaded into the
574+
global context.
575+
576+
!!! compat "OpenSSL v3" `load_provider` is only available with version 3 of the OpenSSL_jll
577+
"""
578+
function load_provider(libctx, provider_name)
579+
result = ccall(
580+
(:OSSL_PROVIDER_load, libcrypto),
581+
Ptr{Nothing},
582+
(Ptr{Nothing}, Cstring),
583+
libctx,
584+
provider_name
585+
)
586+
if result == C_NULL
587+
throw(OpenSSLError())
588+
end
589+
return nothing
590+
end
591+
load_provider(provider_name) = load_provider(C_NULL, provider_name)
592+
593+
"""
594+
load_legacy_provider()
595+
596+
Load the legacy provider. This loads legacy ciphers such as Blowfish.
597+
598+
See https://www.openssl.org/docs/man3.0/man7/OSSL_PROVIDER-legacy.html
599+
600+
!!! compat "OpenSSL v3" `load_legacy_provider` is only available with version 3 of the OpenSSL_jll
601+
"""
602+
load_legacy_provider() = load_provider(C_NULL, "legacy")
603+
469604
"""
470605
Random bytes.
471606
"""
@@ -718,19 +853,19 @@ mutable struct EvpCipher
718853
end
719854

720855
get_block_size(evp_cipher::EvpCipher)::Int32 = ccall(
721-
(:EVP_CIPHER_block_size, libcrypto),
856+
(EVP_CIPHER_block_size, libcrypto),
722857
Int32,
723858
(EvpCipher,),
724859
evp_cipher)
725860

726861
get_key_length(evp_cipher::EvpCipher)::Int32 = ccall(
727-
(:EVP_CIPHER_key_length, libcrypto),
862+
(EVP_CIPHER_key_length, libcrypto),
728863
Int32,
729864
(EvpCipher,),
730865
evp_cipher)
731866

732867
get_init_vector_length(evp_cipher::EvpCipher)::Int32 = ccall(
733-
(:EVP_CIPHER_iv_length, libcrypto),
868+
(EVP_CIPHER_iv_length, libcrypto),
734869
Int32,
735870
(EvpCipher,),
736871
evp_cipher)
@@ -997,19 +1132,19 @@ function cipher(evp_cipher_ctx::EvpCipherContext, in_io::IO, out_io::IO)
9971132
end
9981133

9991134
get_block_size(evp_cipher_ctx::EvpCipherContext)::Int32 = ccall(
1000-
(:EVP_CIPHER_CTX_block_size, libcrypto),
1135+
(EVP_CIPHER_CTX_block_size, libcrypto),
10011136
Int32,
10021137
(EvpCipherContext,),
10031138
evp_cipher_ctx)
10041139

10051140
get_key_length(evp_cipher_ctx::EvpCipherContext)::Int32 = ccall(
1006-
(:EVP_CIPHER_CTX_key_length, libcrypto),
1141+
(EVP_CIPHER_CTX_key_length, libcrypto),
10071142
Int32,
10081143
(EvpCipherContext,),
10091144
evp_cipher_ctx)
10101145

10111146
get_init_vector_length(evp_cipher_ctx::EvpCipherContext)::Int32 = ccall(
1012-
(:EVP_CIPHER_CTX_iv_length, libcrypto),
1147+
(EVP_CIPHER_CTX_iv_length, libcrypto),
10131148
Int32,
10141149
(EvpCipherContext,),
10151150
evp_cipher_ctx)
@@ -1748,7 +1883,7 @@ end
17481883

17491884
function get_key_type(evp_pkey::EvpPKey)::EvpPKeyType
17501885
pkey_type = ccall(
1751-
(:EVP_PKEY_base_id, libcrypto),
1886+
(EVP_PKEY_base_id, libcrypto),
17521887
EvpPKeyType,
17531888
(EvpPKey,),
17541889
evp_pkey)
@@ -2971,16 +3106,6 @@ function update_tls_error_state()
29713106
end
29723107
end
29733108

2974-
function version(; version_type::OpenSSLVersion=OPENSSL_VERSION)::String
2975-
version = ccall(
2976-
(:OpenSSL_version, libcrypto),
2977-
Cstring,
2978-
(Cint,),
2979-
version_type)
2980-
2981-
return unsafe_string(version)
2982-
end
2983-
29843109
include("ssl.jl")
29853110

29863111
const OPEN_SSL_INIT = Ref{OpenSSLInit}()
@@ -2994,6 +3119,12 @@ function __init__()
29943119
OPEN_SSL_INIT.x = OpenSSLInit()
29953120
BIO_STREAM_CALLBACKS.x = BIOStreamCallbacks()
29963121
BIO_STREAM_METHOD.x = BIOMethod("BIO_STREAM_METHOD")
3122+
3123+
# Set the openssl provider search path
3124+
if version_number() v"3"
3125+
ossl_provider_set_default_search_path()
3126+
end
3127+
29973128
return
29983129
end
29993130

src/ssl.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -700,7 +700,7 @@ end
700700
"""
701701
function get_peer_certificate(ssl::SSLStream)::Option{X509Certificate}
702702
x509 = ccall(
703-
(:SSL_get_peer_certificate, libssl),
703+
(SSL_get_peer_certificate, libssl),
704704
Ptr{Cvoid},
705705
(SSL,),
706706
ssl.ssl)

0 commit comments

Comments
 (0)