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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PqcRails
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module PqcRails
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []