pq_crypto 0.6.3 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aee26c8aa17143d9a17bd24b9435f073cc5d40beff39cbc90d0685d2221ff8d9
4
- data.tar.gz: b0d51294d4639587c619fd5854d2c33d01b5aaa82929aae759dcb8f5776fefbe
3
+ metadata.gz: a7887d01882b53d518ecac6559fe2444572c50d5cd7074ee6770d8ea7bcfa32b
4
+ data.tar.gz: 9b518ef75cacc9d192b40c831e79e31c6883fddd2ce4cf56d2e08f5054da48a3
5
5
  SHA512:
6
- metadata.gz: a293e6386c7a350be36b271f9e26fef69f1f3e6050fd3eca5e5f69a7f039c39ad08d152cecf46918b1fa8d60908746c2784de27cae3e0351082cb1c63470fe49
7
- data.tar.gz: e12be8a7ad95c42cb27659215c4bfe9201d5b18d531c344279707c2ce74d5fb981328c28beb79e72815e182acc0790b9a5682af8adf17c8b737a12e691d830ec
6
+ metadata.gz: 90f571ac5fd32557f539303cfc9d7f148aac81532325cd31baf69d879528f91107184ae2c8b785737808bce762f635436506ef265e623dc14f9d51cc61e826b8
7
+ data.tar.gz: 1783ad30164c271257ee6813ba83c24bc50c99cec0ef274217d4a5d65cffef73447f0b4e66c77952ca3260f2abdfad5740d208729517b34d2a60a84304793e71
@@ -8,7 +8,7 @@ on:
8
8
  jobs:
9
9
  vendor-verify:
10
10
  name: vendor-verify
11
- runs-on: ubuntu-latest
11
+ runs-on: ubuntu-24.04
12
12
 
13
13
  steps:
14
14
  - name: Checkout
@@ -30,8 +30,8 @@ jobs:
30
30
  strategy:
31
31
  fail-fast: false
32
32
  matrix:
33
- os: [ubuntu-latest, macos-latest]
34
- ruby: ["3.1", "3.4", "4.0"]
33
+ os: [ubuntu-24.04, macos-latest]
34
+ ruby: ["2.7.2", "3.1", "3.4", "4.0"]
35
35
 
36
36
  steps:
37
37
  - name: Checkout
@@ -48,15 +48,139 @@ jobs:
48
48
  with:
49
49
  go-version: "1.26.0"
50
50
 
51
+ - name: Select system OpenSSL 3 for Ruby 2.7 on Linux
52
+ if: matrix.os == 'ubuntu-24.04' && matrix.ruby == '2.7.2'
53
+ shell: bash
54
+ run: |
55
+ set -euxo pipefail
56
+
57
+ sudo apt-get update
58
+ sudo apt-get install -y libssl-dev pkg-config
59
+
60
+ multiarch="$(dpkg-architecture -qDEB_HOST_MULTIARCH)"
61
+ system_pc_path="/usr/lib/${multiarch}/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"
62
+
63
+ version="$(
64
+ PKG_CONFIG_PATH="${system_pc_path}" \
65
+ PKG_CONFIG_LIBDIR="${system_pc_path}" \
66
+ pkg-config --modversion openssl
67
+ )"
68
+
69
+ case "${version}" in
70
+ 3.*) ;;
71
+ *)
72
+ echo "Expected system OpenSSL 3.x, got ${version}"
73
+ exit 1
74
+ ;;
75
+ esac
76
+
77
+ {
78
+ echo "OPENSSL_ROOT_DIR=/usr"
79
+ echo "OPENSSL_DIR="
80
+ echo "PKG_CONFIG_PATH=${system_pc_path}"
81
+ echo "PKG_CONFIG_LIBDIR=${system_pc_path}"
82
+ } >> "${GITHUB_ENV}"
83
+
84
+ echo "System OpenSSL selected: ${version}"
85
+ PKG_CONFIG_PATH="${system_pc_path}" PKG_CONFIG_LIBDIR="${system_pc_path}" \
86
+ pkg-config --variable=includedir openssl
87
+ PKG_CONFIG_PATH="${system_pc_path}" PKG_CONFIG_LIBDIR="${system_pc_path}" \
88
+ pkg-config --variable=libdir openssl
89
+
51
90
  - name: Verify vendored sources
