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,136 @@
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 "net/http"
18
+
19
+ module Sigstore
20
+ module Rekor
21
+ class Client
22
+ DEFAULT_REKOR_URL = "https://rekor.sigstore.dev"
23
+ STAGING_REKOR_URL = "https://rekor.sigstage.dev"
24
+
25
+ def initialize(url:)
26
+ @url = URI.join(url, "api/v1/")
27
+
28
+ net = defined?(Gem::Net) ? Gem::Net : Net
29
+ @session = net::HTTP.new(@url.host, @url.port)
30
+ @session.use_ssl = true
31
+ end
32
+
33
+ def self.production
34
+ new(url: DEFAULT_REKOR_URL)
35
+ end
36
+
37
+ def self.staging
38
+ new(url: STAGING_REKOR_URL)
39
+ end
40
+
41
+ def log
42
+ Log.new(URI.join(@url, "log/"), session: @session)
43
+ end
44
+ end
45
+
46
+ class Log
47
+ def initialize(url, session:)
48
+ @url = url
49
+ @session = session
50
+ end
51
+
52
+ def entries
53
+ Entries.new(URI.join(@url, "entries/"), session: @session)
54
+ end
55
+ end
56
+
57
+ class Entries
58
+ def initialize(url, session:)
59
+ @url = url
60
+ @session = session
61
+ end
62
+
63
+ def retrieve
64
+ Retrieve.new(URI.join(@url, "retrieve/"), session: @session)
65
+ end
66
+
67
+ def post(entry)
68
+ resp = @session.post2(@url.path.chomp("/"), entry.to_json,
69
+ { "Content-Type" => "application/json", "Accept" => "application/json" })
70
+
71
+ unless resp.code == "201"
72
+ raise Error::FailedRekorPost,
73
+ "#{resp.code} #{resp.message.inspect}\n#{JSON.pretty_generate(entry)}\n#{resp.body}"
74
+ end
75
+ unless resp.content_type == "application/json"
76
+ raise Error::FailedRekorPost, "Unexpected content type: #{resp.content_type.inspect}"
77
+ end
78
+
79
+ body = JSON.parse(resp.body)
80
+ Entries.decode_transparency_log_entry(body)
81
+ end
82
+
83
+ class Retrieve
84
+ def initialize(url, session:)
85
+ @url = url
86
+ @session = session
87
+ end
88
+
89
+ def post(expected_entry)
90
+ data = { entries: [expected_entry] }
91
+ resp = @session.post2(@url.path, data.to_json,
92
+ { "Content-Type" => "application/json", "Accept" => "application/json" })
93
+
94
+ if resp.code != "200"
95
+ raise Error::FailedRekorLookup,
96
+ "#{resp.code} #{resp.message.inspect}\n#{JSON.pretty_generate(data)}\n#{resp.body}"
97
+ end
98
+
99
+ results = JSON.parse(resp.body)
100
+
101
+ results.map do |result|
102
+ Entries.decode_transparency_log_entry(result)
103
+ end.min_by(&:integrated_time)
104
+ end
105
+ end
106
+
107
+ def self.decode_transparency_log_entry(response)
108
+ raise ArgumentError, "response must be a Hash" unless response.is_a?(Hash)
109
+ raise ArgumentError, "Received multiple entries in response" if response.size != 1
110
+
111
+ _, result = response.first
112
+ entry = V1::TransparencyLogEntry.new
113
+ entry.canonicalized_body = Internal::Util.base64_decode(result.fetch("body"))
114
+ entry.integrated_time = result.fetch("integratedTime")
115
+ entry.log_id = Common::V1::LogId.new
116
+ entry.log_id.key_id = Internal::Util.hex_decode(result.fetch("logID"))
117
+ entry.log_index = result.fetch("logIndex")
118
+ if (set = result.dig("verification", "signedEntryTimestamp"))
119
+ entry.inclusion_promise = V1::InclusionPromise.new
120
+ entry.inclusion_promise.signed_entry_timestamp = Internal::Util.base64_decode(set)
121
+ end
122
+ if (inclusion_proof = result.dig("verification", "inclusionProof"))
123
+ entry.inclusion_proof = V1::InclusionProof.new
124
+ entry.inclusion_proof.checkpoint = V1::Checkpoint.new
125
+ entry.inclusion_proof.checkpoint.envelope = inclusion_proof.fetch("checkpoint")
126
+ entry.inclusion_proof.hashes = inclusion_proof.fetch("hashes").map { |h| Internal::Util.hex_decode(h) }
127
+ entry.inclusion_proof.log_index = inclusion_proof.fetch("logIndex")
128
+ entry.inclusion_proof.root_hash = Internal::Util.hex_decode(inclusion_proof.fetch("rootHash"))
129
+ entry.inclusion_proof.tree_size = inclusion_proof.fetch("treeSize")
130
+ end
131
+
132
+ entry
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,280 @@
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 "internal/util"
18
+ require_relative "internal/x509"
19
+ require_relative "models"
20
+ require_relative "oidc"
21
+ require_relative "policy"
22
+ require_relative "verifier"
23
+
24
+ module Sigstore
25
+ class Signer
26
+ include Loggable
27
+
28
+ def initialize(jwt:, trusted_root:)
29
+ @identity_token = OIDC::IdentityToken.new(jwt)
30
+ @trusted_root = trusted_root
31
+
32
+ @verifier = Verifier.for_trust_root(trust_root: @trusted_root)
33
+ end
34
+
35
+ def sign(payload)
36
+ # 2) generate a keypair
37
+ keypair = generate_keypair
38
+ # 3) generate a CreateSigningCertificateRequest
39
+ csr = generate_csr(keypair)
40
+ # 4) get a cert chain from fulcio
41
+ leaf = fetch_cert(csr)
42
+ # 5) verify returned cert chain
43
+ verify_chain(leaf)
44
+ # 6) sign the payload
45
+ signature = sign_payload(payload, keypair)
46
+ # 7) send hash of signature to timestamping service
47
+ timestamp_verification_data = submit_signature_hash_to_timstamping_service(signature)
48
+ # 8) submit signed metadata to transparency service
49
+ hashed_input = Common::V1::HashOutput.new
50
+ hashed_input.algorithm = Common::V1::HashAlgorithm::SHA2_256
51
+ hashed_input.digest = OpenSSL::Digest("SHA256").digest(payload)
52
+ tlog_entry = submit_signed_metadata_to_transparency_service(signature, leaf, hashed_input)
53
+ # 9) perform verification
54
+
55
+ bundle = collect_bundle(leaf, [tlog_entry], timestamp_verification_data, hashed_input, signature)
56
+ verify(payload, bundle)
57
+
58
+ bundle
59
+ end
60
+
61
+ private
62
+
63
+ def generate_keypair
64
+ # maybe allow configuring?
65
+ key = OpenSSL::PKey::EC.generate("prime256v1")
66
+ logger.debug { "Generated keypair #{key}" }
67
+ key
68
+ end
69
+
70
+ def generate_csr(keypair)
71
+ csr = OpenSSL::X509::Request.new
72
+
73
+ # The subject is unused, but must be set to avoid an error on JRuby
74
+ csr.subject = OpenSSL::X509::Name.new
75
+ csr.public_key = keypair
76
+
77
+ # The subject in the CertificationRequestInfo is an X.501 RelativeDistinguishedName.
78
+ # The value of the RelativeDistinguishedName SHOULD be the subject of the authentication token;
79
+ # its type MUST be the type identified in the Fulcio instance’s public configuration.
80
+ # NOTE: the subject of the CSR is unused
81
+
82
+ extension = OpenSSL::X509::ExtensionFactory.new.create_extension(
83
+ "basicConstraints",
84
+ "CA:FALSE",
85
+ true # critical
86
+ )
87
+ csr.add_attribute OpenSSL::X509::Attribute.new(
88
+ "extReq",
89
+ OpenSSL::ASN1::Set.new(
90
+ [OpenSSL::ASN1::Sequence.new([extension])]
91
+ )
92
+ )
93
+
94
+ csr.sign keypair, OpenSSL::Digest.new("SHA256")
95
+
96
+ logger.debug { "Generated CSR" }
97
+
98
+ {
99
+ credentials: {
100
+ oidc_identity_token: @identity_token.raw_token
101
+ },
102
+ certificate_signing_request: Internal::Util.base64_encode(csr.to_pem)
103
+ }
104
+ end
105
+
106
+ def fetch_cert(csr)
107
+ uri = URI.parse @trusted_root.certificate_authority_for_signing.uri
108
+ uri = URI.join(uri, "api/v2/signingCert")
109
+ resp = Net::HTTP.post(
110
+ uri,
111
+ JSON.dump(csr),
112
+ { "Content-Type" => "application/json" }
113
+ )
114
+
115
+ unless resp.code == "200"
116
+ raise Error::Signing,
117
+ "#{resp.code} #{resp.message}\n\n#{resp.body}"
118
+ end
119
+
120
+ resp_body = JSON.parse(resp.body)
121
+
122
+ unless resp_body.key?("signedCertificateEmbeddedSct")
123
+ raise Error::Signing, "missing signedCertificateEmbeddedSct in response from fulcio"
124
+ end
125
+
126
+ cert = resp_body.fetch("signedCertificateEmbeddedSct").fetch("chain")
127
+ .fetch("certificates").first.then { |pem| Internal::X509::Certificate.read(pem) }
128
+ logger.debug { "Fetched cert from fulcio" }
129
+ cert
130
+ end
131
+
132
+ def verify_chain(leaf)
133
+ # Perform certification path validation (RFC 5280 §6) of the returned certificate chain with the pre-distributed
134
+ # Fulcio root certificate(s) as a trust anchor.
135
+
136
+ x509_store = OpenSSL::X509::Store.new
137
+ expected_chain = @trusted_root.fulcio_cert_chain
138
+
139
+ x509_store.add_cert expected_chain.last.openssl
140
+ unless x509_store.verify(leaf.openssl, expected_chain[..-2].map(&:openssl))
141
+ raise Error::Signing, "returned certificate does not validate: #{x509_store.error_string}"
142
+ end
143
+
144
+ chain = x509_store.chain
145
+ chain.shift # remove the leaf cert
146
+ chain.map! { |cert| Internal::X509::Certificate.new(cert) }
147
+
148
+ logger.debug { "verified chain" }
149
+
150
+ # Extract a SignedCertificateTimestamp, which may be embedded as an X.509 extension in the leaf certificate or
151
+ # attached separately in the SigningCertificate returned from the Identity Service.
152
+ # Verify this SignedCertificateTimestamp as in RFC 9162 §8.1.3, using the root certificate from
153
+ # the Certificate Transparency Log.
154
+ if (result = @verifier.verify_scts(leaf, chain)) && !result.verified?
155
+ raise Error::Signing, "Failed to verify SCTs: #{result.reason}"
156
+ end
157
+
158
+ # Check that the leaf certificate contains the subject from the certificate signing request and encodes the
159
+ # appropriate AuthenticationServiceIdentifier in an extension with OID 1.3.6.1.4.1.57264.1.8.
160
+
161
+ fulcio_issuer = leaf.extension(Internal::X509::Extension::FulcioIssuer)
162
+ unless fulcio_issuer && fulcio_issuer.issuer == @identity_token.issuer
163
+ raise Error::Signing, "certificate does not contain expected Fulcio issuer"
164
+ end
165
+
166
+ unless leaf.subject.to_a.empty?
167
+ raise Error::Signing,
168
+ "certificate contains unexpected subject #{leaf.subject.to_a}"
169
+ end
170
+
171
+ general_names = leaf.extension(Internal::X509::Extension::SubjectAlternativeName).general_names
172
+ expected_san = [@identity_token.identity]
173
+ if general_names.map(&:last) != expected_san
174
+ raise Error::Signing,
175
+ "certificate does not contain expected SAN #{expected_san}, got #{general_names}"
176
+ end
177
+
178
+ [leaf, x509_store.chain]
179
+ end
180
+
181
+ def sign_payload(payload, key)
182
+ # The Signer MAY pre-hash the payload using a hash algorithm from the registry (Spec: Sigstore Registries) for
183
+ # compatibility with some signing metadata formats (see §Submission of Signing Metadata to Transparency Service).
184
+ key.sign("SHA256", payload)
185
+ end
186
+
187
+ # TODO: implement
188
+ def submit_signature_hash_to_timstamping_service(_signature)
189
+ # The Signer sends a hash of the signature as the messageImprint in a TimeStampReq to the Timestamping Service and
190
+ # receives a TimeStampResp including a `TimeStampToken`.
191
+ # The signer MUST verify the TimeStampToken against the payload and Timestamping Service root certificate.
192
+
193
+ nil
194
+ end
195
+
196
+ def build_proposed_hashed_rekord_entry(signature, cert, hashed_input)
197
+ algorithm = case hashed_input.algorithm
198
+ when Common::V1::HashAlgorithm::SHA2_256 then "sha256"
199
+ when Common::V1::HashAlgorithm::SHA2_384 then "sha384"
200
+ when Common::V1::HashAlgorithm::SHA2_512 then "sha512"
201
+ else
202
+ raise ArgumentError,
203
+ "unsupported hash algorithm: #{hashed_input.algorithm.inspect}"
204
+ end
205
+ {
206
+ "spec" => {
207
+ "signature" => {
208
+ "content" => Internal::Util.base64_encode(signature),
209
+ "publicKey" => {
210
+ "content" => Internal::Util.base64_encode(cert.to_pem)
211
+ }
212
+ },
213
+ "data" => {
214
+ "hash" => {
215
+ "algorithm" => algorithm,
216
+ "value" => Internal::Util.hex_encode(hashed_input.digest)
217
+ }
218
+ }
219
+ },
220
+ "kind" => "hashedrekord",
221
+ "apiVersion" => "0.0.1"
222
+ }
223
+ end
224
+
225
+ def submit_signed_metadata_to_transparency_service(signature, cert, hashed_input)
226
+ # The Signer chooses a format for signing metadata; this format MUST be in the supportedMetadataFormats in the
227
+ # Transparency Service configuration. The Signer prepares signing metadata containing at a minimum:
228
+ # * The signature.
229
+ # * The payload (possibly pre-hashed; if so, the entry also includes the identifier of the hash algorithm).
230
+ # * Verification material (signing certificate or verification key).
231
+ # * If the verification material is a certificate, the client SHOULD upload only the signing certificate and
232
+ # SHOULD NOT upload the CA certificate chain.
233
+ #
234
+ # The signing metadata might contain additional, application-specific metadata according to the format used.
235
+ # The Signer then canonically encodes the metadata (according to the chosen format).
236
+
237
+ # TODO: allow configuring the entry kind?
238
+ proposed_entry = build_proposed_hashed_rekord_entry(signature, cert, hashed_input)
239
+
240
+ ctlog = @trusted_root.tlog_for_signing
241
+ logger.info { "Submitting to #{ctlog.base_url}" }
242
+
243
+ # The signer MUST verify the log entry as in Spec: Transparency Service.
244
+ @verifier.rekor_client.log.entries.post(proposed_entry)
245
+ end
246
+
247
+ def verify(artifact, bundle)
248
+ verification_input = Verification::V1::Input.new
249
+ verification_input.bundle = bundle
250
+ verification_input.artifact = Verification::V1::Artifact.new
251
+ verification_input.artifact.artifact = artifact
252
+
253
+ result = @verifier.verify(
254
+ input: VerificationInput.new(verification_input),
255
+ policy: expected_identity,
256
+ offline: false
257
+ )
258
+ raise Error::Signing, "Failed to verify: #{result.reason}" unless result.verified?
259
+ end
260
+
261
+ def expected_identity
262
+ Policy::Identity.new(identity: @identity_token.identity, issuer: @identity_token.issuer)
263
+ end
264
+
265
+ def collect_bundle(leaf_certificate, tlog_entries, timestamp_verification_data, hashed_input, signature)
266
+ bundle = Bundle::V1::Bundle.new
267
+ bundle.media_type = BundleType::BUNDLE_0_3.media_type
268
+ bundle.verification_material = Bundle::V1::VerificationMaterial.new
269
+ bundle.verification_material.certificate = Common::V1::X509Certificate.new
270
+ bundle.verification_material.certificate.raw_bytes = leaf_certificate.to_pem
271
+ bundle.verification_material.tlog_entries = tlog_entries
272
+ bundle.verification_material.timestamp_verification_data = timestamp_verification_data
273
+ bundle.message_signature = Sigstore::Common::V1::MessageSignature.new.tap do |ms|
274
+ ms.message_digest = hashed_input
275
+ ms.signature = signature
276
+ end
277
+ bundle
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,116 @@
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 "delegate"
18
+ require "openssl"
19
+
20
+ require "protobug_sigstore_protos"
21
+
22
+ require_relative "tuf"
23
+
24
+ module Sigstore
25
+ REGISTRY = Protobug::Registry.new do |registry|
26
+ Sigstore::TrustRoot::V1.register_sigstore_trustroot_protos(registry)
27
+ Sigstore::Bundle::V1.register_sigstore_bundle_protos(registry)
28
+ end
29
+ class TrustedRoot < DelegateClass(Sigstore::TrustRoot::V1::TrustedRoot)
30
+ def self.production(offline: false)
31
+ from_tuf(TUF::DEFAULT_TUF_URL, offline)
32
+ end
33
+
34
+ def self.staging(offline: false)
35
+ from_tuf(TUF::STAGING_TUF_URL, offline)
36
+ end
37
+
38
+ def self.from_tuf(url, offline)
39
+ path = TUF::TrustUpdater.new(url, offline).tap { _1.refresh unless offline }.trusted_root_path
40
+ from_file(path)
41
+ end
42
+
43
+ def self.from_file(path)
44
+ contents = Gem.read_binary(path)
45
+ new Sigstore::TrustRoot::V1::TrustedRoot.decode_json(contents, registry: REGISTRY)
46
+ end
47
+
48
+ def rekor_keys
49
+ keys = tlog_keys(tlogs).to_a
50
+ raise Error::InvalidBundle, "Did not find one Rekor key" if keys.size != 1
51
+
52
+ keys
53
+ end
54
+
55
+ def ctfe_keys
56
+ keys = tlog_keys(ctlogs).to_a
57
+ raise Error::InvalidBundle, "Did not find any CTFE keys" if keys.empty?
58
+
59
+ keys
60
+ end
61
+
62
+ def fulcio_cert_chain
63
+ certs = ca_keys(certificate_authorities, allow_expired: true).flat_map do |raw_bytes|
64
+ Internal::X509::Certificate.read(raw_bytes)
65
+ end
66
+ raise Error::InvalidBundle, "Fulcio certificates not found in trusted root" if certs.empty?
67
+
68
+ certs
69
+ end
70
+
71
+ def tlog_for_signing
72
+ tlogs.find do |ctlog|
73
+ timerange_valid?(ctlog.public_key.valid_for, allow_expired: false)
74
+ end
75
+ end
76
+
77
+ def certificate_authority_for_signing
78
+ certificate_authorities.find do |ca|
79
+ timerange_valid?(ca.valid_for, allow_expired: false)
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def tlog_keys(tlogs)
86
+ return enum_for(__method__, tlogs) unless block_given?
87
+
88
+ tlogs.each do |transparency_log_instance|
89
+ key = transparency_log_instance.public_key
90
+ yield Internal::Key.from_key_details(key.key_details, key.raw_bytes)
91
+ end
92
+ end
93
+
94
+ def ca_keys(certificate_authorities, allow_expired:)
95
+ return enum_for(__method__, certificate_authorities, allow_expired:) unless block_given?
96
+
97
+ certificate_authorities.each do |ca|
98
+ next unless timerange_valid?(ca.valid_for, allow_expired:)
99
+
100
+ ca.cert_chain.certificates.each do |cert|
101
+ yield cert.raw_bytes
102
+ end
103
+ end
104
+ end
105
+
106
+ def timerange_valid?(period, allow_expired:)
107
+ now = Time.now.utc
108
+ return true unless period
109
+ return false if now < period.start.to_time
110
+ return true if allow_expired
111
+ return false if period.end && now > period.end.to_time
112
+
113
+ true
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,46 @@
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
+ module TUF
19
+ class UpdaterConfig
20
+ attr_reader :max_root_rotations, :max_delegations, :root_max_length, :timestamp_max_length, :snapshot_max_length,
21
+ :targets_max_length, :prefix_targets_with_hash, :envelope_type, :app_user_agent
22
+
23
+ def initialize(
24
+ max_root_rotations: 32,
25
+ max_delegations: 32,
26
+ root_max_length: 512_000, # bytes
27
+ timestamp_max_length: 16_384, # bytes
28
+ snapshot_max_length: 2_000_000, # bytes
29
+ targets_max_length: 5_000_000, # bytes
30
+ prefix_targets_with_hash: true,
31
+ envelope_type: :metadata,
32
+ app_user_agent: nil
33
+ )
34
+ @max_root_rotations = max_root_rotations
35
+ @max_delegations = max_delegations
36
+ @root_max_length = root_max_length
37
+ @timestamp_max_length = timestamp_max_length
38
+ @snapshot_max_length = snapshot_max_length
39
+ @targets_max_length = targets_max_length
40
+ @prefix_targets_with_hash = prefix_targets_with_hash
41
+ @envelope_type = envelope_type
42
+ @app_user_agent = app_user_agent
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,49 @@
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 "../error"
18
+
19
+ module Sigstore::TUF
20
+ class Error < ::Sigstore::Error
21
+ # An error with a repository's state, such as a missing file.
22
+ class RepositoryError < Error; end
23
+
24
+ class LengthOrHashMismatch < RepositoryError; end
25
+ class ExpiredMetadata < RepositoryError; end
26
+ class BadVersionNumber < RepositoryError; end
27
+ class EqualVersionNumber < BadVersionNumber; end
28
+ class TooFewSignatures < RepositoryError; end
29
+
30
+ class BadUpdateOrder < Error; end
31
+ class InvalidData < Error; end
32
+ class DuplicateKeys < Error; end
33
+
34
+ # An error occurred while attempting to download a file.
35
+ class DownloadError < Error; end
36
+
37
+ class Fetch < Error; end
38
+ class RemoteConnection < Fetch; end
39
+
40
+ class UnsuccessfulResponse < Fetch
41
+ attr_reader :response
42
+
43
+ def initialize(message, response)
44
+ super(message)
45
+ @response = response
46
+ end
47
+ end
48
+ end
49
+ end