sigstore 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +7 -0
  3. data/CODEOWNERS +6 -0
  4. data/LICENSE +201 -0
  5. data/README.md +26 -0
  6. data/data/_store/prod/root.json +165 -0
  7. data/data/_store/prod/trusted_root.json +114 -0
  8. data/data/_store/staging/root.json +107 -0
  9. data/data/_store/staging/trusted_root.json +87 -0
  10. data/lib/sigstore/error.rb +43 -0
  11. data/lib/sigstore/internal/json.rb +53 -0
  12. data/lib/sigstore/internal/key.rb +183 -0
  13. data/lib/sigstore/internal/keyring.rb +42 -0
  14. data/lib/sigstore/internal/merkle.rb +117 -0
  15. data/lib/sigstore/internal/set.rb +42 -0
  16. data/lib/sigstore/internal/util.rb +52 -0
  17. data/lib/sigstore/internal/x509.rb +460 -0
  18. data/lib/sigstore/models.rb +272 -0
  19. data/lib/sigstore/oidc.rb +149 -0
  20. data/lib/sigstore/policy.rb +104 -0
  21. data/lib/sigstore/rekor/checkpoint.rb +114 -0
  22. data/lib/sigstore/rekor/client.rb +136 -0
  23. data/lib/sigstore/signer.rb +280 -0
  24. data/lib/sigstore/trusted_root.rb +116 -0
  25. data/lib/sigstore/tuf/config.rb +46 -0
  26. data/lib/sigstore/tuf/error.rb +49 -0
  27. data/lib/sigstore/tuf/file.rb +96 -0
  28. data/lib/sigstore/tuf/keys.rb +42 -0
  29. data/lib/sigstore/tuf/roles.rb +106 -0
  30. data/lib/sigstore/tuf/root.rb +53 -0
  31. data/lib/sigstore/tuf/snapshot.rb +45 -0
  32. data/lib/sigstore/tuf/targets.rb +84 -0
  33. data/lib/sigstore/tuf/timestamp.rb +39 -0
  34. data/lib/sigstore/tuf/trusted_metadata_set.rb +193 -0
  35. data/lib/sigstore/tuf/updater.rb +267 -0
  36. data/lib/sigstore/tuf.rb +158 -0
  37. data/lib/sigstore/verifier.rb +492 -0
  38. data/lib/sigstore/version.rb +19 -0
  39. data/lib/sigstore.rb +44 -0
  40. metadata +128 -0
