pq_crypto-seal 0.1.0

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.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +66 -0
  3. data/CHANGELOG.md +44 -0
  4. data/FORMAT.md +139 -0
  5. data/GET_STARTED.md +30 -0
  6. data/Gemfile +7 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +215 -0
  9. data/RELEASING.md +34 -0
  10. data/Rakefile +19 -0
  11. data/SECURITY.md +85 -0
  12. data/VENDORING.md +14 -0
  13. data/ext/pq_crypto_seal/aegis_unused_stubs.c +20 -0
  14. data/ext/pq_crypto_seal/extconf.rb +296 -0
  15. data/ext/pq_crypto_seal/pq_crypto_seal.c +390 -0
  16. data/ext/pq_crypto_seal/vendor/libaegis/LICENSE +21 -0
  17. data/ext/pq_crypto_seal/vendor/libaegis/TREE_SHA256 +1 -0
  18. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l.c +246 -0
  19. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_aesni.c +73 -0
  20. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_aesni.h +9 -0
  21. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_altivec.c +70 -0
  22. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_altivec.h +9 -0
  23. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_common.h +691 -0
  24. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_aes.c +77 -0
  25. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_aes.h +9 -0
  26. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_sha3.c +86 -0
  27. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_sha3.h +9 -0
  28. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_soft.c +63 -0
  29. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_soft.h +9 -0
  30. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/implementations.h +40 -0
  31. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2.c +247 -0
  32. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_aesni.c +108 -0
  33. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_aesni.h +9 -0
  34. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_altivec.c +103 -0
  35. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_altivec.h +9 -0
  36. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_avx2.c +79 -0
  37. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_avx2.h +11 -0
  38. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_common.h +795 -0
  39. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_neon_aes.c +111 -0
  40. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_neon_aes.h +9 -0
  41. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_soft.c +96 -0
  42. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_soft.h +9 -0
  43. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/implementations.h +40 -0
  44. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4.c +252 -0
  45. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_aesni.c +117 -0
  46. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_aesni.h +9 -0
  47. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_altivec.c +111 -0
  48. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_altivec.h +9 -0
  49. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx2.c +112 -0
  50. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx2.h +11 -0
  51. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx512.c +85 -0
  52. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx512.h +11 -0
  53. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_common.h +808 -0
  54. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_neon_aes.c +119 -0
  55. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_neon_aes.h +9 -0
  56. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_soft.c +104 -0
  57. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_soft.h +9 -0
  58. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/implementations.h +41 -0
  59. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256.c +240 -0
  60. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_aesni.c +68 -0
  61. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_aesni.h +9 -0
  62. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_altivec.c +68 -0
  63. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_altivec.h +9 -0
  64. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_common.h +673 -0
  65. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_neon_aes.c +75 -0
  66. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_neon_aes.h +9 -0
  67. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_soft.c +58 -0
  68. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_soft.h +9 -0
  69. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/implementations.h +39 -0
  70. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2.c +247 -0
  71. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_aesni.c +103 -0
  72. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_aesni.h +9 -0
  73. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_altivec.c +101 -0
  74. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_altivec.h +9 -0
  75. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_avx2.c +74 -0
  76. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_avx2.h +11 -0
  77. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_common.h +790 -0
  78. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_neon_aes.c +109 -0
  79. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_neon_aes.h +9 -0
  80. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_soft.c +91 -0
  81. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_soft.h +9 -0
  82. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/implementations.h +41 -0
  83. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4.c +252 -0
  84. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_aesni.c +112 -0
  85. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_aesni.h +9 -0
  86. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_altivec.c +109 -0
  87. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_altivec.h +9 -0
  88. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx2.c +107 -0
  89. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx2.h +11 -0
  90. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx512.c +79 -0
  91. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx512.h +11 -0
  92. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_common.h +805 -0
  93. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_neon_aes.c +117 -0
  94. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_neon_aes.h +9 -0
  95. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_soft.c +99 -0
  96. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_soft.h +9 -0
  97. data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/implementations.h +42 -0
  98. data/ext/pq_crypto_seal/vendor/libaegis/src/common/common.c +81 -0
  99. data/ext/pq_crypto_seal/vendor/libaegis/src/common/common.h +144 -0
  100. data/ext/pq_crypto_seal/vendor/libaegis/src/common/cpu.c +378 -0
  101. data/ext/pq_crypto_seal/vendor/libaegis/src/common/cpu.h +34 -0
  102. data/ext/pq_crypto_seal/vendor/libaegis/src/common/keccak.c +187 -0
  103. data/ext/pq_crypto_seal/vendor/libaegis/src/common/keccak.h +13 -0
  104. data/ext/pq_crypto_seal/vendor/libaegis/src/common/softaes.c +338 -0
  105. data/ext/pq_crypto_seal/vendor/libaegis/src/common/softaes.h +69 -0
  106. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis.h +77 -0
  107. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis128l.h +311 -0
  108. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis128x2.h +313 -0
  109. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis128x4.h +313 -0
  110. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis256.h +311 -0
  111. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis256x2.h +313 -0
  112. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis256x4.h +313 -0
  113. data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis_raf.h +580 -0
  114. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf.c +87 -0
  115. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis128l.c +20 -0
  116. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis128x2.c +20 -0
  117. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis128x4.c +20 -0
  118. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis256.c +20 -0
  119. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis256x2.c +20 -0
  120. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis256x4.c +20 -0
  121. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_internal.h +152 -0
  122. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_merkle.c +268 -0
  123. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_merkle.h +165 -0
  124. data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_variant.h +1119 -0
  125. data/fuzz/decrypt_fuzz.rb +119 -0
  126. data/lib/pq_crypto/seal/binary.rb +62 -0
  127. data/lib/pq_crypto/seal/core.rb +398 -0
  128. data/lib/pq_crypto/seal/errors.rb +15 -0
  129. data/lib/pq_crypto/seal/format.rb +275 -0
  130. data/lib/pq_crypto/seal/io.rb +336 -0
  131. data/lib/pq_crypto/seal/native.rb +13 -0
  132. data/lib/pq_crypto/seal/padding.rb +47 -0
  133. data/lib/pq_crypto/seal/version.rb +6 -0
  134. data/lib/pq_crypto/seal.rb +32 -0
  135. data/pq_crypto-seal.gemspec +37 -0
  136. data/script/vendor_libs.rb +79 -0
  137. data/test/aegis_vectors_test.rb +54 -0
  138. data/test/golden_vectors_test.rb +183 -0
  139. data/test/io_test.rb +125 -0
  140. data/test/native_test.rb +36 -0
  141. data/test/seal_test.rb +238 -0
  142. data/test/test_helper.rb +6 -0
  143. metadata +244 -0