52
91
  run: bundle exec rake vendor:verify
53
92
 
54
93
  - name: Compile extension
55
- run: bundle exec rake compile
94
+ run: bundle exec rake clean compile
95
+
96
+ - name: Verify linked OpenSSL 3 on Linux Ruby 2.7
97
+ if: matrix.os == 'ubuntu-24.04' && matrix.ruby == '2.7.2'
98
+ shell: bash
99
+ run: |
100
+ set -euxo pipefail
101
+
102
+ extension="lib/pqcrypto/pqcrypto_secure.so"
103
+ test -f "${extension}"
104
+ ldd "${extension}" | tee linked-libraries.txt
105
+ # The extension uses libcrypto only (SHA3/SHAKE/EVP/RAND); it links no
106
+ # TLS symbols, so libssl is intentionally not a dependency.
107
+ grep -E 'libcrypto\.so\.3([[:space:]]|$)' linked-libraries.txt
108
+ if grep -E 'libssl\.so' linked-libraries.txt; then
109
+ echo "Unexpected libssl linkage; the extension should need libcrypto only" >&2
110
+ exit 1
111
+ fi
112
+
113
+ - name: Verify linked OpenSSL 3 on macOS Ruby 2.7
114
+ if: matrix.os == 'macos-latest' && matrix.ruby == '2.7.2'
115
+ shell: bash
116
+ run: |
117
+ set -euxo pipefail
118
+
119
+ extension="lib/pqcrypto/pqcrypto_secure.bundle"
120
+ test -f "${extension}"
121
+ otool -L "${extension}" | tee linked-libraries.txt
122
+ # libcrypto only; the extension links no TLS symbols, so libssl is
123
+ # intentionally not a dependency.
124
+ grep -E 'libcrypto\.3\.dylib' linked-libraries.txt
125
+ if grep -E 'libssl\.3\.dylib' linked-libraries.txt; then
126
+ echo "Unexpected libssl linkage; the extension should need libcrypto only" >&2
127
+ exit 1
128
+ fi
56
129
 
57
130
  - name: Run tests
58
131
  run: bundle exec rake test
59
132
 
133
+ - name: Build and install source gem on Linux Ruby 2.7
134
+ if: matrix.os == 'ubuntu-24.04' && matrix.ruby == '2.7.2'
135
+ shell: bash
136
+ run: |
137
+ set -euxo pipefail
138
+
139
+ gem build pq_crypto.gemspec
140
+
141
+ gem_file="$(
142
+ ruby -Ilib -rpq_crypto/version \
143
+ -e 'print "pq_crypto-#{PQCrypto::VERSION}.gem"'
144
+ )"
145
+
146
+ gem install "./${gem_file}" --no-document
147
+
148
+ ruby -e '
149
+ require "pq_crypto"
150
+
151
+ kem = PQCrypto::KEM.generate(:ml_kem_768)
152
+ encapsulation = kem.public_key.encapsulate
153
+ shared_secret = kem.secret_key.decapsulate(encapsulation.ciphertext)
154
+ raise "ML-KEM mismatch" unless shared_secret == encapsulation.shared_secret
155
+
156
+ signature_key = PQCrypto::Signature.generate(:ml_dsa_65)
157
+ message = "ruby-2.7.2-built-gem".b
158
+ signature = signature_key.secret_key.sign(message)
159
+ raise "ML-DSA verify failed" unless signature_key.public_key.verify(message, signature)
160
+ '
161
+
162
+ installed_extension="$(
163
+ ruby -e '
164
+ spec = Gem::Specification.find_by_name("pq_crypto")
165
+ path = Dir[File.join(spec.full_gem_path, "lib", "pqcrypto", "pqcrypto_secure.so")].first
166
+ abort "installed extension not found" unless path
167
+ print path
168
+ '
169
+ )"
170
+
171
+ ldd "${installed_extension}" | tee installed-linked-libraries.txt
172
+ grep -E 'libcrypto\.so\.3([[:space:]]|$)' installed-linked-libraries.txt
173
+ if grep -E 'libssl\.so' installed-linked-libraries.txt; then
174
+ echo "Unexpected libssl linkage; the extension should need libcrypto only" >&2
175
+ exit 1
176
+ fi
177
+
178
+ - name: Show mkmf logs on failure
179
+ if: failure()
180
+ shell: bash
181
+ run: |
182
+ find tmp -name mkmf.log -print -exec cat {} \; || true
183
+
60
184
  interop-openssl-3-5-required:
