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.
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: []