Class: PqcRails::Sig

Inherits:
Object
  • Object
show all
Includes:
LengthValidation
Defined in:
lib/pqc_rails/sig.rb

Overview

liboqsのGeneric SIG(署名)APIをRubyらしく包んだクラス。

使い方:

sig = PqcRails::Sig.new(:ml_dsa_44) # またはliboqsの生の名前 "ML-DSA-44"
keypair   = sig.generate_keypair
signature = sig.sign("hello world", keypair.secret_key)
sig.verify("hello world", signature, keypair.public_key) # => true

PqcRails::Kem と同じパターン(open/free によるリソース管理)を踏襲している。

Defined Under Namespace

Classes: Keypair

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(alg_name) ⇒ Sig

Returns a new instance of Sig.



24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/pqc_rails/sig.rb', line 24

def initialize(alg_name)
  @alg_name = alg_name
  liboqs_alg_name = Algorithms.resolve_sig_name(alg_name)
  @sig_ptr = Ffi::Sig.OQS_SIG_new(liboqs_alg_name)
  if @sig_ptr.null?
    raise PqcRails::Error,
          "OQS_SIG_new failed for '#{liboqs_alg_name}'. " \
          "liboqsがこのアルゴリズムを有効にしてビルドされているか確認してください。"
  end

  @sig = Ffi::Sig::Struct.new(@sig_ptr)
  @freed = false
end

Instance Attribute Details

#alg_nameObject (readonly)

Returns the value of attribute alg_name.



22
23
24
# File 'lib/pqc_rails/sig.rb', line 22

def alg_name
  @alg_name
end

Class Method Details

.open(alg_name) ⇒ Object



146
147
148
149
150
151
# File 'lib/pqc_rails/sig.rb', line 146

def self.open(alg_name)
  sig = new(alg_name)
  yield sig
ensure
  sig&.free
end

Instance Method Details

#freeObject



139
140
141
142
143
144
# File 'lib/pqc_rails/sig.rb', line 139

def free
  return if @freed

  Ffi::Sig.OQS_SIG_free(@sig_ptr)
  @freed = true
end

#generate_keypairKeypair

鍵ペアを生成する

Returns:

Raises:



47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/pqc_rails/sig.rb', line 47

def generate_keypair
  ensure_not_freed!

  public_key = FFI::MemoryPointer.new(:uint8, length_public_key)
  secret_key = FFI::MemoryPointer.new(:uint8, length_secret_key)

  status = Ffi::Sig.OQS_SIG_keypair(@sig_ptr, public_key, secret_key)
  raise PqcRails::Error, "OQS_SIG_keypair failed (status=#{status})" unless status.zero?

  Keypair.new(
    public_key.read_bytes(length_public_key),
    secret_key.read_bytes(length_secret_key)
  )
end

#length_public_keyObject



38
# File 'lib/pqc_rails/sig.rb', line 38

def length_public_key = @sig[:length_public_key]

#length_secret_keyObject



39
# File 'lib/pqc_rails/sig.rb', line 39

def length_secret_key = @sig[:length_secret_key]

#length_signatureObject

(最大の)署名長。ML-DSAのような一部のアルゴリズムは固定長だが、 アルゴリズムによっては実際の署名長がこれより短くなることがある。



43
# File 'lib/pqc_rails/sig.rb', line 43

def length_signature = @sig[:length_signature]

#sign(message, secret_key) ⇒ String

メッセージに署名する

Parameters:

  • message (String)

    署名対象のバイト列

  • secret_key (String)

    バイト列

Returns:

  • (String)

    署名のバイト列(実際の長さは length_signature 以下のことがある)

Raises:



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/pqc_rails/sig.rb', line 66

def sign(message, secret_key)
  ensure_not_freed!
  validate_length!(secret_key, length_secret_key, "secret_key")

  message_ptr = FFI::MemoryPointer.new(:uint8, message.bytesize)
  message_ptr.put_bytes(0, message)

  secret_key_ptr = FFI::MemoryPointer.new(:uint8, secret_key.bytesize)
  secret_key_ptr.put_bytes(0, secret_key)

  # signatureバッファは「最大長」で確保しておく。
  # 実際に書き込まれた長さは signature_len_ptr 経由で後から読み取る。
  signature_ptr = FFI::MemoryPointer.new(:uint8, length_signature)

  # OQS_SIG_signの第3引数(signature_len)は size_t* の出力引数。
  # FFI::MemoryPointer.new(:size_t, 1) で size_t 1個分の領域を確保し、
  # 関数呼び出し後に read_uint64 (64bit環境でのsize_t) で読み出す。
  signature_len_ptr = FFI::MemoryPointer.new(:size_t, 1)

  status = Ffi::Sig.OQS_SIG_sign(
    @sig_ptr,
    signature_ptr,
    signature_len_ptr,
    message_ptr,
    message.bytesize,
    secret_key_ptr
  )
  raise PqcRails::Error, "OQS_SIG_sign failed (status=#{status})" unless status.zero?

  actual_signature_len = signature_len_ptr.read(:size_t)
  signature_ptr.read_bytes(actual_signature_len)
end

#verify(message, signature, public_key) ⇒ Boolean

署名を検証する

Parameters:

  • message (String)

    元のメッセージのバイト列

  • signature (String)

    検証する署名のバイト列

  • public_key (String)

    バイト列

Returns:

  • (Boolean)

    署名が正当であれば true



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/pqc_rails/sig.rb', line 104

def verify(message, signature, public_key)
  ensure_not_freed!
  validate_length!(public_key, length_public_key, "public_key")

  # FIPS 204 3.6.3 (Public-Key and Signature Length Checks) は、
  # 検証実装がσ(署名)の長さを規格上の長さと照合し、異なればfalseを返すことを要求している。
  # length_signatureは最大長のため、超過した時点でliboqsを呼ばずにfalseを返す
  # (verifyの契約上、不正な入力は例外ではなく検証失敗として扱う。上のpublic_keyチェックとは
  # 非対称だが、public_keyは通常呼び出し側が管理する信頼済みの値である一方、
  # signatureは検証対象そのもの=攻撃者制御下にありうる値のため、扱いを分けている)。
  return false if signature.bytesize > length_signature

  message_ptr = FFI::MemoryPointer.new(:uint8, message.bytesize)
  message_ptr.put_bytes(0, message)

  signature_ptr = FFI::MemoryPointer.new(:uint8, signature.bytesize)
  signature_ptr.put_bytes(0, signature)

  public_key_ptr = FFI::MemoryPointer.new(:uint8, public_key.bytesize)
  public_key_ptr.put_bytes(0, public_key)

  status = Ffi::Sig.OQS_SIG_verify(
    @sig_ptr,
    message_ptr,
    message.bytesize,
    signature_ptr,
    signature.bytesize,
    public_key_ptr
  )

  # OQS_SIG_verifyは「検証失敗」も含めてOQS_ERRORを返す設計。
  # つまりここでのstatusはエラー(例外送出)ではなく、検証結果そのものとして扱う。
  status.zero?
end