61
185
  needs: vendor-verify
62
186
  name: interop-openssl-3.5-required
@@ -81,12 +205,12 @@ jobs:
81
205
  sudo ldconfig
82
206
  /opt/openssl-3.5/bin/openssl version
83
207
  /opt/openssl-3.5/bin/openssl version | grep -E '^OpenSSL 3\.(5|[6-9]|[1-9][0-9])\.'
84
- echo /opt/openssl-3.5/bin >> "$GITHUB_PATH"
208
+ echo /opt/openssl-3.5/bin >> "${GITHUB_PATH}"
85
209
  {
86
210
  echo "OPENSSL_ROOT_DIR=/opt/openssl-3.5"
87
211
  echo "PKG_CONFIG_PATH=/opt/openssl-3.5/lib64/pkgconfig"
88
212
  echo "LD_LIBRARY_PATH=/opt/openssl-3.5/lib64"
89
- } >> "$GITHUB_ENV"
213
+ } >> "${GITHUB_ENV}"
90
214
 
91
215
  - name: Set up Ruby
92
216
  uses: ruby/setup-ruby@v1
@@ -103,7 +227,7 @@ jobs:
103
227
  run: bundle exec rake vendor:verify
104
228
 
105
229
  - name: Compile extension
106
- run: bundle exec rake compile
230
+ run: bundle exec rake clean compile
107
231
 
108
232
  - name: Run tests
109
233
  run: |
@@ -117,10 +241,17 @@ jobs:
117
241
  exit 1
118
242
  fi
119
243
 
244
+ - name: Show mkmf logs on failure
245
+ if: failure()
246
+ shell: bash
247
+ run: |
248
+ find tmp -name mkmf.log -print -exec cat {} \; || true
249
+
120
250
  linux-native-backend:
121
251
  needs: test
122
252
  name: linux-native-backend
123
- runs-on: ubuntu-latest
253
+ runs-on: ubuntu-24.04
254
+
124
255
  env:
125
256
  PQCRYPTO_NATIVE_ASM: "1"
126
257
 
@@ -148,18 +279,19 @@ jobs:
148
279
  require "pq_crypto"
149
280
 
150
281
  kem = PQCrypto::KEM.generate(:ml_kem_768)
151
- enc = kem.public_key.encapsulate
152
- raise "ML-KEM mismatch" unless kem.secret_key.decapsulate(enc.ciphertext) == enc.shared_secret
282
+ encapsulation = kem.public_key.encapsulate
283
+ shared_secret = kem.secret_key.decapsulate(encapsulation.ciphertext)
284
+ raise "ML-KEM mismatch" unless shared_secret == encapsulation.shared_secret
153
285
 
154
- sig = PQCrypto::Signature.generate(:ml_dsa_65)
155
- msg = "linux-native-backend".b
156
- signature = sig.secret_key.sign(msg)
157
- raise "ML-DSA verify failed" unless sig.public_key.verify(msg, signature)
286
+ signature_key = PQCrypto::Signature.generate(:ml_dsa_65)
287
+ message = "linux-native-backend".b
288
+ signature = signature_key.secret_key.sign(message)
289
+ raise "ML-DSA verify failed" unless signature_key.public_key.verify(message, signature)
158
290
  '
159
291
 
160
292
  - name: Verify AVX2 native symbols
161
293
  run: |
162
294
  set -euxo pipefail
