sigstore 0.1.1
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 +7 -0
- data/CODEOWNERS +6 -0
- data/LICENSE +201 -0
- data/README.md +26 -0
- data/data/_store/prod/root.json +165 -0
- data/data/_store/prod/trusted_root.json +114 -0
- data/data/_store/staging/root.json +107 -0
- data/data/_store/staging/trusted_root.json +87 -0
- data/lib/sigstore/error.rb +43 -0
- data/lib/sigstore/internal/json.rb +53 -0
- data/lib/sigstore/internal/key.rb +183 -0
- data/lib/sigstore/internal/keyring.rb +42 -0
- data/lib/sigstore/internal/merkle.rb +117 -0
- data/lib/sigstore/internal/set.rb +42 -0
- data/lib/sigstore/internal/util.rb +52 -0
- data/lib/sigstore/internal/x509.rb +460 -0
- data/lib/sigstore/models.rb +272 -0
- data/lib/sigstore/oidc.rb +149 -0
- data/lib/sigstore/policy.rb +104 -0
- data/lib/sigstore/rekor/checkpoint.rb +114 -0
- data/lib/sigstore/rekor/client.rb +136 -0
- data/lib/sigstore/signer.rb +280 -0
- data/lib/sigstore/trusted_root.rb +116 -0
- data/lib/sigstore/tuf/config.rb +46 -0
- data/lib/sigstore/tuf/error.rb +49 -0
- data/lib/sigstore/tuf/file.rb +96 -0
- data/lib/sigstore/tuf/keys.rb +42 -0
- data/lib/sigstore/tuf/roles.rb +106 -0
- data/lib/sigstore/tuf/root.rb +53 -0
- data/lib/sigstore/tuf/snapshot.rb +45 -0
- data/lib/sigstore/tuf/targets.rb +84 -0
- data/lib/sigstore/tuf/timestamp.rb +39 -0
- data/lib/sigstore/tuf/trusted_metadata_set.rb +193 -0
- data/lib/sigstore/tuf/updater.rb +267 -0
- data/lib/sigstore/tuf.rb +158 -0
- data/lib/sigstore/verifier.rb +492 -0
- data/lib/sigstore/version.rb +19 -0
- data/lib/sigstore.rb +44 -0
- metadata +128 -0
@@ -0,0 +1,460 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright 2024 The Sigstore Authors
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require "forwardable"
|
18
|
+
require "openssl"
|
19
|
+
|
20
|
+
module Sigstore
|
21
|
+
module Internal
|
22
|
+
module X509
|
23
|
+
class Certificate
|
24
|
+
extend Forwardable
|
25
|
+
|
26
|
+
attr_reader :openssl
|
27
|
+
|
28
|
+
def initialize(x509_certificate)
|
29
|
+
unless x509_certificate.is_a?(OpenSSL::X509::Certificate)
|
30
|
+
raise ArgumentError,
|
31
|
+
"Invalid certificate: #{x509_certificate.inspect}"
|
32
|
+
end
|
33
|
+
|
34
|
+
@openssl = x509_certificate
|
35
|
+
|
36
|
+
raise Error::InvalidCertificate, "invalid X.509 version: #{version.inspect}" if version != 2 # v3
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.read(certificate_bytes)
|
40
|
+
new(OpenSSL::X509::Certificate.new(certificate_bytes))
|
41
|
+
end
|
42
|
+
|
43
|
+
def tbs_certificate_der
|
44
|
+
if openssl.respond_to?(:tbs_bytes)
|
45
|
+
cert = openssl.dup
|
46
|
+
short_name = Extension::PrecertificateSignedCertificateTimestamps.oid.short_name
|
47
|
+
cert.extensions = cert.extensions.reject! do |ext|
|
48
|
+
ext.oid == short_name
|
49
|
+
end || raise(Error::InvalidCertificate,
|
50
|
+
"No PrecertificateSignedCertificateTimestamps found for the certificate")
|
51
|
+
return cert.tbs_bytes
|
52
|
+
end
|
53
|
+
|
54
|
+
extension(Extension::PrecertificateSignedCertificateTimestamps) ||
|
55
|
+
raise(Error::InvalidCertificate,
|
56
|
+
"No PrecertificateSignedCertificateTimestamps found for the certificate")
|
57
|
+
|
58
|
+
# This uglyness is needed because there is no way to force modifying an X509 certificate
|
59
|
+
# in a way that it will be serialized with the modifications.
|
60
|
+
seq = OpenSSL::ASN1.decode(to_der)
|
61
|
+
unless seq.is_a?(OpenSSL::ASN1::Sequence) && seq.value.size == 3
|
62
|
+
raise Error::InvalidCertificate,
|
63
|
+
"invalid X.509 certificate: #{seq.class} #{seq.value.size}"
|
64
|
+
end
|
65
|
+
seq = seq.value[0]
|
66
|
+
unless seq.is_a?(OpenSSL::ASN1::Sequence)
|
67
|
+
raise Error::InvalidCertificate,
|
68
|
+
"invalid X.509 certificate: #{seq.inspect}"
|
69
|
+
end
|
70
|
+
|
71
|
+
seq.value = seq.value.map! do |v|
|
72
|
+
next v unless v.tag == 3
|
73
|
+
|
74
|
+
v.value = v.value.map! do |v2|
|
75
|
+
v2.value = v2.value.map! do |v3|
|
76
|
+
next if v3.first.oid == Extension::PrecertificateSignedCertificateTimestamps.oid.oid
|
77
|
+
|
78
|
+
v3
|
79
|
+
end.compact! || raise(Error::InvalidCertificate, "no SCTs found")
|
80
|
+
v2
|
81
|
+
end
|
82
|
+
v
|
83
|
+
end
|
84
|
+
|
85
|
+
seq.to_der
|
86
|
+
end
|
87
|
+
|
88
|
+
def extension(cls)
|
89
|
+
openssl.extensions.each do |ext|
|
90
|
+
return cls.new(ext) if ext.oid == cls.oid || ext.oid == cls.oid.short_name || ext.oid == cls.oid.oid
|
91
|
+
end
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def_delegators :openssl, :version, :not_after, :not_before, :to_pem, :to_der,
|
96
|
+
:public_key, :to_text, :subject, :hash
|
97
|
+
|
98
|
+
def ==(other)
|
99
|
+
openssl == other.openssl
|
100
|
+
end
|
101
|
+
|
102
|
+
def leaf?
|
103
|
+
return false if ca?
|
104
|
+
|
105
|
+
key_usage = extension(Extension::KeyUsage) ||
|
106
|
+
raise(Error::InvalidCertificate,
|
107
|
+
"no keyUsage in #{@x509_certificate.extensions.map(&:to_h)}")
|
108
|
+
|
109
|
+
unless key_usage.digital_signature
|
110
|
+
raise Error::InvalidCertificate,
|
111
|
+
"invalid certificate for Sigstore purposes: missing digital signature usage: #{key_usage.to_h}"
|
112
|
+
end
|
113
|
+
|
114
|
+
extended_key_usage = extension(Extension::ExtendedKeyUsage)
|
115
|
+
return false unless extended_key_usage
|
116
|
+
|
117
|
+
extended_key_usage.code_signing?
|
118
|
+
end
|
119
|
+
|
120
|
+
def ca?
|
121
|
+
basic_constraints = extension(Extension::BasicConstraints)
|
122
|
+
return false unless basic_constraints
|
123
|
+
|
124
|
+
unless basic_constraints.critical?
|
125
|
+
raise Error::InvalidCertificate,
|
126
|
+
"invalid X.509 certificate: non-critical BasicConstraints in CA"
|
127
|
+
end
|
128
|
+
|
129
|
+
key_usage = extension(Extension::KeyUsage)
|
130
|
+
raise Error::InvalidCertificate, "no keyUsage in #{openssl.inspect}" unless key_usage
|
131
|
+
|
132
|
+
ca = basic_constraints.ca
|
133
|
+
key_cert_sign = key_usage.key_cert_sign
|
134
|
+
|
135
|
+
return true if ca && key_cert_sign
|
136
|
+
|
137
|
+
return false unless key_cert_sign || ca
|
138
|
+
|
139
|
+
raise Error::InvalidCertificate,
|
140
|
+
"invalid X.509 certificate: inconsistent CA/KeyCertSign in BasicConstraints/KeyUsage " \
|
141
|
+
"(#{ca.inspect}, #{key_cert_sign.inspect}):" \
|
142
|
+
"\n#{openssl.extensions.map(&:to_h).pretty_inspect}" \
|
143
|
+
"\n#{key_usage.pretty_inspect}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def preissuer?
|
147
|
+
extended_key_usage = extension(Extension::ExtendedKeyUsage)
|
148
|
+
return false unless extended_key_usage
|
149
|
+
|
150
|
+
extended_key_usage.purposes.include?(OpenSSL::ASN1::ObjectId.new("1.3.6.1.4.1.11129.2.4.4"))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
class Extension
|
155
|
+
class << self
|
156
|
+
attr_accessor :oid, :schema
|
157
|
+
end
|
158
|
+
|
159
|
+
def initialize(extension)
|
160
|
+
@extension = extension
|
161
|
+
value = shift_value([OpenSSL::ASN1.decode(extension.to_der)], OpenSSL::ASN1::Sequence)
|
162
|
+
@oid = value.shift
|
163
|
+
|
164
|
+
unless @extension.is_a?(OpenSSL::X509::Extension) && @oid.oid == self.class.oid.oid
|
165
|
+
raise ArgumentError,
|
166
|
+
"Invalid extension: #{@extension.inspect} is not a #{@oid.inspect} " \
|
167
|
+
"(#{self.class} / #{self.class.oid.inspect})"
|
168
|
+
end
|
169
|
+
|
170
|
+
@critical = false
|
171
|
+
@critical = value.shift.value if value.first.is_a?(OpenSSL::ASN1::Boolean)
|
172
|
+
raise ArgumentError, "Mis-parsed the critical bit" unless @critical == @extension.critical?
|
173
|
+
|
174
|
+
contents = shift_value(value, OpenSSL::ASN1::OctetString)
|
175
|
+
raise ArgumentError, "Invalid extension: extra fields left in #{self}: #{value}" unless value.empty?
|
176
|
+
|
177
|
+
parse_value(OpenSSL::ASN1.decode(contents))
|
178
|
+
rescue OpenSSL::ASN1::ASN1Error => e
|
179
|
+
raise ArgumentError, "Invalid extension: #{e.message} for #{self.class.oid}\n#{extension.inspect}"
|
180
|
+
end
|
181
|
+
|
182
|
+
def critical?
|
183
|
+
@extension.critical?
|
184
|
+
end
|
185
|
+
|
186
|
+
def shift_value(value, klass)
|
187
|
+
v = value.shift
|
188
|
+
raise ArgumentError, "Invalid extension: #{v} is not a #{klass}" unless v.is_a?(klass)
|
189
|
+
|
190
|
+
v.value
|
191
|
+
end
|
192
|
+
|
193
|
+
def shift_bitstring(value)
|
194
|
+
raise ArgumentError, "Invalid bit string: #{value.inspect}" unless value.is_a?(OpenSSL::ASN1::BitString)
|
195
|
+
|
196
|
+
value.value.each_byte.flat_map do |byte|
|
197
|
+
[byte & 0b1000_0000 != 0, byte & 0b0100_0000 != 0, byte & 0b0010_0000 != 0, byte & 0b0001_0000 != 0,
|
198
|
+
byte & 0b0000_1000 != 0, byte & 0b0000_0100 != 0, byte & 0b0000_0010 != 0, byte & 0b0000_0001 != 0]
|
199
|
+
end[..-value.unused_bits.succ]
|
200
|
+
end
|
201
|
+
|
202
|
+
class SubjectKeyIdentifier < Extension
|
203
|
+
attr_reader :key_identifier
|
204
|
+
|
205
|
+
self.oid = OpenSSL::ASN1::ObjectId.new("2.5.29.14")
|
206
|
+
|
207
|
+
def parse_value(value)
|
208
|
+
unless value.is_a?(OpenSSL::ASN1::OctetString)
|
209
|
+
raise ArgumentError,
|
210
|
+
"Invalid key identifier: #{value.inspect}"
|
211
|
+
end
|
212
|
+
|
213
|
+
@key_identifier = value.value
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
class KeyUsage < Extension
|
218
|
+
self.oid = OpenSSL::ASN1::ObjectId.new("2.5.29.15")
|
219
|
+
|
220
|
+
attr_reader :digital_signature, :non_repudiation, :key_encipherment, :data_encipherment, :key_agreement,
|
221
|
+
:key_cert_sign, :crl_sign, :encipher_only, :decipher_only
|
222
|
+
|
223
|
+
def parse_value(value)
|
224
|
+
@digital_signature, @non_repudiation, @key_encipherment, @data_encipherment, @key_agreement, @key_cert_sign,
|
225
|
+
@crl_sign, @encipher_only, @decipher_only =
|
226
|
+
shift_bitstring(value)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
class ExtendedKeyUsage < Extension
|
231
|
+
self.oid = OpenSSL::ASN1::ObjectId.new("2.5.29.37")
|
232
|
+
|
233
|
+
attr_reader :purposes
|
234
|
+
|
235
|
+
def parse_value(value)
|
236
|
+
unless value.is_a?(OpenSSL::ASN1::Sequence)
|
237
|
+
rasie ArgumentError,
|
238
|
+
"Invalid extended key usage: #{value.inspect}"
|
239
|
+
end
|
240
|
+
|
241
|
+
@purposes = value.value
|
242
|
+
return if @purposes.all?(OpenSSL::ASN1::ObjectId)
|
243
|
+
|
244
|
+
raise ArgumentError,
|
245
|
+
"Invalid extended key usage: #{value.inspect}"
|
246
|
+
end
|
247
|
+
|
248
|
+
CODE_SIGNING = OpenSSL::ASN1::ObjectId.new("1.3.6.1.5.5.7.3.3")
|
249
|
+
|
250
|
+
def code_signing?
|
251
|
+
purposes.any? { |purpose| purpose.oid == CODE_SIGNING.oid }
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
class BasicConstraints < Extension
|
256
|
+
self.oid = OpenSSL::ASN1::ObjectId.new("2.5.29.19")
|
257
|
+
|
258
|
+
attr_reader :ca, :path_len_constraint
|
259
|
+
|
260
|
+
def parse_value(value)
|
261
|
+
value = shift_value([value], OpenSSL::ASN1::Sequence)
|
262
|
+
|
263
|
+
@ca = false
|
264
|
+
@path_len_constraint = nil
|
265
|
+
|
266
|
+
@ca = shift_value(value, OpenSSL::ASN1::Boolean) if value.first.is_a?(OpenSSL::ASN1::Boolean)
|
267
|
+
|
268
|
+
return unless value.first.is_a?(OpenSSL::ASN1::Integer)
|
269
|
+
|
270
|
+
@path_len_constraint = shift_value(value, OpenSSL::ASN1::Integer)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
class SubjectAlternativeName < Extension
|
275
|
+
self.oid = OpenSSL::ASN1::ObjectId.new("2.5.29.17")
|
276
|
+
|
277
|
+
attr_reader :general_names
|
278
|
+
|
279
|
+
# id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
|
280
|
+
|
281
|
+
# SubjectAltName ::= GeneralNames
|
282
|
+
|
283
|
+
# GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
|
284
|
+
|
285
|
+
# GeneralName ::= CHOICE {
|
286
|
+
# otherName [0] OtherName,
|
287
|
+
# rfc822Name [1] IA5String,
|
288
|
+
# dNSName [2] IA5String,
|
289
|
+
# x400Address [3] ORAddress,
|
290
|
+
# directoryName [4] Name,
|
291
|
+
# ediPartyName [5] EDIPartyName,
|
292
|
+
# uniformResourceIdentifier [6] IA5String,
|
293
|
+
# iPAddress [7] OCTET STRING,
|
294
|
+
# registeredID [8] OBJECT IDENTIFIER }
|
295
|
+
|
296
|
+
# OtherName ::= SEQUENCE {
|
297
|
+
# type-id OBJECT IDENTIFIER,
|
298
|
+
# value [0] EXPLICIT ANY DEFINED BY type-id }
|
299
|
+
|
300
|
+
# EDIPartyName ::= SEQUENCE {
|
301
|
+
# nameAssigner [0] DirectoryString OPTIONAL,
|
302
|
+
# partyName [1] DirectoryString }
|
303
|
+
|
304
|
+
def parse_value(value)
|
305
|
+
value = shift_value([value], OpenSSL::ASN1::Sequence)
|
306
|
+
|
307
|
+
@general_names = value.map do |general_name|
|
308
|
+
tag = general_name.tag
|
309
|
+
|
310
|
+
case tag
|
311
|
+
when 1
|
312
|
+
[:otherName, general_name.value]
|
313
|
+
when 6
|
314
|
+
[:uniformResourceIdentifier, general_name.value]
|
315
|
+
else
|
316
|
+
raise Error::Unimplemented,
|
317
|
+
"Unhandled general name tag: #{tag}"
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class PrecertificateSignedCertificateTimestamps < Extension
|
324
|
+
self.oid = OpenSSL::ASN1::ObjectId.new("1.3.6.1.4.1.11129.2.4.2")
|
325
|
+
|
326
|
+
attr_reader :signed_certificate_timestamps
|
327
|
+
|
328
|
+
def parse_value(value)
|
329
|
+
unless value.is_a?(OpenSSL::ASN1::OctetString)
|
330
|
+
raise ArgumentError,
|
331
|
+
"Invalid SCT extension: #{value.inspect}"
|
332
|
+
end
|
333
|
+
|
334
|
+
value = value.value
|
335
|
+
length = value.unpack1("n")
|
336
|
+
value = value.byteslice(2, length)
|
337
|
+
|
338
|
+
unless value && value.bytesize == length
|
339
|
+
raise Error::InvalidCertificate,
|
340
|
+
"decoding #{self.class.oid} extension"
|
341
|
+
end
|
342
|
+
|
343
|
+
length = value.unpack1("n")
|
344
|
+
value = value.byteslice(2, length)
|
345
|
+
|
346
|
+
unless value && value.bytesize == length
|
347
|
+
raise Error::InvalidCertificate,
|
348
|
+
"decoding #{self.class.oid} extension"
|
349
|
+
end
|
350
|
+
|
351
|
+
@signed_certificate_timestamps = unpack_sct_list(value)
|
352
|
+
end
|
353
|
+
|
354
|
+
args = %i[version
|
355
|
+
log_id
|
356
|
+
timestamp
|
357
|
+
extensions_bytes
|
358
|
+
hash_algorithm
|
359
|
+
signature_algorithm
|
360
|
+
entry_type
|
361
|
+
signature]
|
362
|
+
Timestamp = defined?(Data.define) ? Data.define(*args) : Struct.new(*args, keyword_init: true) # rubocop:disable Naming/ConstantName
|
363
|
+
|
364
|
+
HASHES = {
|
365
|
+
0 => "none",
|
366
|
+
1 => "md5",
|
367
|
+
2 => "sha1",
|
368
|
+
3 => "sha224",
|
369
|
+
4 => "sha256",
|
370
|
+
5 => "sha384",
|
371
|
+
6 => "sha512",
|
372
|
+
255 => "unknown"
|
373
|
+
}.freeze
|
374
|
+
|
375
|
+
SIGNATURE_ALGORITHMS = {
|
376
|
+
0 => "anonymous",
|
377
|
+
1 => "rsa",
|
378
|
+
2 => "dsa",
|
379
|
+
3 => "ecdsa",
|
380
|
+
255 => "unknown"
|
381
|
+
}.freeze
|
382
|
+
|
383
|
+
private
|
384
|
+
|
385
|
+
if RUBY_VERSION >= "3.1"
|
386
|
+
def unpack_at(string, format, offset:)
|
387
|
+
string.unpack(format, offset:)
|
388
|
+
end
|
389
|
+
|
390
|
+
def unpack1_at(string, format, offset:)
|
391
|
+
string.unpack1(format, offset:)
|
392
|
+
end
|
393
|
+
else
|
394
|
+
def unpack_at(string, format, offset:)
|
395
|
+
string[offset..].unpack(format)
|
396
|
+
end
|
397
|
+
|
398
|
+
def unpack1_at(string, format, offset:)
|
399
|
+
string[offset..].unpack1(format)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
# https://letsencrypt.org/2018/04/04/sct-encoding.html
|
404
|
+
def unpack_sct_list(string)
|
405
|
+
offset = 0
|
406
|
+
len = string.bytesize
|
407
|
+
list = []
|
408
|
+
while offset < len
|
409
|
+
sct_version, sct_log_id, sct_timestamp, sct_extensions_len = unpack_at(string, "Ca32Q>n", offset:)
|
410
|
+
offset += 1 + 32 + 8 + 2
|
411
|
+
raise Error::Unimplemented, "expect sct version to be 0, got #{sct_version}" unless sct_version.zero?
|
412
|
+
|
413
|
+
sct_extensions_bytes = unpack1_at(string, "a#{sct_extensions_len}", offset:).b
|
414
|
+
offset += sct_extensions_len
|
415
|
+
|
416
|
+
unless sct_extensions_len.zero?
|
417
|
+
raise Error::Unimplemented,
|
418
|
+
"sct_extensions_len=#{sct_extensions_len} not supported"
|
419
|
+
end
|
420
|
+
|
421
|
+
sct_signature_alg_hash, sct_signature_alg_sign, sct_signature_len = unpack_at(string, "CCn",
|
422
|
+
offset:)
|
423
|
+
offset += 1 + 1 + 2
|
424
|
+
sct_signature_bytes = unpack1_at(string, "a#{sct_signature_len}", offset:).b
|
425
|
+
offset += sct_signature_len
|
426
|
+
list << Timestamp.new(
|
427
|
+
version: sct_version,
|
428
|
+
log_id: sct_log_id.unpack1("H*"),
|
429
|
+
timestamp: sct_timestamp,
|
430
|
+
hash_algorithm: HASHES.fetch(sct_signature_alg_hash),
|
431
|
+
signature_algorithm: SIGNATURE_ALGORITHMS.fetch(sct_signature_alg_sign),
|
432
|
+
signature: sct_signature_bytes,
|
433
|
+
extensions_bytes: sct_extensions_bytes,
|
434
|
+
entry_type: 1 # X509LogEntryType::PRECERTIFICATE
|
435
|
+
)
|
436
|
+
end
|
437
|
+
raise Error::InvalidCertificate, "failed unpacking SCTs: offset=#{offset} len=#{len}" unless offset == len
|
438
|
+
|
439
|
+
list
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
class FulcioIssuer < Extension
|
444
|
+
self.oid = OpenSSL::ASN1::ObjectId.new("1.3.6.1.4.1.57264.1.8")
|
445
|
+
|
446
|
+
attr_reader :issuer
|
447
|
+
|
448
|
+
def parse_value(value)
|
449
|
+
unless value.is_a?(OpenSSL::ASN1::UTF8String)
|
450
|
+
raise ArgumentError,
|
451
|
+
"Invalid Fulcio issuer: #{value.inspect}"
|
452
|
+
end
|
453
|
+
|
454
|
+
@issuer = value.value
|
455
|
+
end
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|