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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +66 -0
- data/CHANGELOG.md +44 -0
- data/FORMAT.md +139 -0
- data/GET_STARTED.md +30 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +215 -0
- data/RELEASING.md +34 -0
- data/Rakefile +19 -0
- data/SECURITY.md +85 -0
- data/VENDORING.md +14 -0
- data/ext/pq_crypto_seal/aegis_unused_stubs.c +20 -0
- data/ext/pq_crypto_seal/extconf.rb +296 -0
- data/ext/pq_crypto_seal/pq_crypto_seal.c +390 -0
- data/ext/pq_crypto_seal/vendor/libaegis/LICENSE +21 -0
- data/ext/pq_crypto_seal/vendor/libaegis/TREE_SHA256 +1 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l.c +246 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_aesni.c +73 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_aesni.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_altivec.c +70 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_altivec.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_common.h +691 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_aes.c +77 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_aes.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_sha3.c +86 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_neon_sha3.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_soft.c +63 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/aegis128l_soft.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128l/implementations.h +40 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2.c +247 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_aesni.c +108 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_aesni.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_altivec.c +103 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_altivec.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_avx2.c +79 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_avx2.h +11 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_common.h +795 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_neon_aes.c +111 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_neon_aes.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_soft.c +96 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/aegis128x2_soft.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x2/implementations.h +40 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4.c +252 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_aesni.c +117 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_aesni.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_altivec.c +111 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_altivec.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx2.c +112 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx2.h +11 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx512.c +85 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_avx512.h +11 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_common.h +808 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_neon_aes.c +119 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_neon_aes.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_soft.c +104 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/aegis128x4_soft.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis128x4/implementations.h +41 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256.c +240 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_aesni.c +68 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_aesni.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_altivec.c +68 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_altivec.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_common.h +673 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_neon_aes.c +75 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_neon_aes.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_soft.c +58 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/aegis256_soft.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256/implementations.h +39 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2.c +247 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_aesni.c +103 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_aesni.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_altivec.c +101 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_altivec.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_avx2.c +74 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_avx2.h +11 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_common.h +790 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_neon_aes.c +109 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_neon_aes.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_soft.c +91 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/aegis256x2_soft.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x2/implementations.h +41 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4.c +252 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_aesni.c +112 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_aesni.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_altivec.c +109 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_altivec.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx2.c +107 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx2.h +11 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx512.c +79 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_avx512.h +11 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_common.h +805 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_neon_aes.c +117 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_neon_aes.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_soft.c +99 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/aegis256x4_soft.h +9 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/aegis256x4/implementations.h +42 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/common.c +81 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/common.h +144 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/cpu.c +378 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/cpu.h +34 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/keccak.c +187 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/keccak.h +13 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/softaes.c +338 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/common/softaes.h +69 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis.h +77 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis128l.h +311 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis128x2.h +313 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis128x4.h +313 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis256.h +311 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis256x2.h +313 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis256x4.h +313 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/include/aegis_raf.h +580 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf.c +87 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis128l.c +20 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis128x2.c +20 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis128x4.c +20 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis256.c +20 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis256x2.c +20 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_aegis256x4.c +20 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_internal.h +152 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_merkle.c +268 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_merkle.h +165 -0
- data/ext/pq_crypto_seal/vendor/libaegis/src/raf/raf_variant.h +1119 -0
- data/fuzz/decrypt_fuzz.rb +119 -0
- data/lib/pq_crypto/seal/binary.rb +62 -0
- data/lib/pq_crypto/seal/core.rb +398 -0
- data/lib/pq_crypto/seal/errors.rb +15 -0
- data/lib/pq_crypto/seal/format.rb +275 -0
- data/lib/pq_crypto/seal/io.rb +336 -0
- data/lib/pq_crypto/seal/native.rb +13 -0
- data/lib/pq_crypto/seal/padding.rb +47 -0
- data/lib/pq_crypto/seal/version.rb +6 -0
- data/lib/pq_crypto/seal.rb +32 -0
- data/pq_crypto-seal.gemspec +37 -0
- data/script/vendor_libs.rb +79 -0
- data/test/aegis_vectors_test.rb +54 -0
- data/test/golden_vectors_test.rb +183 -0
- data/test/io_test.rb +125 -0
- data/test/native_test.rb +36 -0
- data/test/seal_test.rb +238 -0
- data/test/test_helper.rb +6 -0
- metadata +244 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pq_crypto"
|
|
4
|
+
require "pq_crypto/seal/version"
|
|
5
|
+
require "pq_crypto/seal/errors"
|
|
6
|
+
require "pq_crypto/seal/native"
|
|
7
|
+
require "pq_crypto/seal/binary"
|
|
8
|
+
require "pq_crypto/seal/padding"
|
|
9
|
+
require "pq_crypto/seal/format"
|
|
10
|
+
require "pq_crypto/seal/core"
|
|
11
|
+
require "pq_crypto/seal/io"
|
|
12
|
+
|
|
13
|
+
module PQCrypto
|
|
14
|
+
module Seal
|
|
15
|
+
IO_DELEGATES = %i[
|
|
16
|
+
encrypt_io decrypt_io encrypt_frame_io decrypt_frame_io
|
|
17
|
+
encrypt_file decrypt_file rebuild_recipients_file
|
|
18
|
+
add_recipient_file drop_recipient_stanza_file rotate_dek_file
|
|
19
|
+
inspect_file
|
|
20
|
+
].freeze
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
IO_DELEGATES.each do |name|
|
|
24
|
+
define_method(name) do |*args, **kwargs|
|
|
25
|
+
IOAPI.public_send(name, *args, **kwargs)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private_constant :Native
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/pq_crypto/seal/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "pq_crypto-seal"
|
|
7
|
+
spec.version = PQCrypto::Seal::VERSION
|
|
8
|
+
spec.authors = ["Roman Khaidarov"]
|
|
9
|
+
spec.email = ["roman-haidarov@users.noreply.github.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Post-quantum envelope encryption for Ruby"
|
|
12
|
+
spec.description = "Versioned multi-recipient document encryption using PQCrypto hybrid KEM, HKDF-SHA256, and vendored AEGIS-256."
|
|
13
|
+
spec.homepage = "https://github.com/roman-haidarov/pq_crypto-seal"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.1")
|
|
16
|
+
|
|
17
|
+
spec.metadata["source_code_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
|
19
|
+
|
|
20
|
+
spec.files = Dir.chdir(__dir__) do
|
|
21
|
+
Dir["{lib,ext,test,script,fuzz,.github}/**/*", "pq_crypto-seal.gemspec", "Gemfile", "Rakefile", "README.md", "GET_STARTED.md", "FORMAT.md", "SECURITY.md", "RELEASING.md", "CHANGELOG.md", "LICENSE.txt", "VENDORING.md"]
|
|
22
|
+
.select { |path| File.file?(path) }
|
|
23
|
+
.reject do |path|
|
|
24
|
+
path.match?(/(?:\.o|\.so|\.bundle|\.gem)\z/) ||
|
|
25
|
+
path.end_with?("/Makefile", "/mkmf.log") ||
|
|
26
|
+
path.include?("/.DS_Store") || false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
spec.bindir = "exe"
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
spec.extensions = ["ext/pq_crypto_seal/extconf.rb"]
|
|
32
|
+
|
|
33
|
+
spec.add_runtime_dependency "pq_crypto", "~> 0.6.4"
|
|
34
|
+
spec.add_development_dependency "minitest", "~> 5.14"
|
|
35
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
36
|
+
spec.add_development_dependency "rake-compiler", "~> 1.2"
|
|
37
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "digest"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "net/http"
|
|
7
|
+
require "tmpdir"
|
|
8
|
+
require "uri"
|
|
9
|
+
|
|
10
|
+
ROOT = File.expand_path("..", __dir__)
|
|
11
|
+
VENDOR = File.join(ROOT, "ext/pq_crypto_seal/vendor/libaegis")
|
|
12
|
+
VERSION = "0.10.3"
|
|
13
|
+
URL = "https://github.com/aegis-aead/libaegis/archive/refs/tags/#{VERSION}.tar.gz"
|
|
14
|
+
ARCHIVE_SHA256 = "2f2682c1d08d9a5510caca1c82e3f8ea91f7085fef2ecbed0c398b2a921c79b1"
|
|
15
|
+
TREE_SHA256_FILE = File.join(VENDOR, "TREE_SHA256")
|
|
16
|
+
|
|
17
|
+
def download(uri, limit = 5)
|
|
18
|
+
abort "too many redirects while downloading libaegis" if limit <= 0
|
|
19
|
+
response = Net::HTTP.get_response(uri)
|
|
20
|
+
case response
|
|
21
|
+
when Net::HTTPSuccess
|
|
22
|
+
response.body
|
|
23
|
+
when Net::HTTPRedirection
|
|
24
|
+
location = response["location"]
|
|
25
|
+
abort "download redirect without Location" unless location
|
|
26
|
+
download(URI.join(uri.to_s, location), limit - 1)
|
|
27
|
+
else
|
|
28
|
+
abort "download failed: HTTP #{response.code}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def tree_sha256(directory)
|
|
33
|
+
digest = Digest::SHA256.new
|
|
34
|
+
Dir.chdir(directory) do
|
|
35
|
+
Dir.glob("**/*", File::FNM_DOTMATCH).sort.each do |path|
|
|
36
|
+
next if path == "." || path == ".." || path == "TREE_SHA256"
|
|
37
|
+
next unless File.file?(path)
|
|
38
|
+
bytes = File.binread(path)
|
|
39
|
+
digest << [path.bytesize].pack("N") << path.b
|
|
40
|
+
digest << [bytes.bytesize].pack("Q>") << bytes
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
digest.hexdigest
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def verify!
|
|
47
|
+
expected = File.read(TREE_SHA256_FILE).strip
|
|
48
|
+
actual = tree_sha256(VENDOR)
|
|
49
|
+
abort "libaegis vendor tree mismatch\nexpected: #{expected}\nactual: #{actual}" unless actual == expected
|
|
50
|
+
puts "libaegis #{VERSION} vendor tree verified: #{actual}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def update!
|
|
54
|
+
Dir.mktmpdir("libaegis") do |tmp|
|
|
55
|
+
archive = File.join(tmp, "libaegis.tar.gz")
|
|
56
|
+
uri = URI(URL)
|
|
57
|
+
File.binwrite(archive, download(uri))
|
|
58
|
+
actual = Digest::SHA256.file(archive).hexdigest
|
|
59
|
+
abort "archive checksum mismatch: #{actual}" unless actual == ARCHIVE_SHA256
|
|
60
|
+
abort "tar extraction failed" unless system("tar", "-xzf", archive, "-C", tmp)
|
|
61
|
+
extracted = Dir[File.join(tmp, "libaegis-*")].find { |path| File.directory?(path) }
|
|
62
|
+
abort "extracted libaegis directory not found" unless extracted
|
|
63
|
+
FileUtils.rm_rf(VENDOR)
|
|
64
|
+
FileUtils.mkdir_p(VENDOR)
|
|
65
|
+
FileUtils.cp_r(File.join(extracted, "src"), VENDOR)
|
|
66
|
+
FileUtils.cp(File.join(extracted, "LICENSE"), VENDOR)
|
|
67
|
+
File.write(TREE_SHA256_FILE, tree_sha256(VENDOR) + "\n")
|
|
68
|
+
end
|
|
69
|
+
verify!
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
case ARGV.first
|
|
73
|
+
when nil, "--check"
|
|
74
|
+
verify!
|
|
75
|
+
when "--update"
|
|
76
|
+
update!
|
|
77
|
+
else
|
|
78
|
+
abort "usage: #{$PROGRAM_NAME} [--check|--update]"
|
|
79
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "test_helper"
|
|
3
|
+
|
|
4
|
+
class AegisVectorsTest < Minitest::Test
|
|
5
|
+
def native
|
|
6
|
+
PQCrypto::Seal.const_get(:Native, false)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
KEY = ["1001000000000000000000000000000000000000000000000000000000000000"].pack("H*")
|
|
10
|
+
NONCE = ["1000020000000000000000000000000000000000000000000000000000000000"].pack("H*")
|
|
11
|
+
|
|
12
|
+
POSITIVE = [
|
|
13
|
+
["", "00000000000000000000000000000000", "754fc3d8c973246dcc6d741412a4b236", "1181a1d18091082bf0266f66297d167d2e68b845f61a3b0527d31fc7b7b89f13"],
|
|
14
|
+
["", "", "", "6a348c930adbd654896e1666aad67de989ea75ebaa2b82fb588977b1ffec864a"],
|
|
15
|
+
["0001020304050607", "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f", "f373079ed84b2709faee373584585d60accd191db310ef5d8b11833df9dec711", "b7d28d0c3c0ebd409fd22b44160503073a547412da0854bfb9723020dab8da1a"],
|
|
16
|
+
["0001020304050607", "000102030405060708090a0b0c0d", "f373079ed84b2709faee37358458", "8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2d9"],
|
|
17
|
+
["000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20212223242526272829", "101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637", "57754a7d09963e7c787583a2e7b859bb24fa1e04d49fd550b2511a358e3bca252a9b1b8b30cc4a67", "a3aca270c006094d71c20e6910b5161c0826df233d08919a566ec2c05990f734"]
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
def test_draft_18_positive_vectors
|
|
21
|
+
POSITIVE.each do |ad_hex, msg_hex, ct_hex, tag_hex|
|
|
22
|
+
ad, msg, expected_ct, expected_tag = [ad_hex, msg_hex, ct_hex, tag_hex].map { |hex| [hex].pack("H*") }
|
|
23
|
+
ct, tag = native.aegis256_encrypt(KEY, NONCE, ad, msg)
|
|
24
|
+
assert_equal expected_ct, ct
|
|
25
|
+
assert_equal expected_tag, tag
|
|
26
|
+
assert_equal msg, native.aegis256_decrypt(KEY, NONCE, ad, ct, tag)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_draft_18_negative_vectors
|
|
31
|
+
valid_ct = ["f373079ed84b2709faee37358458"].pack("H*")
|
|
32
|
+
valid_tag = ["8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2d9"].pack("H*")
|
|
33
|
+
ad = ["0001020304050607"].pack("H*")
|
|
34
|
+
|
|
35
|
+
cases = [
|
|
36
|
+
[["1000020000000000000000000000000000000000000000000000000000000000"].pack("H*"), ["1001000000000000000000000000000000000000000000000000000000000000"].pack("H*"), ad, valid_ct, valid_tag],
|
|
37
|
+
[KEY, NONCE, ad, ["f373079ed84b2709faee37358459"].pack("H*"), valid_tag],
|
|
38
|
+
[KEY, NONCE, ["0001020304050608"].pack("H*"), valid_ct, valid_tag],
|
|
39
|
+
[KEY, NONCE, ad, valid_ct, ["8c1cc703c81281bee3f6d9966e14948b4a175b2efbdc31e61a98b4465235c2da"].pack("H*")]
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
cases.each do |key, nonce, case_ad, ct, tag|
|
|
43
|
+
assert_raises(PQCrypto::Seal::AuthenticationError) do
|
|
44
|
+
native.aegis256_decrypt(key, nonce, case_ad, ct, tag)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def test_rfc5869_empty_salt_vector
|
|
50
|
+
ikm = ["0b" * 22].pack("H*")
|
|
51
|
+
expected = ["8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"].pack("H*")
|
|
52
|
+
assert_equal expected, native.hkdf_sha256(ikm, "".b, 42)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "test_helper"
|
|
4
|
+
|
|
5
|
+
# Protocol-drift detection for the frozen v1 wire format.
|
|
6
|
+
#
|
|
7
|
+
# A fully byte-frozen "plaintext -> exact envelope" vector is not achievable
|
|
8
|
+
# from this gem alone, because X-Wing encapsulation draws its own randomness
|
|
9
|
+
# inside pq_crypto and is not injectable here. Instead this suite pins every
|
|
10
|
+
# part of the construction that Seal itself controls and every derived value
|
|
11
|
+
# that would change if the protocol silently drifted:
|
|
12
|
+
#
|
|
13
|
+
# * the X-Wing wire contract (algorithm id + exact key/ciphertext/secret sizes)
|
|
14
|
+
# * the exact immutable-header byte layout for a fixed input
|
|
15
|
+
# * the payload associated data derivation (SHA-256 of the header)
|
|
16
|
+
# * the KDF label wiring and slot offsets
|
|
17
|
+
# * one-shot and incremental encryption producing identical bytes under a
|
|
18
|
+
# fixed deterministic RNG
|
|
19
|
+
#
|
|
20
|
+
# Any change to labels, offsets, field order, or hashing breaks these.
|
|
21
|
+
class GoldenVectorsTest < Minitest::Test
|
|
22
|
+
def native
|
|
23
|
+
PQCrypto::Seal.const_get(:Native, false)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Deterministic counter-based byte stream so every Seal-side random draw
|
|
27
|
+
# (payload id, nonces, DEK, section id, padding) is fixed across a run.
|
|
28
|
+
class CountingRandom
|
|
29
|
+
def initialize(seed, native)
|
|
30
|
+
@state = [seed].pack("Q>")
|
|
31
|
+
@native = native
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def bytes(length)
|
|
35
|
+
return "".b if length.zero?
|
|
36
|
+
|
|
37
|
+
out = +"".b
|
|
38
|
+
while out.bytesize < length
|
|
39
|
+
@state = @native.sha256(@state)
|
|
40
|
+
out << @state
|
|
41
|
+
end
|
|
42
|
+
out.byteslice(0, length).b
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def with_deterministic_random(seed)
|
|
47
|
+
mod = native
|
|
48
|
+
stream = CountingRandom.new(seed, mod)
|
|
49
|
+
singleton = mod.singleton_class
|
|
50
|
+
singleton.send(:alias_method, :__real_random_bytes, :random_bytes)
|
|
51
|
+
singleton.send(:define_method, :random_bytes) { |length| stream.bytes(length) }
|
|
52
|
+
yield
|
|
53
|
+
ensure
|
|
54
|
+
singleton.send(:alias_method, :random_bytes, :__real_random_bytes)
|
|
55
|
+
singleton.send(:remove_method, :__real_random_bytes)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def test_xwing_kem_wire_contract
|
|
59
|
+
keypair = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
60
|
+
assert_equal :ml_kem_768_x25519_xwing, keypair.public_key.algorithm
|
|
61
|
+
assert_equal PQCrypto::Seal::Format::XWING_PUBLIC_KEY_BYTES,
|
|
62
|
+
keypair.public_key.to_bytes.bytesize
|
|
63
|
+
encapsulated = keypair.public_key.encapsulate
|
|
64
|
+
assert_equal PQCrypto::Seal::Format::XWING_CIPHERTEXT_BYTES,
|
|
65
|
+
encapsulated.ciphertext.bytesize
|
|
66
|
+
assert_equal PQCrypto::Seal::Format::XWING_SHARED_SECRET_BYTES,
|
|
67
|
+
encapsulated.shared_secret.bytesize
|
|
68
|
+
recovered = keypair.secret_key.decapsulate(encapsulated.ciphertext)
|
|
69
|
+
assert_equal encapsulated.shared_secret, recovered
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def test_immutable_header_layout_is_frozen
|
|
73
|
+
keypair = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
74
|
+
envelope = with_deterministic_random(0x0102_0304_0506_0708) do
|
|
75
|
+
PQCrypto::Seal.encrypt(
|
|
76
|
+
"golden", to: keypair.public_key,
|
|
77
|
+
public_metadata: "meta", padding: :none,
|
|
78
|
+
recipient_capacity: 4, slot_size: 2048
|
|
79
|
+
)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
fmt = PQCrypto::Seal::Format
|
|
83
|
+
offset = 0
|
|
84
|
+
assert_equal fmt::MAGIC, envelope.byteslice(offset, 8); offset += 8
|
|
85
|
+
assert_equal 1, envelope.getbyte(offset); offset += 1 # version
|
|
86
|
+
header_length = envelope.byteslice(offset, 4).unpack1("N"); offset += 4
|
|
87
|
+
assert_equal fmt::CONTENT_SUITE_AEGIS256, envelope.byteslice(offset, 2).unpack1("n"); offset += 2
|
|
88
|
+
assert_equal fmt::LOOKUP_HINT, envelope.getbyte(offset); offset += 1
|
|
89
|
+
assert_equal fmt::FLAGS, envelope.byteslice(offset, 2).unpack1("n"); offset += 2
|
|
90
|
+
assert_equal fmt::PADDING_NONE, envelope.getbyte(offset); offset += 1
|
|
91
|
+
|
|
92
|
+
header = fmt.parse_header(envelope)
|
|
93
|
+
assert_equal header_length, header.raw.bytesize
|
|
94
|
+
assert_equal "meta", header.public_metadata
|
|
95
|
+
assert_equal fmt::PADDING_NONE, header.padding_policy_id
|
|
96
|
+
assert_equal 4, header.recipient_capacity
|
|
97
|
+
assert_equal 2048, header.slot_size
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def test_payload_ad_is_sha256_of_header
|
|
101
|
+
keypair = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
102
|
+
envelope = PQCrypto::Seal.encrypt("data", to: keypair.public_key, padding: :none)
|
|
103
|
+
header = PQCrypto::Seal::Format.parse_header(envelope)
|
|
104
|
+
# The recipient can open the envelope, which only succeeds if the AD used
|
|
105
|
+
# for the payload equals SHA-256(header.raw); a drifted AD fails the tag.
|
|
106
|
+
opened = PQCrypto::Seal.open(envelope, with: keypair)
|
|
107
|
+
assert_equal "data", opened.data
|
|
108
|
+
refute_equal native.sha256(header.raw), header.raw
|
|
109
|
+
assert_equal 32, native.sha256(header.raw).bytesize
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_one_shot_and_incremental_agree_on_deterministic_regions
|
|
113
|
+
# X-Wing encapsulation randomness lives inside pq_crypto and is NOT covered
|
|
114
|
+
# by the Native.random_bytes stub, so the recipient section (KEM ciphertexts)
|
|
115
|
+
# differs between two runs. Everything Seal itself controls must still match:
|
|
116
|
+
# the immutable header and the payload ciphertext+tag.
|
|
117
|
+
#
|
|
118
|
+
# padding MUST be :none: one-shot draws padding as one random_bytes call, the
|
|
119
|
+
# IO path draws it in chunk_size pieces, which would diverge under the same
|
|
120
|
+
# deterministic RNG. With :none both draw zero padding bytes.
|
|
121
|
+
keypair = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
122
|
+
data = ("payload" * 5000).b
|
|
123
|
+
|
|
124
|
+
one_shot = with_deterministic_random(0xAABB_CCDD_EEFF_0011) do
|
|
125
|
+
PQCrypto::Seal.encrypt(
|
|
126
|
+
data, to: keypair.public_key, metadata: "m",
|
|
127
|
+
public_metadata: "p", padding: :none,
|
|
128
|
+
recipient_capacity: 4, slot_size: 2048
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
incremental = with_deterministic_random(0xAABB_CCDD_EEFF_0011) do
|
|
133
|
+
input = StringIO.new(data)
|
|
134
|
+
output = StringIO.new(+"".b)
|
|
135
|
+
PQCrypto::Seal.encrypt_io(
|
|
136
|
+
input, output, size: data.bytesize, to: keypair.public_key,
|
|
137
|
+
metadata: "m", public_metadata: "p", padding: :none,
|
|
138
|
+
recipient_capacity: 4, slot_size: 2048
|
|
139
|
+
)
|
|
140
|
+
output.string
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
fmt = PQCrypto::Seal::Format
|
|
144
|
+
header_a = fmt.parse_header(one_shot)
|
|
145
|
+
header_b = fmt.parse_header(incremental)
|
|
146
|
+
|
|
147
|
+
# Immutable header bytes are fully deterministic and must be identical.
|
|
148
|
+
assert_equal header_a.raw, header_b.raw,
|
|
149
|
+
"one-shot and incremental must build identical headers"
|
|
150
|
+
|
|
151
|
+
# Payload region (ciphertext + tag) is deterministic given a fixed DEK,
|
|
152
|
+
# nonce, and header hash; extract and compare it across both paths.
|
|
153
|
+
section_len = fmt.section_length(header_a)
|
|
154
|
+
payload_a = one_shot.byteslice(header_a.raw.bytesize + section_len..)
|
|
155
|
+
payload_b = incremental.byteslice(header_b.raw.bytesize + section_len..)
|
|
156
|
+
assert_equal payload_a, payload_b,
|
|
157
|
+
"one-shot and incremental must produce identical payload bytes"
|
|
158
|
+
|
|
159
|
+
# Both envelopes must open to the exact plaintext.
|
|
160
|
+
assert_equal data, PQCrypto::Seal.decrypt(one_shot, with: keypair)
|
|
161
|
+
assert_equal data, PQCrypto::Seal.decrypt(incremental, with: keypair)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def test_multi_recipient_golden_roundtrip_is_stable
|
|
165
|
+
alice = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
166
|
+
bob = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
167
|
+
envelope = with_deterministic_random(0x1122_3344_5566_7788) do
|
|
168
|
+
PQCrypto::Seal.encrypt(
|
|
169
|
+
"shared-doc", to: [alice.public_key, bob.public_key],
|
|
170
|
+
metadata: "private", padding: :padme,
|
|
171
|
+
recipient_capacity: 4, slot_size: 2048
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
assert_equal "shared-doc", PQCrypto::Seal.decrypt(envelope, with: alice)
|
|
175
|
+
assert_equal "shared-doc", PQCrypto::Seal.decrypt(envelope, with: bob)
|
|
176
|
+
info = PQCrypto::Seal.inspect_envelope(envelope)
|
|
177
|
+
assert_equal 4, info.recipient_capacity
|
|
178
|
+
assert_equal PQCrypto::Seal::Format::PADDING_PADME, info.padding_policy_id
|
|
179
|
+
# Padmé targets the full envelope, so the total size equals the Padmé target.
|
|
180
|
+
assert_equal PQCrypto::Seal::Padding.padme_target(info.envelope_bytes),
|
|
181
|
+
info.envelope_bytes
|
|
182
|
+
end
|
|
183
|
+
end
|
data/test/io_test.rb
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "test_helper"
|
|
3
|
+
|
|
4
|
+
class IOTest < Minitest::Test
|
|
5
|
+
def setup
|
|
6
|
+
@keypair = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_io_round_trip
|
|
10
|
+
data = Random.new(7).bytes(2 * 1024 * 1024 + 31)
|
|
11
|
+
encrypted = StringIO.new("".b)
|
|
12
|
+
PQCrypto::Seal.encrypt_io(StringIO.new(data), encrypted, size: data.bytesize,
|
|
13
|
+
to: @keypair.public_key, chunk_size: 65_537)
|
|
14
|
+
encrypted.rewind
|
|
15
|
+
output = StringIO.new("".b)
|
|
16
|
+
PQCrypto::Seal.decrypt_io(encrypted, output, with: @keypair, chunk_size: 43_211)
|
|
17
|
+
assert_equal data, output.string
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_decrypt_io_rejects_trailing_bytes_by_default
|
|
21
|
+
data = "payload".b
|
|
22
|
+
encrypted = StringIO.new("".b)
|
|
23
|
+
PQCrypto::Seal.encrypt_io(StringIO.new(data), encrypted, size: data.bytesize, to: @keypair.public_key)
|
|
24
|
+
encrypted.string << "TRAIL"
|
|
25
|
+
encrypted.rewind
|
|
26
|
+
output = StringIO.new("".b)
|
|
27
|
+
assert_raises(PQCrypto::Seal::FormatError) do
|
|
28
|
+
PQCrypto::Seal.decrypt_io(encrypted, output, with: @keypair)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def test_decrypt_frame_io_tolerates_trailing_bytes
|
|
33
|
+
data = "payload".b
|
|
34
|
+
encrypted = StringIO.new("".b)
|
|
35
|
+
PQCrypto::Seal.encrypt_io(StringIO.new(data), encrypted, size: data.bytesize, to: @keypair.public_key)
|
|
36
|
+
encrypted.string << "TRAIL"
|
|
37
|
+
encrypted.rewind
|
|
38
|
+
output = StringIO.new("".b)
|
|
39
|
+
PQCrypto::Seal.decrypt_frame_io(encrypted, output, with: @keypair)
|
|
40
|
+
assert_equal data, output.string
|
|
41
|
+
assert_equal "TRAIL", encrypted.read
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_file_recipient_rebuild_and_dek_rotation
|
|
45
|
+
bob = PQCrypto::HybridKEM.generate(PQCrypto::Seal::WRAP_KEM_ALGORITHM)
|
|
46
|
+
Dir.mktmpdir do |dir|
|
|
47
|
+
source = File.join(dir, "source")
|
|
48
|
+
sealed = File.join(dir, "sealed")
|
|
49
|
+
rebuilt = File.join(dir, "rebuilt")
|
|
50
|
+
rotated = File.join(dir, "rotated")
|
|
51
|
+
output = File.join(dir, "output")
|
|
52
|
+
data = Random.new(9).bytes(1_300_017)
|
|
53
|
+
File.binwrite(source, data)
|
|
54
|
+
PQCrypto::Seal.encrypt_file(source, sealed, to: @keypair.public_key)
|
|
55
|
+
PQCrypto::Seal.rebuild_recipients_file(
|
|
56
|
+
sealed, rebuilt, with: @keypair,
|
|
57
|
+
recipients: [@keypair.public_key, bob.public_key]
|
|
58
|
+
)
|
|
59
|
+
PQCrypto::Seal.decrypt_file(rebuilt, output, with: bob)
|
|
60
|
+
assert_equal data, File.binread(output)
|
|
61
|
+
|
|
62
|
+
rebuilt_size = File.size(rebuilt)
|
|
63
|
+
PQCrypto::Seal.rotate_dek_file(
|
|
64
|
+
rebuilt, rotated, with: @keypair,
|
|
65
|
+
recipients: [@keypair.public_key, bob.public_key]
|
|
66
|
+
)
|
|
67
|
+
assert_equal rebuilt_size, File.size(rotated)
|
|
68
|
+
refute_equal PQCrypto::Seal.digest(File.binread(rebuilt)), PQCrypto::Seal.digest(File.binread(rotated))
|
|
69
|
+
PQCrypto::Seal.decrypt_file(rotated, output, with: bob)
|
|
70
|
+
assert_equal data, File.binread(output)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def test_rebuild_rejects_corrupted_payload_without_publishing
|
|
75
|
+
Dir.mktmpdir do |dir|
|
|
76
|
+
source = File.join(dir, "source")
|
|
77
|
+
sealed = File.join(dir, "sealed")
|
|
78
|
+
rebuilt = File.join(dir, "rebuilt")
|
|
79
|
+
File.binwrite(source, "payload" * 50_000)
|
|
80
|
+
PQCrypto::Seal.encrypt_file(source, sealed, to: @keypair.public_key)
|
|
81
|
+
bytes = File.binread(sealed)
|
|
82
|
+
info = PQCrypto::Seal.inspect_file(sealed)
|
|
83
|
+
payload_offset = info.envelope_bytes - info.padded_inner_length - PQCrypto::Seal::Format::TAG_BYTES
|
|
84
|
+
bytes.setbyte(payload_offset, bytes.getbyte(payload_offset) ^ 1)
|
|
85
|
+
File.binwrite(sealed, bytes)
|
|
86
|
+
assert_raises(PQCrypto::Seal::AuthenticationError) do
|
|
87
|
+
PQCrypto::Seal.rebuild_recipients_file(
|
|
88
|
+
sealed, rebuilt, with: @keypair, recipients: [@keypair.public_key]
|
|
89
|
+
)
|
|
90
|
+
end
|
|
91
|
+
refute File.exist?(rebuilt)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def test_failed_file_decrypt_does_not_publish_destination
|
|
96
|
+
Dir.mktmpdir do |dir|
|
|
97
|
+
source = File.join(dir, "source")
|
|
98
|
+
sealed = File.join(dir, "sealed")
|
|
99
|
+
destination = File.join(dir, "destination")
|
|
100
|
+
File.binwrite(source, "important" * 100_000)
|
|
101
|
+
PQCrypto::Seal.encrypt_file(source, sealed, to: @keypair.public_key)
|
|
102
|
+
bytes = File.binread(sealed)
|
|
103
|
+
bytes.setbyte(bytes.bytesize - 1, bytes.getbyte(-1) ^ 1)
|
|
104
|
+
File.binwrite(sealed, bytes)
|
|
105
|
+
assert_raises(PQCrypto::Seal::AuthenticationError) do
|
|
106
|
+
PQCrypto::Seal.decrypt_file(sealed, destination, with: @keypair)
|
|
107
|
+
end
|
|
108
|
+
refute File.exist?(destination)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_decrypt_file_resource_limit
|
|
113
|
+
Dir.mktmpdir do |dir|
|
|
114
|
+
source = File.join(dir, "source")
|
|
115
|
+
sealed = File.join(dir, "sealed")
|
|
116
|
+
destination = File.join(dir, "destination")
|
|
117
|
+
File.binwrite(source, "x" * 1000)
|
|
118
|
+
PQCrypto::Seal.encrypt_file(source, sealed, to: @keypair.public_key, padding: :none)
|
|
119
|
+
assert_raises(PQCrypto::Seal::ResourceLimitError) do
|
|
120
|
+
PQCrypto::Seal.decrypt_file(sealed, destination, with: @keypair, max_staging_bytes: 32)
|
|
121
|
+
end
|
|
122
|
+
refute File.exist?(destination)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
data/test/native_test.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require_relative "test_helper"
|
|
3
|
+
|
|
4
|
+
class NativeTest < Minitest::Test
|
|
5
|
+
def native
|
|
6
|
+
PQCrypto::Seal.const_get(:Native, false)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def test_aegis_one_shot_and_incremental_are_identical
|
|
10
|
+
key = "k" * 32
|
|
11
|
+
nonce = "n" * 32
|
|
12
|
+
ad = "header"
|
|
13
|
+
message = ("abc123" * 20_000).b
|
|
14
|
+
ciphertext, tag = native.aegis256_encrypt(key, nonce, ad, message)
|
|
15
|
+
|
|
16
|
+
enc = native::Encryptor.new(key, nonce, ad)
|
|
17
|
+
streamed = message.bytes.each_slice(7777).map { |slice| enc.update(slice.pack("C*")) }.join
|
|
18
|
+
assert_equal ciphertext, streamed
|
|
19
|
+
assert_equal tag, enc.final
|
|
20
|
+
|
|
21
|
+
dec = native::Decryptor.new(key, nonce, ad)
|
|
22
|
+
plain = streamed.bytes.each_slice(3333).map { |slice| dec.update(slice.pack("C*")) }.join
|
|
23
|
+
assert dec.final(tag)
|
|
24
|
+
assert_equal message, plain
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_tampered_tag_fails
|
|
28
|
+
key = "k" * 32
|
|
29
|
+
nonce = "n" * 32
|
|
30
|
+
ciphertext, tag = native.aegis256_encrypt(key, nonce, "", "secret")
|
|
31
|
+
tag.setbyte(0, tag.getbyte(0) ^ 1)
|
|
32
|
+
assert_raises(PQCrypto::Seal::AuthenticationError) do
|
|
33
|
+
native.aegis256_decrypt(key, nonce, "", ciphertext, tag)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|