163
- so="lib/pqcrypto/pqcrypto_secure.so"
164
- test -f "$so"
165
- nm "$so" | grep -E "pqcr_(mlkem|mldsa).*avx2|keccak.*avx2"
295
+ extension="lib/pqcrypto/pqcrypto_secure.so"
296
+ test -f "${extension}"
297
+ nm "${extension}" | grep -E "pqcr_(mlkem|mldsa).*avx2|keccak.*avx2"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.4] - 2026-07-18
4
+
5
+ ### Compatibility
6
+
7
+ - Lowered the minimum supported Ruby version from `>= 3.1` to `>= 2.7.1`.
8
+ - Kept the Ruby 3.4+ scheduler-aware `RB_NOGVL_OFFLOAD_SAFE` path unchanged.
9
+ - Ruby 2.7.1-3.3 use the existing no-GVL compatibility path without claiming Fiber Scheduler offload guarantees.
10
+ - Made the test-only `async` dependency conditional so development dependencies resolve on Ruby 2.7.1.
11
+
12
+ ### CI
13
+
14
+ - Added an Ubuntu Ruby 2.7 compatibility job on Ruby 2.7.2, including the full test suite and a built-gem install smoke test. Ruby 2.7.1 remains the declared floor; the hosted Linux job uses 2.7.2 because exact 2.7.1 is not available through the runner toolchain.
15
+
16
+ ### Build
17
+
18
+ - Fixed OpenSSL 3 discovery on Intel and Apple Silicon macOS by resolving the Homebrew `openssl@3` prefix dynamically.
19
+ - Ignore stale OpenSSL 1.1 `pkg-config` metadata instead of injecting incompatible headers into native builds.
20
+ - Validate the selected OpenSSL prefix and link against the OpenSSL 3 API during configuration, preventing mixed headers/library builds.
21
+ - Link the validated OpenSSL libraries by absolute path, preventing Ruby 2.7 Linux toolchains from selecting their bundled OpenSSL 1.1 while preserving mkmf's libruby-safe default path precedence.
22
+ - Prefer Linux multiarch OpenSSL library directories over generic `lib` directories when resolving an explicit installation prefix.
23
+ - Avoid Clang macro-expansion warnings on Ruby 2.7 by using the function-form `rb_intern2` API without changing symbol semantics.
24
+ - Remove only Ruby 2.7's obsolete macOS `-multiply_defined,suppress` linker option, preserving all other inherited linker flags including `-undefined,dynamic_lookup`.
25
+
26
+ ### Documentation
27
+
28
+ - Documented the Ruby 2.7.1+ support policy and the concurrency guarantees for compatibility runtimes.
29
+
3
30
  ## [0.6.3] - 2026-06-24
4
31
 
5
32
  ### Changed
data/GET_STARTED.md CHANGED
@@ -445,7 +445,7 @@ On Ruby 3.4 and later, signing and verification keep Ruby's scheduler-aware
445
445
  that implements `blocking_operation_wait`, blocking native work can be moved
446
446
  off the event loop.
447
447
 
448
- Ruby 3.1-3.3 are supported as a compatibility path: native operations still
448
+ Ruby 2.7.1-3.3 are supported as a compatibility path: native operations still
449
449
  release the GVL, but `RB_NOGVL_OFFLOAD_SAFE` is not available there, so the gem
450
450
  does not claim Fiber Scheduler offload guarantees on those runtimes.
451
451
 
data/README.md CHANGED
@@ -63,9 +63,9 @@ original algorithms:
63
63
 
64
64
  ## Requirements
65
65
 
66
- - Ruby 3.1 or later
66
+ - Ruby 2.7.1 or later
67
67
  - Ruby 3.4+ keeps the optimized Fiber Scheduler offload path via `RB_NOGVL_OFFLOAD_SAFE`
68
- - Ruby 3.1-3.3 use the compatibility no-GVL path without scheduler offload guarantees
68
+ - Ruby 2.7.1-3.3 use the compatibility no-GVL path without scheduler offload guarantees
69
69
  - a C toolchain with C11 support
70
70
  - OpenSSL 3.0 or later with SHA3-256 and SHAKE256 available
71
71
  - vendored minimal PQ Code Package native snapshot in `ext/pqcrypto/vendor`
@@ -3,6 +3,8 @@
3
3
 
4
4
  require "mkmf"
5
5
  require "rbconfig"
6
+ require "rubygems/version"
7
+ require "shellwords"
6
8
  require_relative "../../lib/pq_crypto/version"
7
9
 
8
10
  def generate_version_header!
@@ -27,7 +29,14 @@ generate_version_header!
27
29
 
28
30
  $CFLAGS << " -std=c11 -Wall -Wextra -O3"
29
31
  $CFLAGS << " -fstack-protector-strong -D_FORTIFY_SOURCE=2"
