pq_crypto 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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +37 -0
- data/CHANGELOG.md +29 -0
- data/GET_STARTED.md +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +135 -0
- data/SECURITY.md +57 -0
- data/ext/pqcrypto/extconf.rb +157 -0
- data/ext/pqcrypto/mldsa_api.h +51 -0
- data/ext/pqcrypto/mlkem_api.h +21 -0
- data/ext/pqcrypto/pqcrypto_ruby_secure.c +889 -0
- data/ext/pqcrypto/pqcrypto_secure.c +1178 -0
- data/ext/pqcrypto/pqcrypto_secure.h +135 -0
- data/ext/pqcrypto/vendor/.vendored +5 -0
- data/ext/pqcrypto/vendor/pqclean/common/aes.c +639 -0
- data/ext/pqcrypto/vendor/pqclean/common/aes.h +64 -0
- data/ext/pqcrypto/vendor/pqclean/common/compat.h +73 -0
- data/ext/pqcrypto/vendor/pqclean/common/crypto_declassify.h +7 -0
- data/ext/pqcrypto/vendor/pqclean/common/fips202.c +928 -0
- data/ext/pqcrypto/vendor/pqclean/common/fips202.h +166 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak2x/feat.S +168 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.c +684 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.h +60 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SIMD256.c +1028 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SnP.h +50 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-unrolling.macros +198 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile +8 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile.Microsoft_nmake +8 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/SIMD256-config.h +3 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/align.h +34 -0
- data/ext/pqcrypto/vendor/pqclean/common/keccak4x/brg_endian.h +142 -0
- data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.c +101 -0
- data/ext/pqcrypto/vendor/pqclean/common/nistseedexpander.h +39 -0
- data/ext/pqcrypto/vendor/pqclean/common/randombytes.c +355 -0
- data/ext/pqcrypto/vendor/pqclean/common/randombytes.h +27 -0
- data/ext/pqcrypto/vendor/pqclean/common/sha2.c +769 -0
- data/ext/pqcrypto/vendor/pqclean/common/sha2.h +173 -0
- data/ext/pqcrypto/vendor/pqclean/common/sp800-185.c +156 -0
- data/ext/pqcrypto/vendor/pqclean/common/sp800-185.h +27 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/api.h +18 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.c +83 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.h +11 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.c +327 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.h +22 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.c +164 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.h +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.c +146 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/params.h +36 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.c +299 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.h +37 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.c +188 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.h +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.c +41 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric-shake.c +71 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric.h +30 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.c +67 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.h +13 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/LICENSE +5 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile +19 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile.Microsoft_nmake +23 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/api.h +50 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.c +98 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.h +10 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.c +261 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.h +31 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/params.h +44 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.c +799 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.h +52 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.c +415 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.h +65 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.c +69 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.h +17 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.c +92 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.h +14 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.c +407 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.h +47 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric-shake.c +26 -0
- data/ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric.h +34 -0
- data/lib/pq_crypto/errors.rb +10 -0
- data/lib/pq_crypto/hybrid_kem.rb +106 -0
- data/lib/pq_crypto/kem.rb +199 -0
- data/lib/pq_crypto/serialization.rb +102 -0
- data/lib/pq_crypto/signature.rb +198 -0
- data/lib/pq_crypto/version.rb +5 -0
- data/lib/pq_crypto.rb +177 -0
- data/lib/pqcrypto.rb +3 -0
- data/script/vendor_libs.rb +199 -0
- metadata +195 -0
data/lib/pq_crypto.rb
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rbconfig"
|
|
4
|
+
require_relative "pq_crypto/version"
|
|
5
|
+
require_relative "pq_crypto/errors"
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require "pqcrypto/pqcrypto_secure"
|
|
9
|
+
rescue LoadError => original_error
|
|
10
|
+
ext_dir = File.expand_path("pqcrypto", __dir__)
|
|
11
|
+
extensions = [".#{RbConfig::CONFIG.fetch('DLEXT')}", ".bundle", ".so"].uniq
|
|
12
|
+
search_dirs = [ext_dir, File.join(ext_dir, "pqcrypto")].uniq
|
|
13
|
+
candidates = search_dirs.flat_map do |dir|
|
|
14
|
+
extensions.map { |ext| File.join(dir, "pqcrypto_secure#{ext}") }
|
|
15
|
+
end
|
|
16
|
+
existing = candidates.select { |path| File.exist?(path) }
|
|
17
|
+
|
|
18
|
+
raise LoadError,
|
|
19
|
+
"Could not find compiled PQCrypto extension. Run: bundle exec rake compile" if existing.empty?
|
|
20
|
+
|
|
21
|
+
loaded = existing.any? do |path|
|
|
22
|
+
begin
|
|
23
|
+
require path
|
|
24
|
+
true
|
|
25
|
+
rescue LoadError
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
raise original_error unless loaded
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
require_relative "pq_crypto/serialization"
|
|
34
|
+
|
|
35
|
+
module PQCrypto
|
|
36
|
+
SUITES = {
|
|
37
|
+
kem: [:ml_kem_768].freeze,
|
|
38
|
+
hybrid_kem: [:ml_kem_768_x25519_hkdf_sha256].freeze,
|
|
39
|
+
signature: [:ml_dsa_65].freeze,
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
NATIVE_EXTENSION_LOADED = true unless const_defined?(:NATIVE_EXTENSION_LOADED)
|
|
43
|
+
|
|
44
|
+
class << self
|
|
45
|
+
unless private_method_defined?(:native_ml_kem_keypair)
|
|
46
|
+
alias_method :native_ml_kem_keypair, :ml_kem_keypair
|
|
47
|
+
alias_method :native_ml_kem_encapsulate, :ml_kem_encapsulate
|
|
48
|
+
alias_method :native_ml_kem_decapsulate, :ml_kem_decapsulate
|
|
49
|
+
alias_method :native_hybrid_kem_keypair, :hybrid_kem_keypair
|
|
50
|
+
alias_method :native_hybrid_kem_encapsulate, :hybrid_kem_encapsulate
|
|
51
|
+
alias_method :native_hybrid_kem_decapsulate, :hybrid_kem_decapsulate
|
|
52
|
+
alias_method :native_sign_keypair, :sign_keypair
|
|
53
|
+
alias_method :native_sign, :sign
|
|
54
|
+
alias_method :native_verify, :verify
|
|
55
|
+
alias_method :native_secure_wipe, :secure_wipe
|
|
56
|
+
alias_method :native_version, :version
|
|
57
|
+
alias_method :native_public_key_to_pqc_container_der, :public_key_to_pqc_container_der
|
|
58
|
+
alias_method :native_public_key_to_pqc_container_pem, :public_key_to_pqc_container_pem
|
|
59
|
+
alias_method :native_secret_key_to_pqc_container_der, :secret_key_to_pqc_container_der
|
|
60
|
+
alias_method :native_secret_key_to_pqc_container_pem, :secret_key_to_pqc_container_pem
|
|
61
|
+
alias_method :native_public_key_from_pqc_container_der, :public_key_from_pqc_container_der
|
|
62
|
+
alias_method :native_public_key_from_pqc_container_pem, :public_key_from_pqc_container_pem
|
|
63
|
+
alias_method :native_secret_key_from_pqc_container_der, :secret_key_from_pqc_container_der
|
|
64
|
+
alias_method :native_secret_key_from_pqc_container_pem, :secret_key_from_pqc_container_pem
|
|
65
|
+
alias_method :native_test_ml_kem_keypair_from_seed, :__test_ml_kem_keypair_from_seed
|
|
66
|
+
alias_method :native_test_ml_kem_encapsulate_from_seed, :__test_ml_kem_encapsulate_from_seed
|
|
67
|
+
alias_method :native_test_sign_keypair_from_seed, :__test_sign_keypair_from_seed
|
|
68
|
+
alias_method :native_test_sign_from_seed, :__test_sign_from_seed
|
|
69
|
+
|
|
70
|
+
private :native_ml_kem_keypair,
|
|
71
|
+
:native_ml_kem_encapsulate,
|
|
72
|
+
:native_ml_kem_decapsulate,
|
|
73
|
+
:native_hybrid_kem_keypair,
|
|
74
|
+
:native_hybrid_kem_encapsulate,
|
|
75
|
+
:native_hybrid_kem_decapsulate,
|
|
76
|
+
:native_sign_keypair,
|
|
77
|
+
:native_sign,
|
|
78
|
+
:native_verify,
|
|
79
|
+
:native_secure_wipe,
|
|
80
|
+
:native_version,
|
|
81
|
+
:native_public_key_to_pqc_container_der,
|
|
82
|
+
:native_public_key_to_pqc_container_pem,
|
|
83
|
+
:native_secret_key_to_pqc_container_der,
|
|
84
|
+
:native_secret_key_to_pqc_container_pem,
|
|
85
|
+
:native_public_key_from_pqc_container_der,
|
|
86
|
+
:native_public_key_from_pqc_container_pem,
|
|
87
|
+
:native_secret_key_from_pqc_container_der,
|
|
88
|
+
:native_secret_key_from_pqc_container_pem,
|
|
89
|
+
:native_test_ml_kem_keypair_from_seed,
|
|
90
|
+
:native_test_ml_kem_encapsulate_from_seed,
|
|
91
|
+
:native_test_sign_keypair_from_seed,
|
|
92
|
+
:native_test_sign_from_seed,
|
|
93
|
+
:ml_kem_keypair,
|
|
94
|
+
:ml_kem_encapsulate,
|
|
95
|
+
:ml_kem_decapsulate,
|
|
96
|
+
:hybrid_kem_keypair,
|
|
97
|
+
:hybrid_kem_encapsulate,
|
|
98
|
+
:hybrid_kem_decapsulate,
|
|
99
|
+
:sign_keypair,
|
|
100
|
+
:sign,
|
|
101
|
+
:verify,
|
|
102
|
+
:public_key_to_pqc_container_der,
|
|
103
|
+
:public_key_to_pqc_container_pem,
|
|
104
|
+
:secret_key_to_pqc_container_der,
|
|
105
|
+
:secret_key_to_pqc_container_pem,
|
|
106
|
+
:public_key_from_pqc_container_der,
|
|
107
|
+
:public_key_from_pqc_container_pem,
|
|
108
|
+
:secret_key_from_pqc_container_der,
|
|
109
|
+
:secret_key_from_pqc_container_pem,
|
|
110
|
+
:__test_ml_kem_keypair_from_seed,
|
|
111
|
+
:__test_ml_kem_encapsulate_from_seed,
|
|
112
|
+
:__test_sign_keypair_from_seed,
|
|
113
|
+
:__test_sign_from_seed
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def version
|
|
117
|
+
native_version
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def backend
|
|
121
|
+
:native_pqclean
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def native_extension_loaded?
|
|
125
|
+
true
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def supported_kems
|
|
129
|
+
SUITES.fetch(:kem).dup
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def supported_hybrid_kems
|
|
133
|
+
SUITES.fetch(:hybrid_kem).dup
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def supported_signatures
|
|
137
|
+
SUITES.fetch(:signature).dup
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def secure_wipe(string)
|
|
141
|
+
string = String(string)
|
|
142
|
+
raise ArgumentError, "secure_wipe requires a mutable String" if string.frozen?
|
|
143
|
+
|
|
144
|
+
native_secure_wipe(string)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
module Testing
|
|
149
|
+
def self.ml_kem_keypair_from_seed(seed)
|
|
150
|
+
PQCrypto.__send__(:native_test_ml_kem_keypair_from_seed, String(seed).b)
|
|
151
|
+
rescue ArgumentError => e
|
|
152
|
+
raise InvalidKeyError, e.message
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def self.ml_kem_encapsulate_from_seed(public_key, seed)
|
|
156
|
+
PQCrypto.__send__(:native_test_ml_kem_encapsulate_from_seed, String(public_key).b, String(seed).b)
|
|
157
|
+
rescue ArgumentError => e
|
|
158
|
+
raise InvalidKeyError, e.message
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def self.ml_dsa_keypair_from_seed(seed)
|
|
162
|
+
PQCrypto.__send__(:native_test_sign_keypair_from_seed, String(seed).b)
|
|
163
|
+
rescue ArgumentError => e
|
|
164
|
+
raise InvalidKeyError, e.message
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def self.ml_dsa_sign_from_seed(message, secret_key, seed)
|
|
168
|
+
PQCrypto.__send__(:native_test_sign_from_seed, String(message).b, String(secret_key).b, String(seed).b)
|
|
169
|
+
rescue ArgumentError => e
|
|
170
|
+
raise InvalidKeyError, e.message
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
require_relative "pq_crypto/kem"
|
|
176
|
+
require_relative "pq_crypto/hybrid_kem"
|
|
177
|
+
require_relative "pq_crypto/signature"
|
data/lib/pqcrypto.rb
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "digest"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "open-uri"
|
|
7
|
+
require "rubygems/package"
|
|
8
|
+
require "tmpdir"
|
|
9
|
+
require "zlib"
|
|
10
|
+
|
|
11
|
+
VENDOR_DIR = File.expand_path("../ext/pqcrypto/vendor", __dir__)
|
|
12
|
+
MANIFEST_PATH = File.join(VENDOR_DIR, ".vendored")
|
|
13
|
+
|
|
14
|
+
DEFAULT_PQCLEAN = {
|
|
15
|
+
version: "2cc64716044832eea747234ddbffc06746ab815d",
|
|
16
|
+
url: "https://github.com/PQClean/PQClean/archive/2cc64716044832eea747234ddbffc06746ab815d.tar.gz",
|
|
17
|
+
strip: "PQClean-2cc64716044832eea747234ddbffc06746ab815d",
|
|
18
|
+
sha256: "0e92076a79082a8d220e27227f37b280fb2ce050af412babd2bc755ab37b871a"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
21
|
+
KEEP_DIRS = %w[
|
|
22
|
+
crypto_kem/ml-kem-768/clean
|
|
23
|
+
crypto_sign/ml-dsa-65/clean
|
|
24
|
+
common
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
WARNING = <<~TEXT.freeze
|
|
28
|
+
WARNING: this script is a manual vendor refresh tool.
|
|
29
|
+
|
|
30
|
+
pq_crypto relies on the vendored PQClean snapshot committed to the repository.
|
|
31
|
+
Running this script will replace ext/pqcrypto/vendor with a fresh upstream copy.
|
|
32
|
+
Use the pinned defaults unless you are intentionally updating the upstream snapshot.
|
|
33
|
+
TEXT
|
|
34
|
+
|
|
35
|
+
def load_manifest(path)
|
|
36
|
+
return {} unless File.exist?(path)
|
|
37
|
+
|
|
38
|
+
File.readlines(path, chomp: true).each_with_object({}) do |line, acc|
|
|
39
|
+
next if line.strip.empty? || line.lstrip.start_with?("#")
|
|
40
|
+
key, value = line.split("=", 2)
|
|
41
|
+
next if key.nil? || value.nil?
|
|
42
|
+
|
|
43
|
+
acc[key] = value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def manifest_value(manifest, key)
|
|
48
|
+
value = manifest[key]
|
|
49
|
+
return nil if value.nil? || value.strip.empty? || value == "unrecorded"
|
|
50
|
+
|
|
51
|
+
value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def commit_archive_url?(url)
|
|
55
|
+
url.match?(%r{\Ahttps://(?:github\.com|codeload\.github\.com)/PQClean/PQClean/(?:archive|tar\.gz)/[0-9a-f]{40}(?:\.tar\.gz)?\z}i) ||
|
|
56
|
+
url.match?(%r{\Ahttps://github\.com/PQClean/PQClean/archive/[0-9a-f]{40}\.tar\.gz\z}i)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def build_vendor_config
|
|
60
|
+
manifest = load_manifest(MANIFEST_PATH)
|
|
61
|
+
|
|
62
|
+
version = ENV["PQCLEAN_VERSION"] || manifest_value(manifest, "pqclean_version") || DEFAULT_PQCLEAN[:version]
|
|
63
|
+
url = ENV["PQCLEAN_URL"] || manifest_value(manifest, "pqclean_url") || DEFAULT_PQCLEAN[:url]
|
|
64
|
+
sha256 = ENV["PQCLEAN_SHA256"] || manifest_value(manifest, "pqclean_archive_sha256") || DEFAULT_PQCLEAN[:sha256]
|
|
65
|
+
strip = ENV["PQCLEAN_STRIP"] || manifest_value(manifest, "pqclean_strip") || DEFAULT_PQCLEAN[:strip]
|
|
66
|
+
|
|
67
|
+
{
|
|
68
|
+
version: version,
|
|
69
|
+
url: url,
|
|
70
|
+
sha256: sha256,
|
|
71
|
+
strip: strip,
|
|
72
|
+
keep: KEEP_DIRS
|
|
73
|
+
}.freeze
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def validate_vendor_config!(config)
|
|
77
|
+
required = %i[version url strip]
|
|
78
|
+
missing = required.select { |key| config[key].to_s.strip.empty? }
|
|
79
|
+
return if missing.empty?
|
|
80
|
+
|
|
81
|
+
abort <<~MSG
|
|
82
|
+
Missing required vendoring configuration: #{missing.join(", ")}
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
PQCLEAN_VERSION=<full-git-commit> \
|
|
86
|
+
PQCLEAN_URL=https://github.com/PQClean/PQClean/archive/<full-git-commit>.tar.gz \
|
|
87
|
+
PQCLEAN_STRIP=PQClean-<full-git-commit> \
|
|
88
|
+
PQCLEAN_SHA256=<archive-sha256> \
|
|
89
|
+
bundle exec ruby script/vendor_libs.rb
|
|
90
|
+
MSG
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def validate_pinning!(config)
|
|
94
|
+
if config[:sha256].to_s.strip.empty?
|
|
95
|
+
abort <<~MSG
|
|
96
|
+
Refusing to vendor without PQCLEAN_SHA256.
|
|
97
|
+
|
|
98
|
+
Use the built-in pinned defaults, or provide all of:
|
|
99
|
+
PQCLEAN_VERSION
|
|
100
|
+
PQCLEAN_URL
|
|
101
|
+
PQCLEAN_STRIP
|
|
102
|
+
PQCLEAN_SHA256
|
|
103
|
+
MSG
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
return if commit_archive_url?(config[:url])
|
|
107
|
+
|
|
108
|
+
abort <<~MSG
|
|
109
|
+
Refusing to vendor from a non-commit archive URL.
|
|
110
|
+
|
|
111
|
+
Use a content-addressed full commit archive URL from the PQClean repository.
|
|
112
|
+
MSG
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def download(url, destination)
|
|
116
|
+
puts "Downloading #{url}"
|
|
117
|
+
URI.open(url) { |remote| File.binwrite(destination, remote.read) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def verify_checksum!(archive, expected_sha256)
|
|
121
|
+
actual = Digest::SHA256.file(archive).hexdigest
|
|
122
|
+
abort "SHA256 mismatch: expected #{expected_sha256}, got #{actual}" unless actual == expected_sha256
|
|
123
|
+
|
|
124
|
+
actual
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def extract_subset(archive, destination, strip_prefix:, keep_dirs:)
|
|
128
|
+
prefix_re = /\A#{Regexp.escape(strip_prefix)}\//
|
|
129
|
+
|
|
130
|
+
Gem::Package::TarReader.new(Zlib::GzipReader.open(archive)) do |tar|
|
|
131
|
+
tar.each do |entry|
|
|
132
|
+
relative_path = entry.full_name.sub(prefix_re, "")
|
|
133
|
+
next if relative_path.empty? || relative_path == entry.full_name
|
|
134
|
+
next unless keep_dirs.any? { |dir| relative_path.start_with?(dir) }
|
|
135
|
+
|
|
136
|
+
target = File.join(destination, relative_path)
|
|
137
|
+
|
|
138
|
+
if entry.directory?
|
|
139
|
+
FileUtils.mkdir_p(target)
|
|
140
|
+
elsif entry.file?
|
|
141
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
142
|
+
File.binwrite(target, entry.read)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def write_manifest!(config:, archive_sha256:, tree_sha256:)
|
|
149
|
+
File.write(
|
|
150
|
+
MANIFEST_PATH,
|
|
151
|
+
<<~TEXT
|
|
152
|
+
pqclean_version=#{config[:version]}
|
|
153
|
+
pqclean_url=#{config[:url]}
|
|
154
|
+
pqclean_archive_sha256=#{archive_sha256}
|
|
155
|
+
pqclean_strip=#{config[:strip]}
|
|
156
|
+
pqclean_tree_sha256=#{tree_sha256}
|
|
157
|
+
TEXT
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def tree_sha256_for(directory)
|
|
162
|
+
entries = Dir.glob(File.join(directory, "**", "*"), File::FNM_DOTMATCH)
|
|
163
|
+
.reject { |path| File.directory?(path) }
|
|
164
|
+
.sort
|
|
165
|
+
|
|
166
|
+
digest = Digest::SHA256.new
|
|
167
|
+
entries.each do |path|
|
|
168
|
+
relative = path.delete_prefix("#{directory}/")
|
|
169
|
+
digest << relative << "\0"
|
|
170
|
+
digest << File.binread(path)
|
|
171
|
+
digest << "\0"
|
|
172
|
+
end
|
|
173
|
+
digest.hexdigest
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
config = build_vendor_config
|
|
177
|
+
validate_vendor_config!(config)
|
|
178
|
+
validate_pinning!(config)
|
|
179
|
+
|
|
180
|
+
puts WARNING
|
|
181
|
+
puts "Vendoring PQClean into #{VENDOR_DIR}"
|
|
182
|
+
puts "Pinned ref: #{config[:version]}"
|
|
183
|
+
puts "Archive checksum: #{config[:sha256]}"
|
|
184
|
+
|
|
185
|
+
FileUtils.rm_rf(VENDOR_DIR)
|
|
186
|
+
FileUtils.mkdir_p(VENDOR_DIR)
|
|
187
|
+
|
|
188
|
+
Dir.mktmpdir("pq_crypto-vendor") do |tmpdir|
|
|
189
|
+
archive = File.join(tmpdir, "pqclean.tar.gz")
|
|
190
|
+
destination = File.join(VENDOR_DIR, "pqclean")
|
|
191
|
+
|
|
192
|
+
download(config[:url], archive)
|
|
193
|
+
archive_sha256 = verify_checksum!(archive, config[:sha256])
|
|
194
|
+
extract_subset(archive, destination, strip_prefix: config[:strip], keep_dirs: config[:keep])
|
|
195
|
+
write_manifest!(config: config, archive_sha256: archive_sha256, tree_sha256: tree_sha256_for(destination))
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
puts "Done. PQClean sources are now available in ext/pqcrypto/vendor/."
|
|
199
|
+
puts "Next step: review vendor diffs, then bundle exec rake compile"
|
metadata
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pq_crypto
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Roman Haydarov
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-20 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: rake
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '13.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '13.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake-compiler
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.2'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.2'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: minitest
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '5.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '5.0'
|
|
69
|
+
description: Native Ruby wrapper around ML-KEM-768, ML-DSA-65, and an optional hybrid
|
|
70
|
+
ML-KEM-768+X25519 KEM, backed by PQClean and OpenSSL.
|
|
71
|
+
email:
|
|
72
|
+
- romanhajdarov@gmail.com
|
|
73
|
+
executables: []
|
|
74
|
+
extensions:
|
|
75
|
+
- ext/pqcrypto/extconf.rb
|
|
76
|
+
extra_rdoc_files: []
|
|
77
|
+
files:
|
|
78
|
+
- ".github/workflows/ci.yml"
|
|
79
|
+
- CHANGELOG.md
|
|
80
|
+
- GET_STARTED.md
|
|
81
|
+
- LICENSE.txt
|
|
82
|
+
- README.md
|
|
83
|
+
- SECURITY.md
|
|
84
|
+
- ext/pqcrypto/extconf.rb
|
|
85
|
+
- ext/pqcrypto/mldsa_api.h
|
|
86
|
+
- ext/pqcrypto/mlkem_api.h
|
|
87
|
+
- ext/pqcrypto/pqcrypto_ruby_secure.c
|
|
88
|
+
- ext/pqcrypto/pqcrypto_secure.c
|
|
89
|
+
- ext/pqcrypto/pqcrypto_secure.h
|
|
90
|
+
- ext/pqcrypto/vendor/.vendored
|
|
91
|
+
- ext/pqcrypto/vendor/pqclean/common/aes.c
|
|
92
|
+
- ext/pqcrypto/vendor/pqclean/common/aes.h
|
|
93
|
+
- ext/pqcrypto/vendor/pqclean/common/compat.h
|
|
94
|
+
- ext/pqcrypto/vendor/pqclean/common/crypto_declassify.h
|
|
95
|
+
- ext/pqcrypto/vendor/pqclean/common/fips202.c
|
|
96
|
+
- ext/pqcrypto/vendor/pqclean/common/fips202.h
|
|
97
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak2x/feat.S
|
|
98
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.c
|
|
99
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak2x/fips202x2.h
|
|
100
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SIMD256.c
|
|
101
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-times4-SnP.h
|
|
102
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/KeccakP-1600-unrolling.macros
|
|
103
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile
|
|
104
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/Makefile.Microsoft_nmake
|
|
105
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/SIMD256-config.h
|
|
106
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/align.h
|
|
107
|
+
- ext/pqcrypto/vendor/pqclean/common/keccak4x/brg_endian.h
|
|
108
|
+
- ext/pqcrypto/vendor/pqclean/common/nistseedexpander.c
|
|
109
|
+
- ext/pqcrypto/vendor/pqclean/common/nistseedexpander.h
|
|
110
|
+
- ext/pqcrypto/vendor/pqclean/common/randombytes.c
|
|
111
|
+
- ext/pqcrypto/vendor/pqclean/common/randombytes.h
|
|
112
|
+
- ext/pqcrypto/vendor/pqclean/common/sha2.c
|
|
113
|
+
- ext/pqcrypto/vendor/pqclean/common/sha2.h
|
|
114
|
+
- ext/pqcrypto/vendor/pqclean/common/sp800-185.c
|
|
115
|
+
- ext/pqcrypto/vendor/pqclean/common/sp800-185.h
|
|
116
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/LICENSE
|
|
117
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile
|
|
118
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/Makefile.Microsoft_nmake
|
|
119
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/api.h
|
|
120
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.c
|
|
121
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/cbd.h
|
|
122
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.c
|
|
123
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/indcpa.h
|
|
124
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.c
|
|
125
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/kem.h
|
|
126
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.c
|
|
127
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/ntt.h
|
|
128
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/params.h
|
|
129
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.c
|
|
130
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/poly.h
|
|
131
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.c
|
|
132
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/polyvec.h
|
|
133
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.c
|
|
134
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/reduce.h
|
|
135
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric-shake.c
|
|
136
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/symmetric.h
|
|
137
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.c
|
|
138
|
+
- ext/pqcrypto/vendor/pqclean/crypto_kem/ml-kem-768/clean/verify.h
|
|
139
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/LICENSE
|
|
140
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile
|
|
141
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/Makefile.Microsoft_nmake
|
|
142
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/api.h
|
|
143
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.c
|
|
144
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/ntt.h
|
|
145
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.c
|
|
146
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/packing.h
|
|
147
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/params.h
|
|
148
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.c
|
|
149
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/poly.h
|
|
150
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.c
|
|
151
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/polyvec.h
|
|
152
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.c
|
|
153
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/reduce.h
|
|
154
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.c
|
|
155
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/rounding.h
|
|
156
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.c
|
|
157
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/sign.h
|
|
158
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric-shake.c
|
|
159
|
+
- ext/pqcrypto/vendor/pqclean/crypto_sign/ml-dsa-65/clean/symmetric.h
|
|
160
|
+
- lib/pq_crypto.rb
|
|
161
|
+
- lib/pq_crypto/errors.rb
|
|
162
|
+
- lib/pq_crypto/hybrid_kem.rb
|
|
163
|
+
- lib/pq_crypto/kem.rb
|
|
164
|
+
- lib/pq_crypto/serialization.rb
|
|
165
|
+
- lib/pq_crypto/signature.rb
|
|
166
|
+
- lib/pq_crypto/version.rb
|
|
167
|
+
- lib/pqcrypto.rb
|
|
168
|
+
- script/vendor_libs.rb
|
|
169
|
+
homepage: https://github.com/roman-haidarov/pq_crypto
|
|
170
|
+
licenses:
|
|
171
|
+
- MIT
|
|
172
|
+
metadata:
|
|
173
|
+
homepage_uri: https://github.com/roman-haidarov/pq_crypto
|
|
174
|
+
source_code_uri: https://github.com/roman-haidarov/pq_crypto
|
|
175
|
+
changelog_uri: https://github.com/roman-haidarov/pq_crypto/blob/main/CHANGELOG.md
|
|
176
|
+
post_install_message:
|
|
177
|
+
rdoc_options: []
|
|
178
|
+
require_paths:
|
|
179
|
+
- lib
|
|
180
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
181
|
+
requirements:
|
|
182
|
+
- - ">="
|
|
183
|
+
- !ruby/object:Gem::Version
|
|
184
|
+
version: 3.1.0
|
|
185
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
|
+
requirements:
|
|
187
|
+
- - ">="
|
|
188
|
+
- !ruby/object:Gem::Version
|
|
189
|
+
version: '0'
|
|
190
|
+
requirements: []
|
|
191
|
+
rubygems_version: 3.3.27
|
|
192
|
+
signing_key:
|
|
193
|
+
specification_version: 4
|
|
194
|
+
summary: Primitive-first post-quantum cryptography for Ruby
|
|
195
|
+
test_files: []
|