pqc_rails 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/CHANGELOG.md +41 -0
- data/LICENSE.txt +104 -0
- data/README.md +306 -0
- data/Rakefile +4 -0
- data/docs/CRYPTO_INVENTORY.md +47 -0
- data/docs/MIGRATION.md +107 -0
- data/docs/THREAT_MODEL.md +146 -0
- data/lib/pqc_rails/active_record/context.rb +42 -0
- data/lib/pqc_rails/active_record/key_provider.rb +67 -0
- data/lib/pqc_rails/algorithms.rb +74 -0
- data/lib/pqc_rails/blob_packing.rb +25 -0
- data/lib/pqc_rails/cipher.rb +74 -0
- data/lib/pqc_rails/configuration.rb +47 -0
- data/lib/pqc_rails/dh_kem.rb +66 -0
- data/lib/pqc_rails/envelope_cipher.rb +59 -0
- data/lib/pqc_rails/ffi/kem.rb +40 -0
- data/lib/pqc_rails/ffi/sig.rb +68 -0
- data/lib/pqc_rails/generators/install/install_generator.rb +48 -0
- data/lib/pqc_rails/generators/install/templates/initializer.rb +10 -0
- data/lib/pqc_rails/hybrid_kem.rb +128 -0
- data/lib/pqc_rails/kem.rb +116 -0
- data/lib/pqc_rails/key_source.rb +84 -0
- data/lib/pqc_rails/length_validation.rb +16 -0
- data/lib/pqc_rails/session/encryptor.rb +45 -0
- data/lib/pqc_rails/session/key_manager.rb +43 -0
- data/lib/pqc_rails/session/pqc_cookie_store.rb +56 -0
- data/lib/pqc_rails/sig.rb +159 -0
- data/lib/pqc_rails/version.rb +5 -0
- data/lib/pqc_rails.rb +26 -0
- data/sig/pqc_rails.rbs +4 -0
- metadata +151 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "algorithms"
|
|
4
|
+
require_relative "length_validation"
|
|
5
|
+
require_relative "ffi/sig"
|
|
6
|
+
|
|
7
|
+
module PqcRails
|
|
8
|
+
# liboqsのGeneric SIG(署名)APIをRubyらしく包んだクラス。
|
|
9
|
+
#
|
|
10
|
+
# 使い方:
|
|
11
|
+
# sig = PqcRails::Sig.new(:ml_dsa_44) # またはliboqsの生の名前 "ML-DSA-44"
|
|
12
|
+
# keypair = sig.generate_keypair
|
|
13
|
+
# signature = sig.sign("hello world", keypair.secret_key)
|
|
14
|
+
# sig.verify("hello world", signature, keypair.public_key) # => true
|
|
15
|
+
#
|
|
16
|
+
# PqcRails::Kem と同じパターン(open/free によるリソース管理)を踏襲している。
|
|
17
|
+
class Sig
|
|
18
|
+
include LengthValidation
|
|
19
|
+
|
|
20
|
+
Keypair = Struct.new(:public_key, :secret_key)
|
|
21
|
+
|
|
22
|
+
attr_reader :alg_name
|
|
23
|
+
|
|
24
|
+
def initialize(alg_name)
|
|
25
|
+
@alg_name = alg_name
|
|
26
|
+
liboqs_alg_name = Algorithms.resolve_sig_name(alg_name)
|
|
27
|
+
@sig_ptr = Ffi::Sig.OQS_SIG_new(liboqs_alg_name)
|
|
28
|
+
if @sig_ptr.null?
|
|
29
|
+
raise PqcRails::Error,
|
|
30
|
+
"OQS_SIG_new failed for '#{liboqs_alg_name}'. " \
|
|
31
|
+
"liboqsがこのアルゴリズムを有効にしてビルドされているか確認してください。"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@sig = Ffi::Sig::Struct.new(@sig_ptr)
|
|
35
|
+
@freed = false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def length_public_key = @sig[:length_public_key]
|
|
39
|
+
def length_secret_key = @sig[:length_secret_key]
|
|
40
|
+
|
|
41
|
+
# (最大の)署名長。ML-DSAのような一部のアルゴリズムは固定長だが、
|
|
42
|
+
# アルゴリズムによっては実際の署名長がこれより短くなることがある。
|
|
43
|
+
def length_signature = @sig[:length_signature]
|
|
44
|
+
|
|
45
|
+
# 鍵ペアを生成する
|
|
46
|
+
# @return [Keypair]
|
|
47
|
+
def generate_keypair
|
|
48
|
+
ensure_not_freed!
|
|
49
|
+
|
|
50
|
+
public_key = FFI::MemoryPointer.new(:uint8, length_public_key)
|
|
51
|
+
secret_key = FFI::MemoryPointer.new(:uint8, length_secret_key)
|
|
52
|
+
|
|
53
|
+
status = Ffi::Sig.OQS_SIG_keypair(@sig_ptr, public_key, secret_key)
|
|
54
|
+
raise PqcRails::Error, "OQS_SIG_keypair failed (status=#{status})" unless status.zero?
|
|
55
|
+
|
|
56
|
+
Keypair.new(
|
|
57
|
+
public_key.read_bytes(length_public_key),
|
|
58
|
+
secret_key.read_bytes(length_secret_key)
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# メッセージに署名する
|
|
63
|
+
# @param message [String] 署名対象のバイト列
|
|
64
|
+
# @param secret_key [String] バイト列
|
|
65
|
+
# @return [String] 署名のバイト列(実際の長さは length_signature 以下のことがある)
|
|
66
|
+
def sign(message, secret_key)
|
|
67
|
+
ensure_not_freed!
|
|
68
|
+
validate_length!(secret_key, length_secret_key, "secret_key")
|
|
69
|
+
|
|
70
|
+
message_ptr = FFI::MemoryPointer.new(:uint8, message.bytesize)
|
|
71
|
+
message_ptr.put_bytes(0, message)
|
|
72
|
+
|
|
73
|
+
secret_key_ptr = FFI::MemoryPointer.new(:uint8, secret_key.bytesize)
|
|
74
|
+
secret_key_ptr.put_bytes(0, secret_key)
|
|
75
|
+
|
|
76
|
+
# signatureバッファは「最大長」で確保しておく。
|
|
77
|
+
# 実際に書き込まれた長さは signature_len_ptr 経由で後から読み取る。
|
|
78
|
+
signature_ptr = FFI::MemoryPointer.new(:uint8, length_signature)
|
|
79
|
+
|
|
80
|
+
# OQS_SIG_signの第3引数(signature_len)は size_t* の出力引数。
|
|
81
|
+
# FFI::MemoryPointer.new(:size_t, 1) で size_t 1個分の領域を確保し、
|
|
82
|
+
# 関数呼び出し後に read_uint64 (64bit環境でのsize_t) で読み出す。
|
|
83
|
+
signature_len_ptr = FFI::MemoryPointer.new(:size_t, 1)
|
|
84
|
+
|
|
85
|
+
status = Ffi::Sig.OQS_SIG_sign(
|
|
86
|
+
@sig_ptr,
|
|
87
|
+
signature_ptr,
|
|
88
|
+
signature_len_ptr,
|
|
89
|
+
message_ptr,
|
|
90
|
+
message.bytesize,
|
|
91
|
+
secret_key_ptr
|
|
92
|
+
)
|
|
93
|
+
raise PqcRails::Error, "OQS_SIG_sign failed (status=#{status})" unless status.zero?
|
|
94
|
+
|
|
95
|
+
actual_signature_len = signature_len_ptr.read(:size_t)
|
|
96
|
+
signature_ptr.read_bytes(actual_signature_len)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# 署名を検証する
|
|
100
|
+
# @param message [String] 元のメッセージのバイト列
|
|
101
|
+
# @param signature [String] 検証する署名のバイト列
|
|
102
|
+
# @param public_key [String] バイト列
|
|
103
|
+
# @return [Boolean] 署名が正当であれば true
|
|
104
|
+
def verify(message, signature, public_key)
|
|
105
|
+
ensure_not_freed!
|
|
106
|
+
validate_length!(public_key, length_public_key, "public_key")
|
|
107
|
+
|
|
108
|
+
# FIPS 204 3.6.3 (Public-Key and Signature Length Checks) は、
|
|
109
|
+
# 検証実装がσ(署名)の長さを規格上の長さと照合し、異なればfalseを返すことを要求している。
|
|
110
|
+
# length_signatureは最大長のため、超過した時点でliboqsを呼ばずにfalseを返す
|
|
111
|
+
# (verifyの契約上、不正な入力は例外ではなく検証失敗として扱う。上のpublic_keyチェックとは
|
|
112
|
+
# 非対称だが、public_keyは通常呼び出し側が管理する信頼済みの値である一方、
|
|
113
|
+
# signatureは検証対象そのもの=攻撃者制御下にありうる値のため、扱いを分けている)。
|
|
114
|
+
return false if signature.bytesize > length_signature
|
|
115
|
+
|
|
116
|
+
message_ptr = FFI::MemoryPointer.new(:uint8, message.bytesize)
|
|
117
|
+
message_ptr.put_bytes(0, message)
|
|
118
|
+
|
|
119
|
+
signature_ptr = FFI::MemoryPointer.new(:uint8, signature.bytesize)
|
|
120
|
+
signature_ptr.put_bytes(0, signature)
|
|
121
|
+
|
|
122
|
+
public_key_ptr = FFI::MemoryPointer.new(:uint8, public_key.bytesize)
|
|
123
|
+
public_key_ptr.put_bytes(0, public_key)
|
|
124
|
+
|
|
125
|
+
status = Ffi::Sig.OQS_SIG_verify(
|
|
126
|
+
@sig_ptr,
|
|
127
|
+
message_ptr,
|
|
128
|
+
message.bytesize,
|
|
129
|
+
signature_ptr,
|
|
130
|
+
signature.bytesize,
|
|
131
|
+
public_key_ptr
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# OQS_SIG_verifyは「検証失敗」も含めてOQS_ERRORを返す設計。
|
|
135
|
+
# つまりここでのstatusはエラー(例外送出)ではなく、検証結果そのものとして扱う。
|
|
136
|
+
status.zero?
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def free
|
|
140
|
+
return if @freed
|
|
141
|
+
|
|
142
|
+
Ffi::Sig.OQS_SIG_free(@sig_ptr)
|
|
143
|
+
@freed = true
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.open(alg_name)
|
|
147
|
+
sig = new(alg_name)
|
|
148
|
+
yield sig
|
|
149
|
+
ensure
|
|
150
|
+
sig&.free
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
private
|
|
154
|
+
|
|
155
|
+
def ensure_not_freed!
|
|
156
|
+
raise PqcRails::Error, "this PqcRails::Sig instance has already been freed" if @freed
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
data/lib/pqc_rails.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PqcRails
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
require_relative "pqc_rails/version"
|
|
8
|
+
require_relative "pqc_rails/configuration"
|
|
9
|
+
require_relative "pqc_rails/algorithms"
|
|
10
|
+
require_relative "pqc_rails/length_validation"
|
|
11
|
+
require_relative "pqc_rails/blob_packing"
|
|
12
|
+
require_relative "pqc_rails/key_source"
|
|
13
|
+
require_relative "pqc_rails/kem"
|
|
14
|
+
require_relative "pqc_rails/sig"
|
|
15
|
+
require_relative "pqc_rails/dh_kem"
|
|
16
|
+
require_relative "pqc_rails/envelope_cipher"
|
|
17
|
+
require_relative "pqc_rails/hybrid_kem"
|
|
18
|
+
require_relative "pqc_rails/session/encryptor"
|
|
19
|
+
require_relative "pqc_rails/session/key_manager"
|
|
20
|
+
require_relative "pqc_rails/session/pqc_cookie_store"
|
|
21
|
+
require_relative "pqc_rails/generators/install/install_generator"
|
|
22
|
+
require_relative "pqc_rails/cipher"
|
|
23
|
+
require_relative "pqc_rails/active_record/key_provider"
|
|
24
|
+
require_relative "pqc_rails/active_record/context"
|
|
25
|
+
|
|
26
|
+
ActionDispatch::Session::PqcCookieStore = PqcRails::Session::PqcCookieStore
|
data/sig/pqc_rails.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: pqc_rails
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- mabutast
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: actionpack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: activerecord
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '7.1'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '7.1'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '9'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: ffi
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - "~>"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '1.16'
|
|
59
|
+
type: :runtime
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '1.16'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: railties
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: '7.1'
|
|
73
|
+
- - "<"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '9'
|
|
76
|
+
type: :runtime
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - ">="
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '7.1'
|
|
83
|
+
- - "<"
|
|
84
|
+
- !ruby/object:Gem::Version
|
|
85
|
+
version: '9'
|
|
86
|
+
description: pqc_rails provides zero-downtime-friendly post-quantum cryptography primitives
|
|
87
|
+
(NIST-standardized algorithms such as ML-KEM and ML-DSA) for existing Rails applications,
|
|
88
|
+
via native FFI bindings to liboqs.
|
|
89
|
+
email:
|
|
90
|
+
- contact@rubyquantum.dev
|
|
91
|
+
executables: []
|
|
92
|
+
extensions: []
|
|
93
|
+
extra_rdoc_files: []
|
|
94
|
+
files:
|
|
95
|
+
- CHANGELOG.md
|
|
96
|
+
- LICENSE.txt
|
|
97
|
+
- README.md
|
|
98
|
+
- Rakefile
|
|
99
|
+
- docs/CRYPTO_INVENTORY.md
|
|
100
|
+
- docs/MIGRATION.md
|
|
101
|
+
- docs/THREAT_MODEL.md
|
|
102
|
+
- lib/pqc_rails.rb
|
|
103
|
+
- lib/pqc_rails/active_record/context.rb
|
|
104
|
+
- lib/pqc_rails/active_record/key_provider.rb
|
|
105
|
+
- lib/pqc_rails/algorithms.rb
|
|
106
|
+
- lib/pqc_rails/blob_packing.rb
|
|
107
|
+
- lib/pqc_rails/cipher.rb
|
|
108
|
+
- lib/pqc_rails/configuration.rb
|
|
109
|
+
- lib/pqc_rails/dh_kem.rb
|
|
110
|
+
- lib/pqc_rails/envelope_cipher.rb
|
|
111
|
+
- lib/pqc_rails/ffi/kem.rb
|
|
112
|
+
- lib/pqc_rails/ffi/sig.rb
|
|
113
|
+
- lib/pqc_rails/generators/install/install_generator.rb
|
|
114
|
+
- lib/pqc_rails/generators/install/templates/initializer.rb
|
|
115
|
+
- lib/pqc_rails/hybrid_kem.rb
|
|
116
|
+
- lib/pqc_rails/kem.rb
|
|
117
|
+
- lib/pqc_rails/key_source.rb
|
|
118
|
+
- lib/pqc_rails/length_validation.rb
|
|
119
|
+
- lib/pqc_rails/session/encryptor.rb
|
|
120
|
+
- lib/pqc_rails/session/key_manager.rb
|
|
121
|
+
- lib/pqc_rails/session/pqc_cookie_store.rb
|
|
122
|
+
- lib/pqc_rails/sig.rb
|
|
123
|
+
- lib/pqc_rails/version.rb
|
|
124
|
+
- sig/pqc_rails.rbs
|
|
125
|
+
homepage: https://github.com/mabutast/pqc_rails
|
|
126
|
+
licenses:
|
|
127
|
+
- Nonstandard
|
|
128
|
+
metadata:
|
|
129
|
+
homepage_uri: https://github.com/mabutast/pqc_rails
|
|
130
|
+
source_code_uri: https://github.com/mabutast/pqc_rails
|
|
131
|
+
changelog_uri: https://github.com/mabutast/pqc_rails/blob/main/CHANGELOG.md
|
|
132
|
+
allowed_push_host: https://rubygems.org
|
|
133
|
+
rubygems_mfa_required: 'true'
|
|
134
|
+
rdoc_options: []
|
|
135
|
+
require_paths:
|
|
136
|
+
- lib
|
|
137
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
138
|
+
requirements:
|
|
139
|
+
- - ">="
|
|
140
|
+
- !ruby/object:Gem::Version
|
|
141
|
+
version: 3.2.0
|
|
142
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
|
+
requirements:
|
|
144
|
+
- - ">="
|
|
145
|
+
- !ruby/object:Gem::Version
|
|
146
|
+
version: '0'
|
|
147
|
+
requirements: []
|
|
148
|
+
rubygems_version: 4.0.16
|
|
149
|
+
specification_version: 4
|
|
150
|
+
summary: Post-quantum cryptography (PQC) integration for Ruby on Rails, built on liboqs.
|
|
151
|
+
test_files: []
|