30
- $LDFLAGS << " -Wl,-no_warn_duplicate_libraries" if RbConfig::CONFIG["host_os"] =~ /darwin/
32
+ if RbConfig::CONFIG["host_os"] =~ /darwin/
33
+ $DLDFLAGS = $DLDFLAGS.to_s
34
+ .gsub(/(?:\A|\s)-Wl,-multiply_defined,suppress(?=\s|\z)/, " ")
35
+ .gsub(/(?:\A|\s)-multiply_defined\s+suppress(?=\s|\z)/, " ")
36
+ .strip
37
+
38
+ $LDFLAGS << " -Wl,-no_warn_duplicate_libraries"
39
+ end
31
40
 
32
41
  VENDOR_ONLY_CFLAGS = "-Wno-unused-parameter -Wno-unused-function -Wno-strict-prototypes -Wno-pedantic -Wno-c23-extensions -Wno-undef"
33
42
 
@@ -94,30 +103,245 @@ if ENV["PQCRYPTO_NATIVE_TUNE"] == "1"
94
103
  VENDOR_ASM_ARCH_FLAGS << " -march=native -mtune=native"
95
104
  end
96
105
 
106
+ def openssl_library_dirs(prefix)
107
+ dirs = Dir.glob(File.join(prefix, "lib", "*-linux-gnu"))
108
+ dirs.concat([File.join(prefix, "lib64"), File.join(prefix, "lib")])
109
+ dirs.select { |dir| File.directory?(dir) }.uniq
110
+ end
111
+
112
+ def openssl_library_file(dir, name)
113
+ candidates = [
114
+ File.join(dir, "lib#{name}.so.3"),
115
+ *Dir.glob(File.join(dir, "lib#{name}.so.3.*")).sort,
116
+ File.join(dir, "lib#{name}.so"),
117
+ File.join(dir, "lib#{name}.3.dylib"),
118
+ *Dir.glob(File.join(dir, "lib#{name}.3.*.dylib")).sort,
119
+ File.join(dir, "lib#{name}.dylib"),
120
+ File.join(dir, "lib#{name}.a"),
121
+ File.join(dir, "lib#{name}.dll.a"),
122
+ File.join(dir, "#{name}.lib")
123
+ ]
124
+
125
+ path = candidates.find { |candidate| File.file?(candidate) }
126
+ path && File.realpath(path)
127
+ rescue Errno::ENOENT
128
+ nil
129
+ end
130
+
131
+ def openssl_library_present?(dir, name)
132
+ !openssl_library_file(dir, name).nil?
133
+ end
134
+
135
+ def openssl_prefix_usable?(prefix)
136
+ return false if prefix.nil?
137
+
138
+ prefix = prefix.strip
139
+ return false if prefix.empty?
140
+ return false unless File.exist?(File.join(prefix, "include", "openssl", "opensslv.h"))
141
+
142
+ openssl_library_dirs(prefix).any? do |dir|
143
+ openssl_library_present?(dir, "crypto")
144
+ end
145
+ end
146
+
147
+ def explicit_openssl_prefix
148
+ %w[OPENSSL_ROOT_DIR OPENSSL_DIR].each do |var|
149
+ value = ENV[var]
150
+ next if value.nil? || value.strip.empty?
151
+
152
+ prefix = File.expand_path(value.strip)
153
+ abort <<~MSG unless openssl_prefix_usable?(prefix)
154
+ #{var}=#{value.inspect} does not point to a complete OpenSSL installation.
155
+
156
+ Expected OpenSSL headers under:
157
+ #{File.join(prefix, "include", "openssl")}
158
+
159
+ And libcrypto under lib, lib64, or a Linux multiarch lib directory.
160
+ MSG
161
+
162
+ return prefix
163
+ end
164
+
165
+ nil
166
+ end
167
+
168
+ def resolve_openssl_prefix
169
+ explicit = explicit_openssl_prefix
170
+ return explicit if explicit
171
+
172
+ candidates = []
173
+ if find_executable("brew")
174
+ %w[openssl@3 openssl].each do |formula|
175
+ prefix = `brew --prefix #{formula} 2>/dev/null`.strip
176
+ candidates << prefix unless prefix.empty?
177
+ end
178
+ end
179
+ candidates.concat(["/opt/homebrew/opt/openssl@3", "/usr/local/opt/openssl@3"])
180
+ candidates.uniq.find { |prefix| openssl_prefix_usable?(prefix) }
181
+ end
182
+
183
+ def pkg_config_value(variable)
184
+ value = `pkg-config --variable=#{variable} openssl 2>/dev/null`.strip
185
+ return nil unless $?.success?
186
+ return nil if value.empty?
187
+
188
+ File.expand_path(value)
189
+ end
190
+
191
+ def openssl_pkg_config
192
+ return nil unless find_executable("pkg-config")
193
+
194
+ version = `pkg-config --modversion openssl 2>/dev/null`.strip
195
+ return nil unless $?.success?
196
+ return nil if version.empty?
197
+ return { version: version } unless openssl_version_at_least_3?(version)
198
+
199
+ incdir = pkg_config_value("includedir")
200
+ libdir = pkg_config_value("libdir")
201
+ return { version: version } unless incdir && libdir
202
+ return { version: version } unless File.exist?(File.join(incdir, "openssl", "opensslv.h"))
203
+ crypto_lib = openssl_library_file(libdir, "crypto")
204
+ return { version: version } unless crypto_lib
205
+
206
+ { version: version, incdir: incdir, libdir: libdir, crypto_lib: crypto_lib }
207
+ end
208
+
209
+ def openssl_version_at_least_3?(version)
210
+ Gem::Version.new(version) >= Gem::Version.new("3.0.0")
211
+ rescue ArgumentError
212
+ false
213
+ end
214
+
215
+ def normalize_existing_path(path)
216
+ File.realpath(path)
217
+ rescue Errno::ENOENT
218
+ File.expand_path(path)
219
+ end
220
+
221
+ def include_paths_from_flags(flags)
222
+ tokens = Shellwords.split(flags.to_s)
223
+ paths = []
224
+ index = 0
225
+
226
+ while index < tokens.length
227
+ token = tokens[index]
228
+
229
+ case token
230
+ when "-I", "-isystem"
231
+ paths << tokens[index + 1] if tokens[index + 1]
232
+ index += 2
233
+ next
234
+ when /\A-I(.+)\z/
235
+ paths << Regexp.last_match(1)
236
+ when /\A-isystem(.+)\z/
237
+ paths << Regexp.last_match(1)
238
+ end
239
+
240
+ index += 1
241
+ end
242
+
243
+ paths
244
+ rescue ArgumentError
245
+ []
246
+ end
247
+
248
+ def strip_include_path(flags, path)
249
+ escaped = Regexp.escape(path)
250
+
251
+ flags.to_s
252
+ .gsub(/(?:\A|\s)-I\s*#{escaped}(?=\s|\z)/, " ")
253
+ .gsub(/(?:\A|\s)-isystem\s*#{escaped}(?=\s|\z)/, " ")
254
+ .strip
255
+ end
256
+
257
+ def remove_competing_openssl_headers!(selected_incdir)
258
+ selected = normalize_existing_path(selected_incdir)
259
+ flag_values = [$INCFLAGS, $CPPFLAGS, $CFLAGS]
260
+
261
+ competing = flag_values
262
+ .flat_map { |flags| include_paths_from_flags(flags) }.uniq.select do |path|
263
+ next false unless File.file?(File.join(path, "openssl", "opensslv.h"))
264
+
265
+ normalize_existing_path(path) != selected
266
+ end
267
+
268
+ competing.each do |path|
269
+ $INCFLAGS = strip_include_path($INCFLAGS, path)
270
+ $CPPFLAGS = strip_include_path($CPPFLAGS, path)
271
+ $CFLAGS = strip_include_path($CFLAGS, path)
272
+ puts "Ignoring competing OpenSSL headers: #{path}"
273
+ end
274
+ end
275
+
276
+ def configure_openssl_paths(incdir, libdir, crypto_lib:)
277
+ if defined?($configure_args) && $configure_args
278
+ $configure_args["--with-openssl-dir"] = File.dirname(incdir)
279
+ $configure_args["--with-openssl-include"] = incdir
280
+ $configure_args["--with-openssl-lib"] = libdir
281
+ end
282
+
283
+ remove_competing_openssl_headers!(incdir)
284
+
285
+ $INCFLAGS = "-I#{incdir} #{$INCFLAGS}".strip
286
+ $CPPFLAGS = "-I#{incdir} #{$CPPFLAGS}".strip
287
+ $LIBPATH.unshift(libdir) unless $LIBPATH.include?(libdir)
288
+
289
+ selected_libs = [crypto_lib].map { |path| Shellwords.escape(path) }.join(" ")
290
+ $LOCAL_LIBS = "#{selected_libs} #{$LOCAL_LIBS}".strip
291
+
292
+ unless host_os =~ /mswin|mingw|cygwin/i || libdir.start_with?("/usr/lib")
293
+ $LDFLAGS = "-Wl,-rpath,#{Shellwords.escape(libdir)} #{$LDFLAGS}".strip
294
+ end
295
+ end
296
+
297
+ def configure_openssl_prefix(prefix, source:)
298
+ incdir = File.join(prefix, "include")
299
+ libdir = openssl_library_dirs(prefix).find do |dir|
300
+ openssl_library_present?(dir, "crypto")
301
+ end
302
+ crypto_lib = openssl_library_file(libdir, "crypto")
303
+
304
+ configure_openssl_paths(incdir, libdir, crypto_lib: crypto_lib)
305
+ puts "OpenSSL #{source}: #{prefix}"
306
+ puts "OpenSSL include dir: #{incdir}"
307
+ puts "OpenSSL library dir: #{libdir}"
308
+ puts "OpenSSL libcrypto: #{crypto_lib}"
309
+ end
310
+
97
311
  def configure_compiler_environment
98
- if RUBY_PLATFORM.include?("darwin")
99
- dir_config("homebrew", "/opt/homebrew")
100
- $CPPFLAGS << " -I/opt/homebrew/include"
101
- $LDFLAGS << " -L/opt/homebrew/lib"
312
+ prefix = resolve_openssl_prefix
313
+ if prefix
314
+ configure_openssl_prefix(prefix, source: "prefix")
102
315
  return
103
316
  end
104
317
 
105
- openssl_root = ENV["OPENSSL_ROOT_DIR"] || ENV["OPENSSL_DIR"]
106
- if openssl_root && !openssl_root.strip.empty? && File.directory?(openssl_root)
107
- $CPPFLAGS << " -I#{openssl_root}/include"
108
- %w[lib64 lib].each do |suffix|
109
- libdir = File.join(openssl_root, suffix)
110
- next unless File.directory?(libdir)
318
+ pkg = openssl_pkg_config
319
+ if pkg && pkg[:incdir] && pkg[:libdir]
320
+ configure_openssl_paths(pkg[:incdir], pkg[:libdir], crypto_lib: pkg[:crypto_lib])
321
+ puts "OpenSSL pkg-config: #{pkg[:version]}"
322
+ puts "OpenSSL include dir: #{pkg[:incdir]}"
323
+ puts "OpenSSL library dir: #{pkg[:libdir]}"
324
+ puts "OpenSSL libcrypto: #{pkg[:crypto_lib]}"
325
+ return
326
+ end
111
327
 
112
- $LDFLAGS << " -L#{libdir} -Wl,-rpath,#{libdir}"
113
- break
328
+ if pkg && pkg[:version]
329
+ if openssl_version_at_least_3?(pkg[:version])
330
+ puts "Ignoring incomplete pkg-config OpenSSL #{pkg[:version]}; headers or libraries were not found"
331
+ else
332
+ puts "Ignoring pkg-config OpenSSL #{pkg[:version]}; OpenSSL 3.0 or later is required"
114
333
  end
115
- elsif find_executable("pkg-config")
116
- cflags = `pkg-config --cflags openssl 2>/dev/null`.strip
117
- libs = `pkg-config --libs-only-L openssl 2>/dev/null`.strip
118
- $CPPFLAGS << " #{cflags}" unless cflags.empty?
119
- $LDFLAGS << " #{libs}" unless libs.empty?
334
+ else
335
+ puts "OpenSSL pkg-config: unavailable"
120
336
  end
337
+
338
+ conventional = ["/usr/local", "/usr"].find { |candidate| openssl_prefix_usable?(candidate) }
339
+ if conventional
340
+ configure_openssl_prefix(conventional, source: "system prefix")
341
+ return
342
+ end
343
+
344
+ puts "OpenSSL prefix: (none resolved; using compiler defaults)"
121
345
  end
122
346
 
123
347
  def native_vendor_sources_for(vendor_dir)
@@ -196,8 +420,6 @@ end
196
420
  def configure_openssl!
197
421
  configure_compiler_environment
198
422
 
199
- abort "OpenSSL libcrypto is required" unless have_library("crypto")
200
- abort "OpenSSL libssl is required" unless have_library("ssl")
201
423
  abort "openssl/evp.h is required" unless have_header("openssl/evp.h")
202
424
  abort "openssl/rand.h is required" unless have_header("openssl/rand.h")
203
425
  abort "openssl/crypto.h is required" unless have_header("openssl/crypto.h")
@@ -205,13 +427,31 @@ def configure_openssl!
205
427
  abort "openssl/pkcs12.h is required" unless have_header("openssl/pkcs12.h")
206
428
 
207
429
  version_check = <<~SRC
430
+ #include <openssl/crypto.h>
208
431
  #include <openssl/opensslv.h>
209
432
  #if OPENSSL_VERSION_NUMBER < 0x30000000L
210
433
  #error "OpenSSL 3.0 or later is required"
211
434
  #endif
212
- int main(void) { return 0; }
435
+ int main(void) {
436
+ return (int)OPENSSL_version_major();
437
+ }
213
438
  SRC
214
- abort "OpenSSL 3.0 or later is required" unless try_compile(version_check)
439
+ abort <<~MSG unless try_link(version_check)
440
+ OpenSSL 3.0 or later is required, but the compiler and linker did not find
441
+ a consistent OpenSSL 3 headers + libcrypto pair. See mkmf.log for the exact
442
+ include paths, library paths, and linker error.
443
+
444
+ macOS with Homebrew:
445
+ brew install openssl@3
446
+ OPENSSL_ROOT_DIR="$(brew --prefix openssl@3)" bundle exec rake clean compile
447
+
448
+ Debian/Ubuntu:
449
+ sudo apt-get install libssl-dev pkg-config
450
+ pkg-config --modversion openssl # must report 3.x
451
+
452
+ If an old Ruby toolchain injects OpenSSL 1.1 through CPPFLAGS or
453
+ PKG_CONFIG_PATH, inspect mkmf.log and point the build at OpenSSL 3.
454
+ MSG
215
455
 
216
456
  sha3_check = <<~SRC
217
457
  #include <openssl/evp.h>
@@ -220,7 +460,7 @@ def configure_openssl!
220
460
  return md == NULL ? 1 : 0;
221
461
  }
222
462
  SRC
223
- abort "OpenSSL SHA3-256 is required (X-Wing combiner)" unless try_compile(sha3_check)
463
+ abort "OpenSSL SHA3-256 is required (X-Wing combiner)" unless try_link(sha3_check)
224
464
 
225
465
  shake_check = <<~SRC
226
466
  #include <openssl/evp.h>
@@ -229,7 +469,7 @@ def configure_openssl!
229
469
  return md == NULL ? 1 : 0;
230
470
  }