data/VENDORING.md ADDED
@@ -0,0 +1,14 @@
1
+ # Vendored cryptography
2
+
3
+ `ext/pq_crypto_seal/vendor/libaegis/src` contains the C sources corresponding to
4
+ libaegis 0.10.3.
5
+
6
+ - Upstream: https://github.com/aegis-aead/libaegis
7
+ - Tag: `0.10.3`
8
+ - Tag commit: `0bb1639`
9
+ - Official tag archive SHA-256: `2f2682c1d08d9a5510caca1c82e3f8ea91f7085fef2ecbed0c398b2a921c79b1`
10
+ - License: MIT
11
+
12
+ The complete source snapshot is retained, while the Ruby extension compiles only the AEGIS-256 and common implementation files it exposes. No system libaegis is
13
+ loaded at runtime. Update the version, archive checksum, source snapshot, KATs,
14
+ and cross-backend tests together.
@@ -0,0 +1,20 @@
1
+ /*
2
+ * pq_crypto-seal exposes only AEGIS-256. libaegis common.c initializes all
3
+ * optional families, so source-only builds provide no-op selectors for the
4
+ * families deliberately not linked into this extension.
5
+ */
6
+ int aegis128l_pick_best_implementation(void) {
7
+ return 0;
8
+ }
9
+ int aegis128x2_pick_best_implementation(void) {
10
+ return 0;
11
+ }
12
+ int aegis128x4_pick_best_implementation(void) {
13
+ return 0;
14
+ }
15
+ int aegis256x2_pick_best_implementation(void) {
16
+ return 0;
17
+ }
18
+ int aegis256x4_pick_best_implementation(void) {
19
+ return 0;
20
+ }
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "pathname"
5
+ require "rbconfig"
6
+ require "shellwords"
7
+
8
+ extension_name = "pq_crypto/seal/pq_crypto_seal"
9
+
10
+ # Ruby 2.7 installations on macOS are often built against Homebrew OpenSSL 1.1.
11
+ # Their RbConfig can inject that old prefix into every extension build, even when
12
+ # OpenSSL 3 is installed separately. Select one complete OpenSSL 3 installation,
13
+ # remove competing OpenSSL header paths, and link the selected libcrypto by its
14
+ # absolute path so headers and library cannot silently come from different roots.
15
+
16
+ def command_output(*command)
17
+ IO.popen(command, err: File::NULL, &:read).to_s.strip
18
+ rescue StandardError
19
+ ""
20
+ end
21
+
22
+ def host_os
23
+ RbConfig::CONFIG.fetch("host_os", "")
24
+ end
25
+
26
+ def windows_host?
27
+ host_os =~ /mswin|mingw|cygwin/i
28
+ end
29
+
30
+ def openssl_library_dirs(prefix)
31
+ dirs = Dir.glob(File.join(prefix, "lib", "*-linux-gnu"))
32
+ dirs.concat([File.join(prefix, "lib64"), File.join(prefix, "lib")])
33
+ dirs.select { |directory| File.directory?(directory) }.uniq
34
+ end
35
+
36
+ def openssl_library_file(directory)
37
+ candidates = [
38
+ File.join(directory, "libcrypto.so.3"),
39
+ *Dir.glob(File.join(directory, "libcrypto.so.3.*")).sort,
40
+ File.join(directory, "libcrypto.so"),
41
+ File.join(directory, "libcrypto.3.dylib"),
42
+ *Dir.glob(File.join(directory, "libcrypto.3.*.dylib")).sort,
43
+ File.join(directory, "libcrypto.dylib"),
44
+ File.join(directory, "libcrypto.a"),
45
+ File.join(directory, "libcrypto.dll.a"),
46
+ File.join(directory, "crypto.lib")
47
+ ]
48
+
49
+ path = candidates.find { |candidate| File.file?(candidate) }
50
+ path && File.realpath(path)
51
+ rescue Errno::ENOENT
52
+ nil
53
+ end
54
+
55
+ def openssl_header_major(include_directory)
56
+ header = File.join(include_directory, "openssl", "opensslv.h")
57
+ return nil unless File.file?(header)
58
+
59
+ contents = File.read(header)
60
+ major = contents[/^\s*#\s*define\s+OPENSSL_VERSION_MAJOR\s+(\d+)/, 1]
61
+ return major.to_i if major
62
+
63
+ # OpenSSL 1.x has no OPENSSL_VERSION_MAJOR. Parse the high hexadecimal nibble
64
+ # only to reject it cleanly; OpenSSL 3 installations always define the major.
65
+ number = contents[/^\s*#\s*define\s+OPENSSL_VERSION_NUMBER\s+0x([0-9A-Fa-f]+)L?/, 1]
66
+ number ? (number.to_i(16) >> 28) & 0xF : nil
67
+ rescue StandardError
68
+ nil
69
+ end
70
+
71
+ def openssl_installation(include_directory, library_directory)
72
+ return nil unless include_directory && library_directory
73
+
74
+ include_directory = File.expand_path(include_directory)
75
+ library_directory = File.expand_path(library_directory)
76
+ return nil unless openssl_header_major(include_directory).to_i >= 3
77
+
78
+ crypto_library = openssl_library_file(library_directory)
79
+ return nil unless crypto_library
80
+
81
+ {
82
+ include_directory: include_directory,
83
+ library_directory: library_directory,
84
+ crypto_library: crypto_library
85
+ }
86
+ end
87
+
88
+ def openssl_prefix_installation(prefix)
89
+ return nil if prefix.nil? || prefix.to_s.strip.empty?
90
+
91
+ prefix = File.expand_path(prefix.to_s.strip)
92
+ include_directory = File.join(prefix, "include")
93
+ openssl_library_dirs(prefix).each do |library_directory|
94
+ installation = openssl_installation(include_directory, library_directory)
95
+ return installation.merge(prefix: prefix) if installation
96
+ end
97
+ nil
98
+ end
99
+
100
+ def argument_value(name)
101
+ prefix = "--#{name}"
102
+ ARGV.each_with_index do |argument, index|
103
+ return argument.split("=", 2)[1] if argument.start_with?("#{prefix}=")
104
+ return ARGV[index + 1] if argument == prefix && ARGV[index + 1]
105
+ end
106
+ nil
107
+ end
108
+
109
+ def explicit_openssl_installation
110
+ explicit_root = argument_value("with-openssl-dir") || ENV["OPENSSL_ROOT_DIR"] || ENV["OPENSSL_DIR"]
111
+ if explicit_root && !explicit_root.to_s.strip.empty?
112
+ installation = openssl_prefix_installation(explicit_root)
113
+ abort <<~MSG unless installation
114
+ #{explicit_root.inspect} does not point to a complete OpenSSL 3 installation.
115
+
116
+ Expected OpenSSL 3 headers below <prefix>/include/openssl and libcrypto
117
+ below <prefix>/lib, <prefix>/lib64, or a Linux multiarch lib directory.
118
+ MSG
119
+ return installation.merge(source: "explicit prefix")
120
+ end
121
+
122
+ explicit_include = argument_value("with-openssl-include")
123
+ explicit_library = argument_value("with-openssl-lib")
124
+ return nil unless explicit_include || explicit_library
125
+
126
+ abort "Both --with-openssl-include and --with-openssl-lib are required together" unless explicit_include && explicit_library
127
+ installation = openssl_installation(explicit_include, explicit_library)
128
+ abort "Explicit OpenSSL include/lib paths do not contain a matching OpenSSL 3 headers + libcrypto pair" unless installation
129
+
130
+ installation.merge(source: "explicit include/lib")
131
+ end
132
+
133
+ def pkg_config_openssl_installation
134
+ return nil unless find_executable("pkg-config")
135
+
136
+ version = command_output("pkg-config", "--modversion", "openssl")
137
+ return nil if version.empty? || version.split(".").first.to_i < 3
138
+
139
+ include_directory = command_output("pkg-config", "--variable=includedir", "openssl")
140
+ library_directory = command_output("pkg-config", "--variable=libdir", "openssl")
141
+ installation = openssl_installation(include_directory, library_directory)
142
+ installation && installation.merge(source: "pkg-config #{version}")
143
+ end
144
+
145
+ def discover_openssl3_installation
146
+ explicit = explicit_openssl_installation
147
+ return explicit if explicit
148
+
149
+ candidates = []
150
+ if host_os =~ /darwin/i && find_executable("brew")
151
+ candidates << command_output("brew", "--prefix", "openssl@3")
152
+ candidates << command_output("brew", "--prefix", "openssl")
153
+ end
154
+ candidates.concat(
155
+ %w[
156
+ /opt/homebrew/opt/openssl@3
157
+ /usr/local/opt/openssl@3
158
+ /opt/homebrew/opt/openssl
159
+ /usr/local/opt/openssl
160
+ ]
161
+ ) if host_os =~ /darwin/i
162
+
163
+ candidates.compact.map(&:strip).reject(&:empty?).uniq.each do |prefix|
164
+ installation = openssl_prefix_installation(prefix)
165
+ return installation.merge(source: "prefix", prefix: File.expand_path(prefix)) if installation
166
+ end
167
+
168
+ pkg_config_openssl_installation ||
169
+ openssl_prefix_installation("/usr/local")&.merge(source: "system prefix", prefix: "/usr/local") ||
170
+ openssl_prefix_installation("/usr")&.merge(source: "system prefix", prefix: "/usr")
171
+ end
172
+
173
+ def normalize_existing_path(path)
174
+ File.realpath(path)
175
+ rescue Errno::ENOENT
176
+ File.expand_path(path)
177
+ end
178
+
179
+ def include_paths_from_flags(flags)
180
+ tokens = Shellwords.split(flags.to_s)
181
+ paths = []
182
+ index = 0
183
+
184
+ while index < tokens.length
185
+ token = tokens[index]
186
+ case token
187
+ when "-I", "-isystem"
188
+ paths << tokens[index + 1] if tokens[index + 1]
189
+ index += 2
190
+ next
191
+ when /\A-I(.+)\z/
192
+ paths << Regexp.last_match(1)
193
+ when /\A-isystem(.+)\z/
194
+ paths << Regexp.last_match(1)
195
+ end
196
+ index += 1
197
+ end
198
+
199
+ paths
200
+ rescue ArgumentError
201
+ []
202
+ end
203
+
204
+ def strip_include_path(flags, path)
205
+ escaped = Regexp.escape(path)
206
+ flags.to_s
207
+ .gsub(/(?:\A|\s)-I\s*#{escaped}(?=\s|\z)/, " ")
208
+ .gsub(/(?:\A|\s)-isystem\s*#{escaped}(?=\s|\z)/, " ")
209
+ .strip
210
+ end
211
+
212
+ def remove_competing_openssl_headers!(selected_include_directory)
213
+ selected = normalize_existing_path(selected_include_directory)
214
+ competing = [$INCFLAGS, $CPPFLAGS, $CFLAGS]
215
+ .flat_map { |flags| include_paths_from_flags(flags) }
216
+ .uniq
217
+ .select do |path|
218
+ File.file?(File.join(path, "openssl", "opensslv.h")) && normalize_existing_path(path) != selected
219
+ end
220
+
221
+ competing.each do |path|
222
+ $INCFLAGS = strip_include_path($INCFLAGS, path)
223
+ $CPPFLAGS = strip_include_path($CPPFLAGS, path)
224
+ $CFLAGS = strip_include_path($CFLAGS, path)
225
+ puts "Ignoring competing OpenSSL headers: #{path}"
226
+ end
227
+ end
228
+
229
+ def configure_selected_openssl!(installation)
230
+ include_directory = installation.fetch(:include_directory)
231
+ library_directory = installation.fetch(:library_directory)
232
+ crypto_library = installation.fetch(:crypto_library)
233
+
234
+ remove_competing_openssl_headers!(include_directory)
235
+
236
+ # Do not call dir_config("openssl") here. On Ruby 2.7 it can reuse the
237
+ # --with-openssl-dir value from Ruby's own build and reintroduce OpenSSL 1.1.
238
+ $INCFLAGS = "-I#{Shellwords.escape(include_directory)} #{$INCFLAGS}".strip
239
+ $CPPFLAGS = "-I#{Shellwords.escape(include_directory)} #{$CPPFLAGS}".strip
240
+ $LIBPATH.unshift(library_directory) unless $LIBPATH.include?(library_directory)
241
+ $LOCAL_LIBS = "#{Shellwords.escape(crypto_library)} #{$LOCAL_LIBS}".strip
242
+
243
+ unless windows_host? || library_directory.start_with?("/usr/lib")
244
+ $LDFLAGS = "-Wl,-rpath,#{Shellwords.escape(library_directory)} #{$LDFLAGS}".strip
245
+ end
246
+
247
+ puts "OpenSSL source: #{installation.fetch(:source)}"
248
+ puts "OpenSSL prefix: #{installation[:prefix]}" if installation[:prefix]
249
+ puts "OpenSSL include dir: #{include_directory}"
250
+ puts "OpenSSL library dir: #{library_directory}"
251
+ puts "OpenSSL libcrypto: #{crypto_library}"
252
+ end
253
+
254
+ selected_openssl = discover_openssl3_installation
255
+ if selected_openssl
256
+ configure_selected_openssl!(selected_openssl)
257
+ else
258
+ puts "OpenSSL 3 prefix: none resolved; using compiler defaults"
259
+ abort "OpenSSL libcrypto is required" unless have_library("crypto")
260
+ end
261
+
262
+ %w[openssl/evp.h openssl/hmac.h openssl/rand.h openssl/crypto.h openssl/opensslv.h].each do |header|
263
+ abort "#{header} is required" unless have_header(header)
264
+ end
265
+
266
+ if selected_openssl
267
+ header_major = openssl_header_major(selected_openssl.fetch(:include_directory))
268
+ abort "OpenSSL 3.0 or newer headers are required" unless header_major && header_major >= 3
269
+ puts "OpenSSL header major: #{header_major}"
270
+ end
271
+
272
+ abort "rb_thread_call_without_gvl is required" unless have_func("rb_thread_call_without_gvl", "ruby/thread.h")
273
+
274
+ $CFLAGS << " -std=c11 -O3 -Wall -Wextra"
275
+ if ENV["PQC_SEAL_SANITIZE"] == "1"
276
+ $CFLAGS << " -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined"
277
+ $LDFLAGS << " -fsanitize=address,undefined"
278
+ end
279
+
280
+ vendor = Pathname(__dir__).join("vendor/libaegis/src")
281
+ patterns = %w[
282
+ aegis256/*.c common/*.c
283
+ ]
284
+ sources = patterns.flat_map { |pattern| Dir[vendor.join(pattern).to_s] }.sort
285
+ abort "vendored libaegis sources are missing" if sources.empty?
286
+
287
+ $INCFLAGS << " -I$(srcdir)/vendor/libaegis/src/include"
288
+ source_dirs = sources.map { |source| File.dirname(source) }.uniq
289
+ source_dirs.each do |directory|
290
+ relative = Pathname(directory).relative_path_from(Pathname(__dir__))
291
+ $VPATH << "$(srcdir)/#{relative}"
292
+ $INCFLAGS << " -I$(srcdir)/#{relative}"
293
+ end
294
+ $srcs = ["pq_crypto_seal.c", "aegis_unused_stubs.c"] + sources.map { |source| File.basename(source) }
295
+
296
+ create_makefile(extension_name)