-
Notifications
You must be signed in to change notification settings - Fork 374
/
rsa.rb
203 lines (160 loc) · 6.74 KB
/
rsa.rb
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
# frozen_string_literal: true
module JWT
module JWK
class RSA < KeyBase # rubocop:disable Metrics/ClassLength
BINARY = 2
KTY = 'RSA'
KTYS = [KTY, OpenSSL::PKey::RSA, JWT::JWK::RSA].freeze
RSA_PUBLIC_KEY_ELEMENTS = %i[kty n e].freeze
RSA_PRIVATE_KEY_ELEMENTS = %i[d p q dp dq qi].freeze
RSA_KEY_ELEMENTS = (RSA_PRIVATE_KEY_ELEMENTS + RSA_PUBLIC_KEY_ELEMENTS).freeze
RSA_OPT_PARAMS = %i[p q dp dq qi].freeze
RSA_ASN1_SEQUENCE = (%i[n e d] + RSA_OPT_PARAMS).freeze # https://www.rfc-editor.org/rfc/rfc3447#appendix-A.1.2
def initialize(key, params = nil, options = {})
params ||= {}
# For backwards compatibility when kid was a String
params = { kid: params } if params.is_a?(String)
key_params = extract_key_params(key)
params = params.transform_keys(&:to_sym)
check_jwk_params!(key_params, params)
super(options, key_params.merge(params))
end
def keypair
rsa_key
end
def private?
rsa_key.private?
end
def public_key
rsa_key.public_key
end
def signing_key
rsa_key if private?
end
def verify_key
rsa_key.public_key
end
def export(options = {})
exported = parameters.clone
exported.reject! { |k, _| RSA_PRIVATE_KEY_ELEMENTS.include? k } unless private? && options[:include_private] == true
exported
end
def members
RSA_PUBLIC_KEY_ELEMENTS.each_with_object({}) { |i, h| h[i] = self[i] }
end
def key_digest
sequence = OpenSSL::ASN1::Sequence([OpenSSL::ASN1::Integer.new(public_key.n),
OpenSSL::ASN1::Integer.new(public_key.e)])
OpenSSL::Digest::SHA256.hexdigest(sequence.to_der)
end
def []=(key, value)
if RSA_KEY_ELEMENTS.include?(key.to_sym)
raise ArgumentError, 'cannot overwrite cryptographic key attributes'
end
super(key, value)
end
private
def rsa_key
@rsa_key ||= self.class.create_rsa_key(jwk_attributes(*(RSA_KEY_ELEMENTS - [:kty])))
end
def extract_key_params(key)
case key
when JWT::JWK::RSA
key.export(include_private: true)
when OpenSSL::PKey::RSA # Accept OpenSSL key as input
@rsa_key = key # Preserve the object to avoid recreation
parse_rsa_key(key)
when Hash
key.transform_keys(&:to_sym)
else
raise ArgumentError, 'key must be of type OpenSSL::PKey::RSA or Hash with key parameters'
end
end
def check_jwk_params!(key_params, params)
raise ArgumentError, 'cannot overwrite cryptographic key attributes' unless (RSA_KEY_ELEMENTS & params.keys).empty?
raise JWT::JWKError, "Incorrect 'kty' value: #{key_params[:kty]}, expected #{KTY}" unless key_params[:kty] == KTY
raise JWT::JWKError, 'Key format is invalid for RSA' unless key_params[:n] && key_params[:e]
end
def parse_rsa_key(key)
{
kty: KTY,
n: encode_open_ssl_bn(key.n),
e: encode_open_ssl_bn(key.e),
d: encode_open_ssl_bn(key.d),
p: encode_open_ssl_bn(key.p),
q: encode_open_ssl_bn(key.q),
dp: encode_open_ssl_bn(key.dmp1),
dq: encode_open_ssl_bn(key.dmq1),
qi: encode_open_ssl_bn(key.iqmp)
}.compact
end
def jwk_attributes(*attributes)
attributes.each_with_object({}) do |attribute, hash|
hash[attribute] = decode_open_ssl_bn(self[attribute])
end
end
def encode_open_ssl_bn(key_part)
return unless key_part
::JWT::Base64.url_encode(key_part.to_s(BINARY))
end
def decode_open_ssl_bn(jwk_data)
self.class.decode_open_ssl_bn(jwk_data)
end
class << self
def import(jwk_data)
new(jwk_data)
end
def decode_open_ssl_bn(jwk_data)
return nil unless jwk_data
OpenSSL::BN.new(::JWT::Base64.url_decode(jwk_data), BINARY)
end
def create_rsa_key_using_der(rsa_parameters)
validate_rsa_parameters!(rsa_parameters)
sequence = RSA_ASN1_SEQUENCE.each_with_object([]) do |key, arr|
next if rsa_parameters[key].nil?
arr << OpenSSL::ASN1::Integer.new(rsa_parameters[key])
end
if sequence.size > 2 # Append "two-prime" version for private key
sequence.unshift(OpenSSL::ASN1::Integer.new(0))
raise JWT::JWKError, 'Creating a RSA key with a private key requires the CRT parameters to be defined' if sequence.size < RSA_ASN1_SEQUENCE.size
end
OpenSSL::PKey::RSA.new(OpenSSL::ASN1::Sequence(sequence).to_der)
end
def create_rsa_key_using_sets(rsa_parameters)
validate_rsa_parameters!(rsa_parameters)
OpenSSL::PKey::RSA.new.tap do |rsa_key|
rsa_key.set_key(rsa_parameters[:n], rsa_parameters[:e], rsa_parameters[:d])
rsa_key.set_factors(rsa_parameters[:p], rsa_parameters[:q]) if rsa_parameters[:p] && rsa_parameters[:q]
rsa_key.set_crt_params(rsa_parameters[:dp], rsa_parameters[:dq], rsa_parameters[:qi]) if rsa_parameters[:dp] && rsa_parameters[:dq] && rsa_parameters[:qi]
end
end
def create_rsa_key_using_accessors(rsa_parameters) # rubocop:disable Metrics/AbcSize
validate_rsa_parameters!(rsa_parameters)
OpenSSL::PKey::RSA.new.tap do |rsa_key|
rsa_key.n = rsa_parameters[:n]
rsa_key.e = rsa_parameters[:e]
rsa_key.d = rsa_parameters[:d] if rsa_parameters[:d]
rsa_key.p = rsa_parameters[:p] if rsa_parameters[:p]
rsa_key.q = rsa_parameters[:q] if rsa_parameters[:q]
rsa_key.dmp1 = rsa_parameters[:dp] if rsa_parameters[:dp]
rsa_key.dmq1 = rsa_parameters[:dq] if rsa_parameters[:dq]
rsa_key.iqmp = rsa_parameters[:qi] if rsa_parameters[:qi]
end
end
def validate_rsa_parameters!(rsa_parameters)
return unless rsa_parameters.key?(:d)
parameters = RSA_OPT_PARAMS - rsa_parameters.keys
return if parameters.empty? || parameters.size == RSA_OPT_PARAMS.size
raise JWT::JWKError, 'When one of p, q, dp, dq or qi is given all the other optimization parameters also needs to be defined' # https://www.rfc-editor.org/rfc/rfc7518.html#section-6.3.2
end
if ::JWT.openssl_3?
alias create_rsa_key create_rsa_key_using_der
elsif OpenSSL::PKey::RSA.new.respond_to?(:set_key)
alias create_rsa_key create_rsa_key_using_sets
else
alias create_rsa_key create_rsa_key_using_accessors
end
end
end
end
end