231
471
  SRC
232
- abort "OpenSSL SHAKE256 is required (X-Wing key expansion / ML-DSA streaming mu)" unless try_compile(shake_check)
472
+ abort "OpenSSL SHAKE256 is required (X-Wing key expansion / ML-DSA streaming mu)" unless try_link(shake_check)
233
473
 
234
474
  $CFLAGS << " -DHAVE_OPENSSL_EVP_H -DHAVE_OPENSSL_RAND_H"
235
475
  end
@@ -2,6 +2,6 @@
2
2
  #ifndef PQCRYPTO_VERSION_H
3
3
  #define PQCRYPTO_VERSION_H
4
4
 
5
- #define PQCRYPTO_VERSION "0.6.3"
5
+ #define PQCRYPTO_VERSION "0.6.4"
6
6
 
7
7
  #endif
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PQCrypto
4
- VERSION = "0.6.3"
4
+ VERSION = "0.6.4"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pq_crypto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.3
4
+ version: 0.6.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Haydarov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-06-24 00:00:00.000000000 Z
11
+ date: 2026-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -356,14 +356,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
356
356
  requirements:
357
357
  - - ">="
358
358
  - !ruby/object:Gem::Version
359
- version: '3.1'
359
+ version: 2.7.1
360
360
  required_rubygems_version: !ruby/object:Gem::Requirement
361
361
  requirements:
362
362
  - - ">="
363
363
  - !ruby/object:Gem::Version
364
364
  version: '0'
365
365
  requirements: []
366
- rubygems_version: 3.3.27
366
+ rubygems_version: 3.4.22
367
367
  signing_key:
368
368
  specification_version: 4
369
369
  summary: Primitive-first post-quantum cryptography for Ruby