summaryrefslogtreecommitdiff
path: root/test/openssl/test_pkey_dh.rb
blob: cf56032cb3048bb10d88e0e909c012f6410a89fe (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
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
98
99
100
101
102
103
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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# frozen_string_literal: true
require_relative 'utils'

if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH)

class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
  NEW_KEYLEN = 2048

  def test_new_empty
    dh = OpenSSL::PKey::DH.new
    assert_equal nil, dh.p
    assert_equal nil, dh.priv_key
  end

  def test_new_generate
    # This test is slow
    dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
    assert_key(dh)
  end if ENV["OSSL_TEST_ALL"]

  def test_new_break
    unless openssl? && OpenSSL.fips_mode
      assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break })
      assert_raise(RuntimeError) do
        OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise }
      end
    else
      # The block argument is not executed in FIPS case.
      # See https://github.com/ruby/openssl/issues/692 for details.
      assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break })
      assert(OpenSSL::PKey::DH.new(NEW_KEYLEN) { raise })
    end
  end

  def test_derive_key
    params = Fixtures.pkey("dh2048_ffdhe2048")
    dh1 = OpenSSL::PKey.generate_key(params)
    dh2 = OpenSSL::PKey.generate_key(params)
    dh1_pub = OpenSSL::PKey.read(dh1.public_to_der)
    dh2_pub = OpenSSL::PKey.read(dh2.public_to_der)

    z = dh1.g.mod_exp(dh1.priv_key, dh1.p).mod_exp(dh2.priv_key, dh1.p).to_s(2)
    assert_equal z, dh1.derive(dh2_pub)
    assert_equal z, dh2.derive(dh1_pub)

    assert_raise(OpenSSL::PKey::PKeyError) { params.derive(dh1_pub) }
    assert_raise(OpenSSL::PKey::PKeyError) { dh1_pub.derive(params) }

    assert_equal z, dh1.compute_key(dh2.pub_key)
    assert_equal z, dh2.compute_key(dh1.pub_key)
  end

  def test_DHparams
    dh = Fixtures.pkey("dh2048_ffdhe2048")
    dh_params = dh.public_key

    asn1 = OpenSSL::ASN1::Sequence([
      OpenSSL::ASN1::Integer(dh.p),
      OpenSSL::ASN1::Integer(dh.g)
    ])
    key = OpenSSL::PKey::DH.new(asn1.to_der)
    assert_same_dh dh_params, key

    pem = <<~EOF
    -----BEGIN DH PARAMETERS-----
    MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
    +8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
    87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
    YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
    7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
    ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
    -----END DH PARAMETERS-----
    EOF

    key = OpenSSL::PKey::DH.new(pem)
    assert_same_dh dh_params, key
    key = OpenSSL::PKey.read(pem)
    assert_same_dh dh_params, key

    assert_equal asn1.to_der, dh.to_der
    assert_equal pem, dh.export
  end

  def test_public_key
    dh = Fixtures.pkey("dh2048_ffdhe2048")
    public_key = dh.public_key
    assert_no_key(public_key) #implies public_key.public? is false!
    assert_equal(dh.to_der, public_key.to_der)
    assert_equal(dh.to_pem, public_key.to_pem)
  end

  def test_generate_key
    # Deprecated in v3.0.0; incompatible with OpenSSL 3.0
    # Creates a copy with params only
    dh = Fixtures.pkey("dh2048_ffdhe2048").public_key
    assert_no_key(dh)
    dh.generate_key!
    assert_key(dh)

    dh2 = dh.public_key
    dh2.generate_key!
    assert_equal(dh.compute_key(dh2.pub_key), dh2.compute_key(dh.pub_key))
  end if !openssl?(3, 0, 0)

  def test_params_ok?
    # Skip the tests in old OpenSSL version 1.1.1c or early versions before
    # applying the following commits in OpenSSL 1.1.1d to make `DH_check`
    # function pass the RFC 7919 FFDHE group texts.
    # https://github.com/openssl/openssl/pull/9435
    if openssl? && !openssl?(1, 1, 1, 4)
      pend 'DH check for RFC 7919 FFDHE group texts is not implemented'
    end

    dh0 = Fixtures.pkey("dh2048_ffdhe2048")

    dh1 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
      OpenSSL::ASN1::Integer(dh0.p),
      OpenSSL::ASN1::Integer(dh0.g)
    ]))
    assert_equal(true, dh1.params_ok?)

    # AWS-LC automatically does parameter checks on the parsed params.
    if aws_lc?
      assert_raise(OpenSSL::PKey::DHError) {
        OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
          OpenSSL::ASN1::Integer(dh0.p + 1),
          OpenSSL::ASN1::Integer(dh0.g)
        ]))
      }
    else
      dh2 = OpenSSL::PKey::DH.new(OpenSSL::ASN1::Sequence([
        OpenSSL::ASN1::Integer(dh0.p + 1),
        OpenSSL::ASN1::Integer(dh0.g)
      ]))
      assert_equal(false, dh2.params_ok?)
    end

  end

  def test_params
    dh = Fixtures.pkey("dh2048_ffdhe2048")
    assert_kind_of(OpenSSL::BN, dh.p)
    assert_equal(dh.p, dh.params["p"])
    assert_kind_of(OpenSSL::BN, dh.g)
    assert_equal(dh.g, dh.params["g"])
    assert_nil(dh.pub_key)
    assert_nil(dh.params["pub_key"])
    assert_nil(dh.priv_key)
    assert_nil(dh.params["priv_key"])

    dhkey = OpenSSL::PKey.generate_key(dh)
    assert_equal(dh.params["p"], dhkey.params["p"])
    assert_kind_of(OpenSSL::BN, dhkey.pub_key)
    assert_equal(dhkey.pub_key, dhkey.params["pub_key"])
    assert_kind_of(OpenSSL::BN, dhkey.priv_key)
    assert_equal(dhkey.priv_key, dhkey.params["priv_key"])
  end

  def test_dup
    # Parameters only
    dh1 = Fixtures.pkey("dh2048_ffdhe2048")
    dh2 = dh1.dup
    assert_equal dh1.to_der, dh2.to_der
    assert_not_equal nil, dh1.p
    assert_not_equal nil, dh1.g
    assert_equal [dh1.p, dh1.g], [dh2.p, dh2.g]
    assert_equal nil, dh1.pub_key
    assert_equal nil, dh1.priv_key
    assert_equal [dh1.pub_key, dh1.priv_key], [dh2.pub_key, dh2.priv_key]

    # PKey is immutable in OpenSSL >= 3.0
    if !openssl?(3, 0, 0)
      dh2.set_pqg(dh2.p + 1, nil, dh2.g)
      assert_not_equal dh2.p, dh1.p
    end

    # With a key pair
    dh3 = OpenSSL::PKey.generate_key(Fixtures.pkey("dh2048_ffdhe2048"))
    dh4 = dh3.dup
    assert_equal dh3.to_der, dh4.to_der
    assert_equal dh1.to_der, dh4.to_der # encodes parameters only
    assert_equal [dh1.p, dh1.g], [dh4.p, dh4.g]
    assert_not_equal nil, dh3.pub_key
    assert_not_equal nil, dh3.priv_key
    assert_equal [dh3.pub_key, dh3.priv_key], [dh4.pub_key, dh4.priv_key]
  end

  def test_marshal
    dh = Fixtures.pkey("dh2048_ffdhe2048")
    deserialized = Marshal.load(Marshal.dump(dh))

    assert_equal dh.to_der, deserialized.to_der
  end

  private

  def assert_no_key(dh)
    assert_equal(false, dh.public?)
    assert_equal(false, dh.private?)
    assert_equal(nil, dh.pub_key)
    assert_equal(nil, dh.priv_key)
  end

  def assert_key(dh)
    assert(dh.public?)
    assert(dh.private?)
    assert(dh.pub_key)
    assert(dh.priv_key)
  end

  def assert_same_dh(expected, key)
    check_component(expected, key, [:p, :q, :g, :pub_key, :priv_key])
  end
end

end