@@ -0,0 +1,492 @@
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_relative "trusted_root"
18
+ require_relative "internal/keyring"
19
+ require_relative "internal/merkle"
20
+ require_relative "internal/set"
21
+ require_relative "rekor/client"
22
+ require_relative "rekor/checkpoint"
23
+ require_relative "internal/x509"
24
+
25
+ module Sigstore
26
+ class Verifier
27
+ include Loggable
28
+
29
+ attr_reader :rekor_client
30
+
31
+ def initialize(rekor_client:, fulcio_cert_chain:, timestamp_authorities:, rekor_keyring:, ct_keyring:)
32
+ @rekor_client = rekor_client
33
+ @fulcio_cert_chain = fulcio_cert_chain
34
+ @timestamp_authorities = timestamp_authorities
35
+ @rekor_keyring = rekor_keyring
36
+ @ct_keyring = ct_keyring
37
+ end
38
+
39
+ def self.for_trust_root(trust_root:)
40
+ new(
41
+ rekor_client: Rekor::Client.new(url: trust_root.tlog_for_signing.base_url),
42
+ fulcio_cert_chain: trust_root.fulcio_cert_chain,
43
+ timestamp_authorities: trust_root.timestamp_authorities,
44
+ rekor_keyring: Internal::Keyring.new(keys: trust_root.rekor_keys),
45
+ ct_keyring: Internal::Keyring.new(keys: trust_root.ctfe_keys)
46
+ )
47
+ end
48
+
49
+ def self.production(trust_root: TrustedRoot.production)
50
+ for_trust_root(trust_root:)
51
+ end
52
+
53
+ def self.staging(trust_root: TrustedRoot.staging)
54
+ for_trust_root(trust_root:)
55
+ end
56
+
57
+ def verify(input:, policy:, offline:)
58
+ # First, establish a time for the signature. This timestamp is required to validate the certificate chain,
59
+ # so this step comes first.
60
+
61
+ bundle = input.sbundle
62
+ materials = bundle.verification_material
63
+
64
+ # 1)
65
+ # If the verification policy uses the Timestamping Service, the Verifier MUST verify the timestamping response
66
+ # using the Timestamping Service root key material, as described in Spec: Timestamping Service, with the raw bytes
67
+ # of the signature as the timestamped data. The Verifier MUST then extract a timestamp from the timestamping
68
+ # response. If verification or timestamp parsing fails, the Verifier MUST abort.
69
+
70
+ timestamps = extract_timestamp_from_verification_data(materials.timestamp_verification_data) || []
71
+
72
+ # 2)
73
+ # If the verification policy uses timestamps from the Transparency Service, the Verifier MUST verify the signature
74
+ # on the Transparency Service LogEntry as described in Spec: Transparency Service against the pre-distributed root
75
+ # key material from the transparency service. The Verifier SHOULD NOT (yet) attempt to parse the body.
76
+ # The Verifier MUST then parse the integratedTime as a Unix timestamp (seconds since January 1, 1970 UTC).
77
+ # If verification or timestamp parsing fails, the Verifier MUST abort.
78
+
79
+ begin
80
+ # TODO: should this instead be an input to the verify method?
81
+ # See https://docs.google.com/document/d/1kbhK2qyPPk8SLavHzYSDM8-Ueul9_oxIMVFuWMWKz0E/edit?disco=AAABQVV-gT0
82
+ entry = find_rekor_entry(bundle, input.hashed_input, offline:)
83
+ rescue Sigstore::Error::MissingRekorEntry
84
+ return VerificationFailure.new("Rekor entry not found")
85
+ else
86
+ if entry.inclusion_proof&.checkpoint
87
+ Internal::Merkle.verify_merkle_inclusion(entry)
88
+ Rekor::Checkpoint.verify_checkpoint(@rekor_keyring, entry)
89
+ elsif !offline
90
+ return VerificationFailure.new("Missing Rekor inclusion proof")
91
+ else
92
+ logger.warn "inclusion proof not present in bundle: skipping due to offline verification"
93
+ end
94
+ end
95
+
96
+ Internal::SET.verify_set(keyring: @rekor_keyring, entry:) if entry.inclusion_promise
97
+
98
+ timestamps << Time.at(entry.integrated_time).utc
99
+
100
+ # TODO: implement this step
101
+
102
+ store = OpenSSL::X509::Store.new
103
+
104
+ @fulcio_cert_chain.each do |cert|
105
+ store.add_cert(cert.openssl)
106
+ end
107
+
108
+ # 3)
109
+ # The Verifier MUST perform certification path validation (RFC 5280 §6) of the certificate chain with the
110
+ # pre-distributed Fulcio root certificate(s) as a trust anchor, but with a fake “current time.”
111
+ # If a timestamp from the timestamping service is available, the Verifier MUST perform path validation using the
112
+ # timestamp from the Timestamping Service. If a timestamp from the Transparency Service is available, the Verifier
113
+ # MUST perform path validation using the timestamp from the Transparency Service. If both are available, the
114
+ # Verifier performs path validation twice. If either fails, verification fails.
115
+ chains = timestamps.map do |ts|
116
+ store_ctx = OpenSSL::X509::StoreContext.new(store, bundle.leaf_certificate.openssl)
117
+ store_ctx.time = ts
118
+
119
+ unless store_ctx.verify
120
+ return VerificationFailure.new(
121
+ "failed to validate certification from fulcio cert chain: #{store_ctx.error_string}"
122
+ )
123
+ end
124
+
125
+ chain = store_ctx.chain || raise(Error::InvalidCertificate, "no valid cert chain found")
126
+ chain.shift # remove the cert itself
127
+ chain.map! { Internal::X509::Certificate.new(_1) }
128
+ end
129
+
130
+ chains.uniq! { |chain| chain.map(&:to_der) }
131
+ unless chains.size == 1
132
+ raise "expected exactly one certificate chain, got #{chains.size} chains:\n" +
133
+ chains.map do |chain|
134
+ chain.map(&:to_text).join("\n")
135
+ end.join("\n\n")
136
+ end
137
+
138
+ # 4)
139
+ # Unless performing online verification (see §Alternative Workflows), the Verifier MUST extract the
140
+ # SignedCertificateTimestamp embedded in the leaf certificate, and verify it as in RFC 9162 §8.1.3,
141
+ # using the verification key from the Certificate Transparency Log.
142
+ chain = chains.first
143
+ if (result = verify_scts(bundle.leaf_certificate, chain)) && !result.verified?
144
+ return result
145
+ end
146
+
147
+ # 5)
148
+ # The Verifier MUST then check the certificate against the verification policy.
149
+
150
+ usage_ext = bundle.leaf_certificate.extension(Internal::X509::Extension::KeyUsage)
151
+ return VerificationFailure.new("Key usage is not of type `digital signature`") unless usage_ext.digital_signature
152
+
153
+ extended_key_usage = bundle.leaf_certificate.extension(Internal::X509::Extension::ExtendedKeyUsage)
154
+ unless extended_key_usage.code_signing?
155
+ return VerificationFailure.new("Extended key usage is not of type `code signing`")
156
+ end
157
+
158
+ policy_check = policy.verify(bundle.leaf_certificate)
159
+ return policy_check unless policy_check.verified?
160
+
161
+ # 6)
162
+ # By this point, the Verifier has already verified the signature by the Transparency Service (§Establishing a Time
163
+ # for the Signature). The Verifier MUST parse body: body is a base64-encoded JSON document with keys apiVersion
164
+ # and kind. The Verifier implementation contains a list of known Transparency Service formats (by apiVersion and
165
+ # kind); if no type is found, abort. The Verifier MUST parse body as the given type.
166
+ #
167
+ # Then, the Verifier MUST check the following; exactly how to do this will be specified by each type in Spec:
168
+ # Sigstore Registries (§Signature Metadata Formats):
169
+ #
170
+ # * The signature from the parsed body is the same as the provided signature.
171
+ # * The key or certificate from the parsed body is the same as in the input certificate.
172
+ # * The “subject” of the parsed body matches the artifact.
173
+
174
+ signing_key = bundle.leaf_certificate.public_key
175
+
176
+ case bundle.content
177
+ when :message_signature
178
+ verified = verify_raw(signing_key, bundle.message_signature.signature, input.hashed_input.digest)
179
+ return VerificationFailure.new("Signature verification failed") unless verified
180
+ when :dsse_envelope
181
+ verify_dsse(bundle.dsse_envelope, signing_key) or
182
+ return VerificationFailure.new("DSSE envelope verification failed")
183
+
184
+ case bundle.dsse_envelope.payloadType
185
+ when "application/vnd.in-toto+json"
186
+ verify_in_toto(input, JSON.parse(bundle.dsse_envelope.payload))
187
+ else
188
+ raise Sigstore::Error::Unimplemented,
189
+ "unsupported DSSE payload type: #{bundle.dsse_envelope.payloadType.inspect}"
190
+ end
191
+ else
192
+ raise Error::InvalidBundle, "unknown content type: #{bundle.content}"
193
+ end
194
+
195
+ VerificationSuccess.new
196
+ end
197
+
198
+ private
199
+
200
+ def verify_raw(public_key, signature, data)
201
+ if public_key.respond_to?(:verify_raw)
202
+ public_key.verify_raw(nil, signature, data)
203
+ else
204
+ case public_key
205
+ when OpenSSL::PKey::EC
206
+ public_key.dsa_verify_asn1(data, signature)
207
+ else
208
+ raise Error::Unimplemented, "unsupported public key type: #{public_key.class} for raw verification"
209
+ end
210
+ end
211
+ end
212
+
213
+ def verify_dsse(dsse_envelope, public_key)
214
+ payload = dsse_envelope.payload
215
+ payload_type = dsse_envelope.payloadType
216
+ signatures = dsse_envelope.signatures
217
+
218
+ pae = "DSSEv1 #{payload_type.bytesize} #{payload_type} " \
219
+ "#{payload.bytesize} #{payload}".b
220
+
221
+ raise Error::InvalidBundle, "DSSEv1 envelope missing signatures" if signatures.empty?
222
+
223
+ signatures.all? do |signature|
224
+ public_key.verify("SHA256", signature.sig, pae)
225
+ end
226
+ end
227
+
228
+ def verify_in_toto(input, in_toto_payload)
229
+ type = in_toto_payload.fetch("_type")
230
+ raise Error::InvalidBundle, "Expected in-toto statement, got #{type.inspect}" unless type == "https://in-toto.io/Statement/v1"
231
+
232
+ subject = in_toto_payload.fetch("subject")
233
+ raise Error::InvalidBundle, "Expected in-toto statement with subject" unless subject && subject.size == 1
234
+
235
+ subject = subject.first
236
+ digest = subject.fetch("digest")
237
+ raise Error::InvalidBundle, "Expected in-toto statement with digest" if !digest || digest.empty?
238
+
239
+ expected_hexdigest = Internal::Util.hex_encode(input.hashed_input.digest)
240
+ digest.each do |name, value|
241
+ next if expected_hexdigest == value
242
+
243
+ return VerificationFailure.new(
244
+ "in-toto subject does not match for #{input.hashed_input.algorithm} of #{subject.fetch("name")}: " \
245
+ "expected #{name} to be #{value}, got #{expected_hexdigest}"
246
+ )
247
+ end
248
+ end
249
+
250
+ public
251
+
252
+ def verify_scts(leaf_certificate, chain)
253
+ sct_list = leaf_certificate
254
+ .extension(Internal::X509::Extension::PrecertificateSignedCertificateTimestamps)
255
+ .signed_certificate_timestamps
256
+ raise Error::InvalidCertificate, "no SCTs found" if sct_list.empty?
257
+
258
+ sct_list.each do |sct|
259
+ verified = verify_sct(
260
+ sct,
261
+ leaf_certificate,
262
+ chain,
263
+ @ct_keyring
264
+ )
265
+ return VerificationFailure.new("SCT verification failed") unless verified
266
+ end
267
+
268
+ nil
269
+ end
270
+
271
+ private
272
+
273
+ def verify_sct(sct, certificate, chain, ct_keyring)
274
+ if sct.entry_type == 1
275
+ issuer_cert = find_issuer_cert(chain)
276
+ issuer_pubkey = issuer_cert.public_key
277
+ unless issuer_cert.ca?
278
+ raise Error::InvalidCertificate, "Invalid issuer pubkey basicConstraint (not a CA): #{issuer_cert.to_text}"
279
+ end
280
+
281
+ issuer_key_id = OpenSSL::Digest::SHA256.digest(issuer_pubkey.public_to_der)
282
+ end
283
+
284
+ digitally_signed = pack_digitally_signed(sct, certificate, issuer_key_id).b
285
+ ct_keyring.verify(key_id: sct.log_id, signature: sct.signature, data: digitally_signed)
286
+ end
287
+
288
+ def pack_digitally_signed(sct, certificate, issuer_key_id = nil)
289
+ # https://datatracker.ietf.org/doc/html/rfc6962#section-3.4
290
+ # https://datatracker.ietf.org/doc/html/rfc6962#section-3.5
291
+ #
292
+ # digitally-signed struct {
293
+ # Version sct_version;
294
+ # SignatureType signature_type = certificate_timestamp;
295
+ # uint64 timestamp;
296
+ # LogEntryType entry_type;
297
+ # select(entry_type) {
298
+ # case x509_entry: ASN.1Cert;
299
+ # case precert_entry: PreCert;
300
+ # } signed_entry;
301
+ # CtExtensions extensions;
302
+ # };
303
+
304
+ signed_entry =
305
+ case sct.entry_type
306
+ when 0 # x509_entry
307
+ cert_der = certificate.to_public_der
308
+ cert_len = cert_der.bytesize
309
+ unused, len1, len2, len3 = [cert_len].pack("N").unpack("C4")
310
+ raise Error::InvalidCertificate, "invalid cert_len #{cert_len} #{cert_der.inspect}" if unused != 0
311
+
312
+ [len1, len2, len3, cert_der].pack("CCC a#{cert_len}")
313
+ when 1 # precert_entry
314
+ unless issuer_key_id&.bytesize == 32
315
+ raise Error::InvalidCertificate,
316
+ "issuer_key_id must be 32 bytes for precert, given #{issuer_key_id.inspect}"
317
+ end
318
+
319
+ tbs_cert = certificate.tbs_certificate_der
320
+ tbs_cert_len = tbs_cert.bytesize
321
+ unused, len1, len2, len3 = [tbs_cert_len].pack("N").unpack("C4")
322
+ raise Error::InvalidCertificate, "invalid tbs_cert_len #{tbs_cert_len} #{tbs_cert.inspect}" if unused != 0
323
+
324
+ [issuer_key_id, len1, len2, len3, tbs_cert].pack("a32 CCC a#{tbs_cert_len}")
325
+ else
326
+ raise Error::Unimplemented, "only x509_entry and precert_entry supported, given #{sct[:entry_type].inspect}"
327
+ end
328
+
329
+ [sct.version, 0, sct.timestamp, sct.entry_type, signed_entry, 0].pack(<<~PACK)
330
+ C # version
331
+ C # signature_type
332
+ Q> # timestamp
333
+ n # entry_type
334
+ a#{signed_entry.bytesize} # signed_entry
335
+ n # extensions length
336
+ PACK
337
+ end
338
+
339
+ def find_issuer_cert(chain)
340
+ issuer = chain[0]
341
+ issuer = chain[1] if issuer.preissuer?
342
+ raise Error::InvalidCertificate, "no issuer certificate found" unless issuer
343
+
344
+ issuer
345
+ end
346
+
347
+ def extract_timestamp_from_verification_data(data)
348
+ # TODO: allow requiring a verified timestamp
349
+ unless data
350
+ logger.debug { "no timestamp verification data" }
351
+ return nil
352
+ end
353
+
354
+ # Checks for https://github.com/ruby/openssl/pull/770
355
+ if OpenSSL::X509::Store.new.instance_variable_defined?(:@time)
356
+ logger.warn do
357
+ "OpenSSL::X509::Store on this version of openssl (#{OpenSSL::VERSION}) does not set time properly, " \
358
+ "this breaks TSA verification"
359
+ end
360
+ return
361
+ end
362
+
363
+ authorities = @timestamp_authorities.map do |ta|
364
+ store = OpenSSL::X509::Store.new
365
+ chain = ta.cert_chain.certificates.map do |cert|
366
+ Internal::X509::Certificate.read(cert.raw_bytes).openssl
367
+ end
368
+ chain.each do |cert|
369
+ store.add_cert(cert)
370
+ end
371
+ [ta, chain, store]
372
+ end
373
+
374
+ # https://www.rfc-editor.org/rfc/rfc3161.html#section-2.4.2
375
+ data.rfc3161_timestamps.map do |ts|
376
+ resp = OpenSSL::Timestamp::Response.new(ts.signed_timestamp)
377
+
378
+ req = OpenSSL::Timestamp::Request.new
379
+ req.cert_requested = !resp.token.certificates.empty?
380
+ # TODO: verify the message imprint against the signature in the bundle
381
+ req.message_imprint = resp.token_info.message_imprint
382
+ req.algorithm = resp.token_info.algorithm
383
+ req.policy_id = resp.token_info.policy_id
384
+ req.nonce = resp.token_info.nonce
385
+ req.version = resp.token_info.version
386
+
387
+ # TODO: verify the hashed message in the message imprint
388
+ # against the signature in the bundle
389
+
390
+ authorities.any? do |ta, chain, store|
391
+ store.time = resp.token_info.gen_time
392
+
393
+ resp.verify(req, store, chain) &&
394
+ (logger.debug do
395
+ "timestamp (#{resp.to_text}) verified for #{ta}"
396
+ end || true)
397
+ rescue OpenSSL::Timestamp::TimestampError => e
398
+ logger.error { "timestamp verification failed (#{e})" }
399
+ false
400
+ end ||
401
+ raise(OpenSSL::Timestamp::TimestampError, "timestamp verification failed")
402
+ resp.token_info.gen_time
403
+ end
404
+ end
405
+
406
+ def find_rekor_entry(bundle, hashed_input, offline:)
407
+ raise Error::InvalidBundle, "multiple tlog entries" if bundle.verification_material.tlog_entries.size > 1
408
+
409
+ rekor_entry = bundle.verification_material.tlog_entries&.first
410
+ has_inclusion_promise = !rekor_entry.nil? && !rekor_entry.inclusion_promise.nil?
411
+ has_inclusion_proof = !rekor_entry.nil? && !rekor_entry.inclusion_proof&.checkpoint.nil?
412
+
413
+ logger.debug do
414
+ "Looking for rekor entry, " \
415
+ "has_inclusion_promise=#{has_inclusion_promise} has_inclusion_proof=#{has_inclusion_proof}"
416
+ end
417
+
418
+ expected_entry = bundle.expected_tlog_entry(hashed_input)
419
+
420
+ entry = if offline
421
+ logger.debug { "Offline verification, skipping rekor" }
422
+ rekor_entry
423
+ elsif !has_inclusion_proof
424
+ logger.debug { "No inclusion proof, searching rekor" }
425
+ @rekor_client.log.entries.retrieve.post(expected_entry)
426
+ else
427
+ logger.debug { "Using rekor entry in sigstore bundle" }
428
+ rekor_entry
429
+ end
430
+
431
+ raise Error::MissingRekorEntry, "Rekor entry not found" unless entry
432
+
433
+ logger.debug { "Found rekor entry: #{entry}" }
434
+
435
+ actual_body = JSON.parse(entry.canonicalized_body)
436
+ if bundle.dsse_envelope?
437
+ # since the hash is over the uncanonicalized envelope, we need to remove it
438
+ #
439
+ # NOTE(sigstore-python): This is very slightly weaker than the consistency check
440
+ # for hashedrekord entries, due to how inclusion is recorded for DSSE:
441
+ # the included entry for DSSE includes an envelope hash that we
442
+ # *cannot* verify, since the envelope is uncanonicalized JSON.
443
+ # Instead, we manually pick apart the entry body below and verify
444
+ # the parts we can (namely the payload hash and signature list).
445
+ case actual_body["kind"]
446
+ when "intoto"
447
+ actual_body["spec"]["content"].delete("hash")
448
+ when "dsse"
449
+ actual_body["spec"].delete("envelopeHash")
450
+ else
451
+ raise Error::InvalidRekorEntry, "Unknown kind: #{actual_body["kind"]}"
452
+ end
453
+ end
454
+
455
+ if actual_body != expected_entry
456
+ require "pp"
457
+ raise Error::InvalidRekorEntry, "Invalid rekor entry:\n\n" \
458
+ "Envelope:\n#{bundle.dsse_envelope.pretty_inspect}\n\n" \
459
+ "Diff:\n#{diff_json(expected_entry, actual_body).pretty_inspect}"
460
+ end
461
+
462
+ entry
463
+ end
464
+
465
+ def diff_json(a, b) # rubocop:disable Naming/MethodParameterName
466
+ return nil if a == b
467
+
468
+ return [a, b] if a.class != b.class
469
+
470
+ case a
471
+ when Hash
472
+ (a.keys | b.keys).to_h do |k|
473
+ [k, diff_json(a[k], b[k])]
474
+ end.compact
475
+ when Array
476
+ a.zip(b).filter_map { |x, y| diff_json(x, y) }
477
+ when String
478
+ begin
479
+ da = a.unpack1("m0")
480
+ db = b.unpack1("m0")
481
+
482
+ [{ "decoded" => da, "base64" => a },
483
+ { "decoded" => db, "base64" => b }]
484
+ rescue ArgumentError
485
+ [a, b]
486
+ end
487
+ else
488
+ [a, b]
489
+ end
490
+ end
491
+ end
492
+ end
@@ -0,0 +1,19 @@
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
+ module Sigstore
18
+ VERSION = "0.1.1"
19
+ end
data/lib/sigstore.rb ADDED
@@ -0,0 +1,44 @@
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
+ module Sigstore
18
+ class << self
19
+ attr_writer :logger
20
+
21
+ def logger
22
+ @logger ||= begin
23
+ require "logger"
24
+ Logger.new($stderr)
25
+ end
26
+ end
27
+ end
28
+
29
+ module Loggable
30
+ def logger
31
+ self.class.logger
32
+ end
33
+
34
+ def self.included(base)
35
+ base.extend(ClassMethods)
36
+ end
37
+
38
+ module ClassMethods
39
+ def logger
40
+ Sigstore.logger
41
+ end
42
+ end
43
+ end
44
+ end
metadata ADDED
@@ -0,0 +1,128 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sigstore
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - The Sigstore Authors
8
+ - Samuel Giddins
9
+ autorequire:
10
+ bindir: exe
11
+ cert_chain: []
12
+ date: 2024-10-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-http
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: protobug_sigstore_protos
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 0.1.0
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 0.1.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: uri
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ description:
57
+ email:
58
+ -
59
+ - segiddins@segiddins.me
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - CHANGELOG.md
65
+ - CODEOWNERS
66
+ - LICENSE
67
+ - README.md
68
+ - data/_store/prod/root.json
69
+ - data/_store/prod/trusted_root.json
70
+ - data/_store/staging/root.json
71
+ - data/_store/staging/trusted_root.json
72
+ - lib/sigstore.rb
73
+ - lib/sigstore/error.rb
74
+ - lib/sigstore/internal/json.rb
75
+ - lib/sigstore/internal/key.rb
76
+ - lib/sigstore/internal/keyring.rb
77
+ - lib/sigstore/internal/merkle.rb
78
+ - lib/sigstore/internal/set.rb
79
+ - lib/sigstore/internal/util.rb
80
+ - lib/sigstore/internal/x509.rb
81
+ - lib/sigstore/models.rb
82
+ - lib/sigstore/oidc.rb
83
+ - lib/sigstore/policy.rb
84
+ - lib/sigstore/rekor/checkpoint.rb
85
+ - lib/sigstore/rekor/client.rb
86
+ - lib/sigstore/signer.rb
87
+ - lib/sigstore/trusted_root.rb
88
+ - lib/sigstore/tuf.rb
89
+ - lib/sigstore/tuf/config.rb
90
+ - lib/sigstore/tuf/error.rb
91
+ - lib/sigstore/tuf/file.rb
92
+ - lib/sigstore/tuf/keys.rb
93
+ - lib/sigstore/tuf/roles.rb
94
+ - lib/sigstore/tuf/root.rb
95
+ - lib/sigstore/tuf/snapshot.rb
96
+ - lib/sigstore/tuf/targets.rb
97
+ - lib/sigstore/tuf/timestamp.rb
98
+ - lib/sigstore/tuf/trusted_metadata_set.rb
99
+ - lib/sigstore/tuf/updater.rb
100
+ - lib/sigstore/verifier.rb
101
+ - lib/sigstore/version.rb
102
+ homepage: https://github.com/sigstore/sigstore-ruby
103
+ licenses:
104
+ - Apache-2.0
105
+ metadata:
106
+ allowed_push_host: https://rubygems.org
107
+ homepage_uri: https://github.com/sigstore/sigstore-ruby
108
+ rubygems_mfa_required: 'true'
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: 3.1.0
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubygems_version: 3.5.16
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: A pure-ruby implementation of sigstore signature verification
128
+ test_files: []