sigstore 0.2.1 → 0.2.3
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 +4 -4
- data/README.md +1 -1
- data/lib/sigstore/internal/key.rb +5 -5
- data/lib/sigstore/internal/merkle.rb +2 -2
- data/lib/sigstore/internal/x509.rb +86 -33
- data/lib/sigstore/models.rb +1 -1
- data/lib/sigstore/policy.rb +10 -2
- data/lib/sigstore/rekor/checkpoint.rb +4 -4
- data/lib/sigstore/rekor/client.rb +13 -5
- data/lib/sigstore/signer.rb +15 -11
- data/lib/sigstore/trusted_root.rb +8 -9
- data/lib/sigstore/tuf/updater.rb +4 -2
- data/lib/sigstore/tuf.rb +3 -2
- data/lib/sigstore/verifier.rb +41 -41
- data/lib/sigstore/version.rb +2 -1
- metadata +22 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a9a3ef5da7b2fc632b31f48e168423e1c3d12dd601621937d1dc0ddf27be250
|
|
4
|
+
data.tar.gz: 9daf366761da11b908f6670644976bbb12ac11c6d7037d36c754083a56be9995
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7f4ccdeef1781aa702cc3bde7fa2006b0d58d6e89e0eb5ef17cf53db6570bbd50a7d65ba2c33638f09fcd9439fb565566429926497e3aaa9e7e9cee16631b86e
|
|
7
|
+
data.tar.gz: 361d3082ca855a0ed086ffc15cb63c011be86c6bde88afd9c0c88cc916768b8370088e6a488af4316f7e2eec94fee81f72f1f117ff349e44af7c93aad774df0e
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sigstore
|
|
2
2
|
|
|
3
|
-
This is a pure Ruby implementation of the `sigstore verify` command from the [sigstore/cosign](https://sigstore.dev/projects/cosign) project. It is intended to be used as a library in other Ruby projects
|
|
3
|
+
This is a pure Ruby implementation of the `sigstore verify` command from the [sigstore/cosign](https://sigstore.dev/projects/cosign) project. It is intended to be used as a library in other Ruby projects or directly through a new `gem` subcommand. The project also contains a TUF client implementation, given TUF is a part of the sigstore verification flow.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
|
|
@@ -30,7 +30,11 @@ module Sigstore
|
|
|
30
30
|
key_type = "rsa"
|
|
31
31
|
key_schema = "rsa-pkcs1v15-sha256"
|
|
32
32
|
else
|
|
33
|
-
|
|
33
|
+
# Skip unrecognized key types instead of raising an error.
|
|
34
|
+
# This allows the library to work with newer trusted roots that include
|
|
35
|
+
# key types we don't yet support (e.g., PKIX_ED25519 for Rekor v2).
|
|
36
|
+
logger.warn { "Skipping unrecognized key type: #{key_details}" }
|
|
37
|
+
return nil
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
read(key_type, key_schema, key_bytes, key_id: OpenSSL::Digest::SHA256.hexdigest(key_bytes))
|
|
@@ -49,10 +53,6 @@ module Sigstore
|
|
|
49
53
|
RSA.new(key_type, schema, pkey, key_id:)
|
|
50
54
|
else
|
|
51
55
|
raise ArgumentError, "Unsupported key type #{key_type}"
|
|
52
|
-
end.tap do |key|
|
|
53
|
-
if RUBY_ENGINE == "jruby" && key.to_pem != key_bytes && key.to_der != key_bytes
|
|
54
|
-
raise Error::UnsupportedPlatform, "Key mismatch: #{key.to_pem.inspect} != #{key_bytes.inspect}"
|
|
55
|
-
end
|
|
56
56
|
end
|
|
57
57
|
rescue OpenSSL::PKey::PKeyError => e
|
|
58
58
|
raise OpenSSL::PKey::PKeyError, "Invalid key: #{e} for #{key_type} #{schema} #{key_id}"
|
|
@@ -69,7 +69,7 @@ module Sigstore
|
|
|
69
69
|
|
|
70
70
|
intermediate_result = chain_inner(
|
|
71
71
|
leaf_hash,
|
|
72
|
-
|
|
72
|
+
proof[...inner] || raise(MissingHashError, "missing left hashes"),
|
|
73
73
|
log_index
|
|
74
74
|
)
|
|
75
75
|
|
|
@@ -93,7 +93,7 @@ module Sigstore
|
|
|
93
93
|
|
|
94
94
|
def self.chain_inner(seed, hashes, log_index)
|
|
95
95
|
hashes.each_with_index do |hash, i|
|
|
96
|
-
seed = if (
|
|
96
|
+
seed = if (log_index >> i).nobits?(1)
|
|
97
97
|
hash_children(seed, hash)
|
|
98
98
|
else
|
|
99
99
|
hash_children(hash, seed)
|
|
@@ -20,6 +20,74 @@ require "openssl"
|
|
|
20
20
|
module Sigstore
|
|
21
21
|
module Internal
|
|
22
22
|
module X509
|
|
23
|
+
if RUBY_ENGINE == "jruby"
|
|
24
|
+
unless JOpenSSL::VERSION >= "0.15.3"
|
|
25
|
+
raise Error::UnsupportedPlatform, "JRuby support requires jruby-openssl >= 0.15.3, is #{JOpenSSL::VERSION}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.validate_chain(trust_roots, leaf, time)
|
|
29
|
+
cert_factory = java.security.cert.CertificateFactory.getInstance("X.509")
|
|
30
|
+
cert_factory.generateCertificate(java.io.ByteArrayInputStream.new(leaf.to_der.to_java_bytes))
|
|
31
|
+
target = leaf.openssl.to_java
|
|
32
|
+
|
|
33
|
+
trust_anchors = Set.new
|
|
34
|
+
intermediate_certs = []
|
|
35
|
+
trust_roots.each do |chain|
|
|
36
|
+
root = chain.last
|
|
37
|
+
|
|
38
|
+
trust_anchors << java.security.cert.TrustAnchor.new(root.openssl.to_java, nil)
|
|
39
|
+
chain[..-2].each do |cert|
|
|
40
|
+
intermediate_certs << cert.openssl.to_java
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
cert_store_parameters = java.security.cert.CollectionCertStoreParameters.new(intermediate_certs)
|
|
45
|
+
cert_store = java.security.cert.CertStore.getInstance("Collection", cert_store_parameters)
|
|
46
|
+
|
|
47
|
+
cert_selector = java.security.cert.X509CertSelector.new
|
|
48
|
+
cert_selector.setCertificate(target)
|
|
49
|
+
|
|
50
|
+
pkix_builder_parameters = java.security.cert.PKIXBuilderParameters.new(trust_anchors, cert_selector)
|
|
51
|
+
pkix_builder_parameters.setDate(time) if time
|
|
52
|
+
pkix_builder_parameters.setRevocationEnabled(false)
|
|
53
|
+
pkix_builder_parameters.addCertStore(cert_store)
|
|
54
|
+
|
|
55
|
+
cert_path_builder = java.security.cert.CertPathBuilder.getInstance("PKIX")
|
|
56
|
+
cert_path_result = cert_path_builder.build(pkix_builder_parameters)
|
|
57
|
+
chain = cert_path_result.cert_path.getCertificates.map do |cert|
|
|
58
|
+
der = String.from_java_bytes(cert.getEncoded).b
|
|
59
|
+
Certificate.read(der)
|
|
60
|
+
end
|
|
61
|
+
chain.shift # remove the cert itself
|
|
62
|
+
chain << Certificate.read(
|
|
63
|
+
String.from_java_bytes(cert_path_result.get_trust_anchor.getTrustedCert.getEncoded).b
|
|
64
|
+
)
|
|
65
|
+
[chain, nil]
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
def self.validate_chain(trust_roots, leaf, time)
|
|
69
|
+
store = OpenSSL::X509::Store.new
|
|
70
|
+
intermediate_certs = []
|
|
71
|
+
trust_roots.each do |chain|
|
|
72
|
+
store.add_cert(chain.last.openssl)
|
|
73
|
+
chain[..-2].each do |cert|
|
|
74
|
+
intermediate_certs << cert.openssl
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
store_ctx = OpenSSL::X509::StoreContext.new(store, leaf.openssl, intermediate_certs)
|
|
78
|
+
store_ctx.time = time if time
|
|
79
|
+
unless store_ctx.verify
|
|
80
|
+
return nil, VerificationFailure.new(
|
|
81
|
+
"failed to validate certificate from fulcio cert chain: #{store_ctx.error_string}"
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
chain = store_ctx.chain || raise(Error::InvalidCertificate, "no valid cert chain found")
|
|
86
|
+
chain.shift # remove the cert itself
|
|
87
|
+
[chain.map! { Certificate.new(_1) }, nil]
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
23
91
|
class Certificate
|
|
24
92
|
extend Forwardable
|
|
25
93
|
|
|
@@ -149,7 +217,7 @@ module Sigstore
|
|
|
149
217
|
extended_key_usage = extension(Extension::ExtendedKeyUsage)
|
|
150
218
|
return false unless extended_key_usage
|
|
151
219
|
|
|
152
|
-
extended_key_usage.
|
|
220
|
+
extended_key_usage.precert?
|
|
153
221
|
end
|
|
154
222
|
end
|
|
155
223
|
|
|
@@ -236,7 +304,7 @@ module Sigstore
|
|
|
236
304
|
|
|
237
305
|
def parse_value(value)
|
|
238
306
|
unless value.is_a?(OpenSSL::ASN1::Sequence)
|
|
239
|
-
|
|
307
|
+
raise ArgumentError,
|
|
240
308
|
"Invalid extended key usage: #{value.inspect}"
|
|
241
309
|
end
|
|
242
310
|
|
|
@@ -248,10 +316,15 @@ module Sigstore
|
|
|
248
316
|
end
|
|
249
317
|
|
|
250
318
|
CODE_SIGNING = OpenSSL::ASN1::ObjectId.new("1.3.6.1.5.5.7.3.3")
|
|
319
|
+
PRECERT_PURPOSE = OpenSSL::ASN1::ObjectId.new("1.3.6.1.4.1.11129.2.4.4")
|
|
251
320
|
|
|
252
321
|
def code_signing?
|
|
253
322
|
purposes.any? { |purpose| purpose.oid == CODE_SIGNING.oid }
|
|
254
323
|
end
|
|
324
|
+
|
|
325
|
+
def precert?
|
|
326
|
+
purposes.any? { |purpose| purpose.oid == PRECERT_PURPOSE.oid }
|
|
327
|
+
end
|
|
255
328
|
end
|
|
256
329
|
|
|
257
330
|
class BasicConstraints < Extension
|
|
@@ -309,11 +382,15 @@ module Sigstore
|
|
|
309
382
|
@general_names = value.map do |general_name|
|
|
310
383
|
tag = general_name.tag
|
|
311
384
|
|
|
385
|
+
value = general_name.value
|
|
386
|
+
value = value.first if value.is_a?(Array) && value.size == 1
|
|
387
|
+
value = value.value if value.is_a?(OpenSSL::ASN1::OctetString)
|
|
388
|
+
|
|
312
389
|
case tag
|
|
313
390
|
when 1
|
|
314
|
-
[:otherName,
|
|
391
|
+
[:otherName, value]
|
|
315
392
|
when 6
|
|
316
|
-
[:uniformResourceIdentifier,
|
|
393
|
+
[:uniformResourceIdentifier, value]
|
|
317
394
|
else
|
|
318
395
|
raise Error::Unimplemented,
|
|
319
396
|
"Unhandled general name tag: #{tag}"
|
|
@@ -361,7 +438,7 @@ module Sigstore
|
|
|
361
438
|
signature_algorithm
|
|
362
439
|
entry_type
|
|
363
440
|
signature]
|
|
364
|
-
Timestamp = defined?(Data.define) ? Data.define(*args) : Struct.new(*args
|
|
441
|
+
Timestamp = defined?(Data.define) ? Data.define(*args) : Struct.new(*args) # rubocop:disable Naming/ConstantName
|
|
365
442
|
|
|
366
443
|
HASHES = {
|
|
367
444
|
0 => "none",
|
|
@@ -384,46 +461,22 @@ module Sigstore
|
|
|
384
461
|
|
|
385
462
|
private
|
|
386
463
|
|
|
387
|
-
if RUBY_VERSION >= "3.1"
|
|
388
|
-
def unpack_at(string, format, offset:)
|
|
389
|
-
string.unpack(format, offset:)
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
def unpack1_at(string, format, offset:)
|
|
393
|
-
string.unpack1(format, offset:)
|
|
394
|
-
end
|
|
395
|
-
else
|
|
396
|
-
def unpack_at(string, format, offset:)
|
|
397
|
-
string[offset..].unpack(format)
|
|
398
|
-
end
|
|
399
|
-
|
|
400
|
-
def unpack1_at(string, format, offset:)
|
|
401
|
-
string[offset..].unpack1(format)
|
|
402
|
-
end
|
|
403
|
-
end
|
|
404
|
-
|
|
405
464
|
# https://letsencrypt.org/2018/04/04/sct-encoding.html
|
|
406
465
|
def unpack_sct_list(string)
|
|
407
466
|
offset = 0
|
|
408
467
|
len = string.bytesize
|
|
409
468
|
list = []
|
|
410
469
|
while offset < len
|
|
411
|
-
sct_version, sct_log_id, sct_timestamp, sct_extensions_len =
|
|
470
|
+
sct_version, sct_log_id, sct_timestamp, sct_extensions_len = string.unpack("Ca32Q>n", offset:)
|
|
412
471
|
offset += 1 + 32 + 8 + 2
|
|
413
472
|
raise Error::Unimplemented, "expect sct version to be 0, got #{sct_version}" unless sct_version.zero?
|
|
414
473
|
|
|
415
|
-
sct_extensions_bytes =
|
|
474
|
+
sct_extensions_bytes = string.unpack1("a#{sct_extensions_len}", offset:).b
|
|
416
475
|
offset += sct_extensions_len
|
|
417
476
|
|
|
418
|
-
|
|
419
|
-
raise Error::Unimplemented,
|
|
420
|
-
"sct_extensions_len=#{sct_extensions_len} not supported"
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
sct_signature_alg_hash, sct_signature_alg_sign, sct_signature_len = unpack_at(string, "CCn",
|
|
424
|
-
offset:)
|
|
477
|
+
sct_signature_alg_hash, sct_signature_alg_sign, sct_signature_len = string.unpack("CCn", offset:)
|
|
425
478
|
offset += 1 + 1 + 2
|
|
426
|
-
sct_signature_bytes =
|
|
479
|
+
sct_signature_bytes = string.unpack1("a#{sct_signature_len}", offset:).b
|
|
427
480
|
offset += sct_signature_len
|
|
428
481
|
list << Timestamp.new(
|
|
429
482
|
version: sct_version,
|
data/lib/sigstore/models.rb
CHANGED
|
@@ -19,7 +19,7 @@ require_relative "error"
|
|
|
19
19
|
require_relative "trusted_root"
|
|
20
20
|
|
|
21
21
|
module Sigstore
|
|
22
|
-
VerificationResult = Struct.new(:success
|
|
22
|
+
VerificationResult = Struct.new(:success) do
|
|
23
23
|
# @implements VerificationResult
|
|
24
24
|
|
|
25
25
|
alias_method :verified?, :success
|
data/lib/sigstore/policy.rb
CHANGED
|
@@ -38,8 +38,16 @@ module Sigstore
|
|
|
38
38
|
VerificationSuccess.new
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
ext
|
|
41
|
+
if RUBY_ENGINE == "jruby"
|
|
42
|
+
def ext_value(ext)
|
|
43
|
+
der = ext.to_der
|
|
44
|
+
seq = OpenSSL::ASN1.decode(der)
|
|
45
|
+
seq.value.last.value
|
|
46
|
+
end
|
|
47
|
+
else
|
|
48
|
+
def ext_value(ext)
|
|
49
|
+
ext.value
|
|
50
|
+
end
|
|
43
51
|
end
|
|
44
52
|
|
|
45
53
|
def oid
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
module Sigstore
|
|
18
18
|
module Rekor
|
|
19
19
|
module Checkpoint
|
|
20
|
-
Signature = Struct.new(:name, :sig_hash, :signature
|
|
20
|
+
Signature = Struct.new(:name, :sig_hash, :signature)
|
|
21
21
|
|
|
22
|
-
SignedCheckpoint = Struct.new(:signed_note, :checkpoint
|
|
22
|
+
SignedCheckpoint = Struct.new(:signed_note, :checkpoint) do
|
|
23
23
|
# @implements SignedCheckpoint
|
|
24
24
|
|
|
25
25
|
def self.from_text(text)
|
|
@@ -30,7 +30,7 @@ module Sigstore
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
-
SignedNote = Struct.new(:note, :signatures
|
|
33
|
+
SignedNote = Struct.new(:note, :signatures) do
|
|
34
34
|
# @implements SignedNote
|
|
35
35
|
|
|
36
36
|
def self.from_text(text)
|
|
@@ -77,7 +77,7 @@ module Sigstore
|
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
-
LogCheckpoint = Struct.new(:origin, :log_size, :log_hash, :other_content
|
|
80
|
+
LogCheckpoint = Struct.new(:origin, :log_size, :log_hash, :other_content) do
|
|
81
81
|
# @implements LogCheckpoint
|
|
82
82
|
|
|
83
83
|
def self.from_text(text)
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# limitations under the License.
|
|
16
16
|
|
|
17
17
|
require "net/http"
|
|
18
|
+
require_relative "../version"
|
|
18
19
|
|
|
19
20
|
module Sigstore
|
|
20
21
|
module Rekor
|
|
@@ -66,7 +67,8 @@ module Sigstore
|
|
|
66
67
|
|
|
67
68
|
def post(entry)
|
|
68
69
|
resp = @session.post2(@url.path.chomp("/"), entry.to_json,
|
|
69
|
-
{ "Content-Type" => "application/json", "Accept" => "application/json"
|
|
70
|
+
{ "Content-Type" => "application/json", "Accept" => "application/json",
|
|
71
|
+
"User-Agent" => Sigstore::USER_AGENT })
|
|
70
72
|
|
|
71
73
|
unless resp.code == "201"
|
|
72
74
|
raise Error::FailedRekorPost,
|
|
@@ -89,7 +91,8 @@ module Sigstore
|
|
|
89
91
|
def post(expected_entry)
|
|
90
92
|
data = { entries: [expected_entry] }
|
|
91
93
|
resp = @session.post2(@url.path, data.to_json,
|
|
92
|
-
{ "Content-Type" => "application/json", "Accept" => "application/json"
|
|
94
|
+
{ "Content-Type" => "application/json", "Accept" => "application/json",
|
|
95
|
+
"User-Agent" => Sigstore::USER_AGENT })
|
|
93
96
|
|
|
94
97
|
if resp.code != "200"
|
|
95
98
|
raise Error::FailedRekorLookup,
|
|
@@ -109,12 +112,17 @@ module Sigstore
|
|
|
109
112
|
raise ArgumentError, "Received multiple entries in response" if response.size != 1
|
|
110
113
|
|
|
111
114
|
_, result = response.first
|
|
115
|
+
canonicalized_body = Internal::Util.base64_decode(result.fetch("body"))
|
|
116
|
+
body = JSON.parse(canonicalized_body)
|
|
112
117
|
entry = V1::TransparencyLogEntry.new
|
|
113
|
-
entry.
|
|
114
|
-
entry.integrated_time = result.fetch("integratedTime")
|
|
118
|
+
entry.log_index = result.fetch("logIndex")
|
|
115
119
|
entry.log_id = Common::V1::LogId.new
|
|
116
120
|
entry.log_id.key_id = Internal::Util.hex_decode(result.fetch("logID"))
|
|
117
|
-
entry.
|
|
121
|
+
entry.kind_version = V1::KindVersion.new
|
|
122
|
+
entry.kind_version.kind = body.fetch("kind")
|
|
123
|
+
entry.kind_version.version = body.fetch("apiVersion")
|
|
124
|
+
entry.integrated_time = result.fetch("integratedTime")
|
|
125
|
+
entry.canonicalized_body = canonicalized_body
|
|
118
126
|
if (set = result.dig("verification", "signedEntryTimestamp"))
|
|
119
127
|
entry.inclusion_promise = V1::InclusionPromise.new
|
|
120
128
|
entry.inclusion_promise.signed_entry_timestamp = Internal::Util.base64_decode(set)
|
data/lib/sigstore/signer.rb
CHANGED
|
@@ -20,6 +20,7 @@ require_relative "models"
|
|
|
20
20
|
require_relative "oidc"
|
|
21
21
|
require_relative "policy"
|
|
22
22
|
require_relative "verifier"
|
|
23
|
+
require_relative "version"
|
|
23
24
|
|
|
24
25
|
module Sigstore
|
|
25
26
|
class Signer
|
|
@@ -109,7 +110,7 @@ module Sigstore
|
|
|
109
110
|
resp = Net::HTTP.post(
|
|
110
111
|
uri,
|
|
111
112
|
JSON.dump(csr),
|
|
112
|
-
{ "Content-Type" => "application/json" }
|
|
113
|
+
{ "Content-Type" => "application/json", "User-Agent" => Sigstore::USER_AGENT }
|
|
113
114
|
)
|
|
114
115
|
|
|
115
116
|
unless resp.code == "200"
|
|
@@ -133,17 +134,20 @@ module Sigstore
|
|
|
133
134
|
# Perform certification path validation (RFC 5280 §6) of the returned certificate chain with the pre-distributed
|
|
134
135
|
# Fulcio root certificate(s) as a trust anchor.
|
|
135
136
|
|
|
136
|
-
|
|
137
|
-
|
|
137
|
+
now = Time.now
|
|
138
|
+
if leaf.not_before > now
|
|
139
|
+
unless leaf.not_before - now < 60
|
|
140
|
+
raise Error::Signing, "leaf certificate not yet valid: #{leaf.not_before.inspect} vs #{now.inspect}"
|
|
141
|
+
end
|
|
138
142
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
143
|
+
logger.warn do
|
|
144
|
+
"leaf certificate not yet valid: #{leaf.not_before.inspect} vs #{now.inspect}, sleeping until valid"
|
|
145
|
+
end
|
|
146
|
+
sleep(leaf.not_before - now)
|
|
142
147
|
end
|
|
143
148
|
|
|
144
|
-
chain =
|
|
145
|
-
chain
|
|
146
|
-
chain.map! { |cert| Internal::X509::Certificate.new(cert) }
|
|
149
|
+
chain, err = Internal::X509.validate_chain(@trusted_root.fulcio_cert_chains, leaf, nil)
|
|
150
|
+
raise Error::Signing, "failed to validate returned certificate chain: #{err.reason}" if err
|
|
147
151
|
|
|
148
152
|
logger.debug { "verified chain" }
|
|
149
153
|
|
|
@@ -175,7 +179,7 @@ module Sigstore
|
|
|
175
179
|
"certificate does not contain expected SAN #{expected_san}, got #{general_names}"
|
|
176
180
|
end
|
|
177
181
|
|
|
178
|
-
[leaf,
|
|
182
|
+
[leaf, chain.unshift(leaf)]
|
|
179
183
|
end
|
|
180
184
|
|
|
181
185
|
def sign_payload(payload, key)
|
|
@@ -267,7 +271,7 @@ module Sigstore
|
|
|
267
271
|
bundle.media_type = BundleType::BUNDLE_0_3.media_type
|
|
268
272
|
bundle.verification_material = Bundle::V1::VerificationMaterial.new
|
|
269
273
|
bundle.verification_material.certificate = Common::V1::X509Certificate.new
|
|
270
|
-
bundle.verification_material.certificate.raw_bytes = leaf_certificate.
|
|
274
|
+
bundle.verification_material.certificate.raw_bytes = leaf_certificate.to_der
|
|
271
275
|
bundle.verification_material.tlog_entries = tlog_entries
|
|
272
276
|
bundle.verification_material.timestamp_verification_data = timestamp_verification_data
|
|
273
277
|
bundle.message_signature = Sigstore::Common::V1::MessageSignature.new.tap do |ms|
|
|
@@ -59,13 +59,13 @@ module Sigstore
|
|
|
59
59
|
keys
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
def
|
|
63
|
-
|
|
64
|
-
Internal::X509::Certificate.read(raw_bytes)
|
|
62
|
+
def fulcio_cert_chains
|
|
63
|
+
chains = ca_keys(certificate_authorities, allow_expired: true).map do |certs|
|
|
64
|
+
certs.map { |raw_bytes| Internal::X509::Certificate.read(raw_bytes) }
|
|
65
65
|
end
|
|
66
|
-
raise Error::InvalidBundle, "Fulcio certificates not found in trusted root" if
|
|
66
|
+
raise Error::InvalidBundle, "Fulcio certificates not found in trusted root" if chains.none?(&:any?)
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
chains
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def tlog_for_signing
|
|
@@ -87,7 +87,8 @@ module Sigstore
|
|
|
87
87
|
|
|
88
88
|
tlogs.each do |transparency_log_instance|
|
|
89
89
|
key = transparency_log_instance.public_key
|
|
90
|
-
|
|
90
|
+
parsed_key = Internal::Key.from_key_details(key.key_details, key.raw_bytes)
|
|
91
|
+
yield parsed_key if parsed_key
|
|
91
92
|
end
|
|
92
93
|
end
|
|
93
94
|
|
|
@@ -97,9 +98,7 @@ module Sigstore
|
|
|
97
98
|
certificate_authorities.each do |ca|
|
|
98
99
|
next unless timerange_valid?(ca.valid_for, allow_expired:)
|
|
99
100
|
|
|
100
|
-
ca.cert_chain.certificates.
|
|
101
|
-
yield cert.raw_bytes
|
|
102
|
-
end
|
|
101
|
+
yield ca.cert_chain.certificates.map(&:raw_bytes)
|
|
103
102
|
end
|
|
104
103
|
end
|
|
105
104
|
|
data/lib/sigstore/tuf/updater.rb
CHANGED
|
@@ -21,8 +21,6 @@ require_relative "snapshot"
|
|
|
21
21
|
require_relative "targets"
|
|
22
22
|
require_relative "timestamp"
|
|
23
23
|
|
|
24
|
-
require "set"
|
|
25
|
-
|
|
26
24
|
module Sigstore::TUF
|
|
27
25
|
class Updater
|
|
28
26
|
include Sigstore::Loggable
|
|
@@ -74,6 +72,10 @@ module Sigstore::TUF
|
|
|
74
72
|
target_base_url ||= @target_base_url
|
|
75
73
|
raise ArgumentError, "No target_base_url set" unless target_base_url
|
|
76
74
|
|
|
75
|
+
if (cached_target = find_cached_target(target_info, filepath))
|
|
76
|
+
return cached_target
|
|
77
|
+
end
|
|
78
|
+
|
|
77
79
|
filepath ||= generate_target_file_path(target_info)
|
|
78
80
|
|
|
79
81
|
target_filepath = target_info.path
|
data/lib/sigstore/tuf.rb
CHANGED
|
@@ -19,6 +19,7 @@ require "tempfile"
|
|
|
19
19
|
require "uri"
|
|
20
20
|
require "net/http"
|
|
21
21
|
require "rubygems/remote_fetcher"
|
|
22
|
+
require_relative "version"
|
|
22
23
|
|
|
23
24
|
module Sigstore
|
|
24
25
|
module TUF
|
|
@@ -133,8 +134,8 @@ module Sigstore
|
|
|
133
134
|
|
|
134
135
|
fetcher = Gem::RemoteFetcher.fetcher
|
|
135
136
|
begin
|
|
136
|
-
response = fetcher.request(uri, Net::HTTP::Get, nil) do
|
|
137
|
-
|
|
137
|
+
response = fetcher.request(uri, Net::HTTP::Get, nil) do |req|
|
|
138
|
+
req["User-Agent"] = Sigstore::USER_AGENT
|
|
138
139
|
end
|
|
139
140
|
response.uri = uri
|
|
140
141
|
case response
|
data/lib/sigstore/verifier.rb
CHANGED
|
@@ -28,9 +28,9 @@ module Sigstore
|
|
|
28
28
|
|
|
29
29
|
attr_reader :rekor_client
|
|
30
30
|
|
|
31
|
-
def initialize(rekor_client:,
|
|
31
|
+
def initialize(rekor_client:, fulcio_cert_chains:, timestamp_authorities:, rekor_keyring:, ct_keyring:)
|
|
32
32
|
@rekor_client = rekor_client
|
|
33
|
-
@
|
|
33
|
+
@fulcio_cert_chains = fulcio_cert_chains
|
|
34
34
|
@timestamp_authorities = timestamp_authorities
|
|
35
35
|
@rekor_keyring = rekor_keyring
|
|
36
36
|
@ct_keyring = ct_keyring
|
|
@@ -39,7 +39,7 @@ module Sigstore
|
|
|
39
39
|
def self.for_trust_root(trust_root:)
|
|
40
40
|
new(
|
|
41
41
|
rekor_client: Rekor::Client.new(url: trust_root.tlog_for_signing.base_url),
|
|
42
|
-
|
|
42
|
+
fulcio_cert_chains: trust_root.fulcio_cert_chains,
|
|
43
43
|
timestamp_authorities: trust_root.timestamp_authorities,
|
|
44
44
|
rekor_keyring: Internal::Keyring.new(keys: trust_root.rekor_keys),
|
|
45
45
|
ct_keyring: Internal::Keyring.new(keys: trust_root.ctfe_keys)
|
|
@@ -97,14 +97,6 @@ module Sigstore
|
|
|
97
97
|
|
|
98
98
|
timestamps << Time.at(entry.integrated_time).utc
|
|
99
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
100
|
# 3)
|
|
109
101
|
# The Verifier MUST perform certification path validation (RFC 5280 §6) of the certificate chain with the
|
|
110
102
|
# pre-distributed Fulcio root certificate(s) as a trust anchor, but with a fake “current time.”
|
|
@@ -112,19 +104,12 @@ module Sigstore
|
|
|
112
104
|
# timestamp from the Timestamping Service. If a timestamp from the Transparency Service is available, the Verifier
|
|
113
105
|
# MUST perform path validation using the timestamp from the Transparency Service. If both are available, the
|
|
114
106
|
# 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
107
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
)
|
|
123
|
-
end
|
|
108
|
+
chains = timestamps.map do |ts|
|
|
109
|
+
chain, err = Internal::X509.validate_chain(@fulcio_cert_chains, bundle.leaf_certificate, ts)
|
|
110
|
+
return err if err
|
|
124
111
|
|
|
125
|
-
chain
|
|
126
|
-
chain.shift # remove the cert itself
|
|
127
|
-
chain.map! { Internal::X509::Certificate.new(_1) }
|
|
112
|
+
chain
|
|
128
113
|
end
|
|
129
114
|
|
|
130
115
|
chains.uniq! { |chain| chain.map(&:to_der) }
|
|
@@ -188,7 +173,9 @@ module Sigstore
|
|
|
188
173
|
rescue JSON::ParserError
|
|
189
174
|
raise Error::InvalidBundle, "invalid JSON for in-toto statement in DSSE payload"
|
|
190
175
|
end
|
|
191
|
-
verify_in_toto(input, in_toto)
|
|
176
|
+
if (result = verify_in_toto(input, in_toto))
|
|
177
|
+
return result
|
|
178
|
+
end
|
|
192
179
|
else
|
|
193
180
|
raise Sigstore::Error::Unimplemented,
|
|
194
181
|
"unsupported DSSE payload type: #{bundle.dsse_envelope.payloadType.inspect}"
|
|
@@ -231,25 +218,27 @@ module Sigstore
|
|
|
231
218
|
end
|
|
232
219
|
|
|
233
220
|
def verify_in_toto(input, in_toto_payload)
|
|
234
|
-
type = in_toto_payload
|
|
221
|
+
type = in_toto_payload["_type"]
|
|
235
222
|
raise Error::InvalidBundle, "Expected in-toto statement, got #{type.inspect}" unless type == "https://in-toto.io/Statement/v1"
|
|
236
223
|
|
|
237
|
-
|
|
238
|
-
raise Error::InvalidBundle, "Expected in-toto statement with subject"
|
|
239
|
-
|
|
240
|
-
subject = subject.first
|
|
241
|
-
digest = subject.fetch("digest")
|
|
242
|
-
raise Error::InvalidBundle, "Expected in-toto statement with digest" if !digest || digest.empty?
|
|
224
|
+
subjects = in_toto_payload["subject"]
|
|
225
|
+
raise Error::InvalidBundle, "Expected in-toto statement with subject" if !subjects || subjects.empty?
|
|
243
226
|
|
|
227
|
+
expected_algorithm = Internal::Util.hash_algorithm_name(input.hashed_input.algorithm)
|
|
244
228
|
expected_hexdigest = Internal::Util.hex_encode(input.hashed_input.digest)
|
|
245
|
-
digest.each do |name, value|
|
|
246
|
-
next if expected_hexdigest == value
|
|
247
229
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
230
|
+
matched = subjects.map do |subject|
|
|
231
|
+
digest = subject["digest"]
|
|
232
|
+
raise Error::InvalidBundle, "Expected in-toto statement with digest" if !digest || digest.empty?
|
|
233
|
+
|
|
234
|
+
digest[expected_algorithm] == expected_hexdigest
|
|
235
|
+
end.any?
|
|
236
|
+
|
|
237
|
+
return if matched
|
|
238
|
+
|
|
239
|
+
VerificationFailure.new(
|
|
240
|
+
"None of in-toto subjects matches artifact for #{expected_algorithm}: #{expected_hexdigest}"
|
|
241
|
+
)
|
|
253
242
|
end
|
|
254
243
|
|
|
255
244
|
public
|
|
@@ -280,13 +269,15 @@ module Sigstore
|
|
|
280
269
|
issuer_cert = find_issuer_cert(chain)
|
|
281
270
|
issuer_pubkey = issuer_cert.public_key
|
|
282
271
|
unless issuer_cert.ca?
|
|
283
|
-
raise Error::InvalidCertificate, "Invalid issuer pubkey basicConstraint (not a CA): #{issuer_cert.
|
|
272
|
+
raise Error::InvalidCertificate, "Invalid issuer pubkey basicConstraint (not a CA): #{issuer_cert.to_pem}"
|
|
284
273
|
end
|
|
285
274
|
|
|
286
|
-
|
|
275
|
+
# TODO: use public_to_der when available
|
|
276
|
+
issuer_key_id = OpenSSL::Digest::SHA256.digest(issuer_pubkey.to_der)
|
|
287
277
|
end
|
|
288
278
|
|
|
289
279
|
digitally_signed = pack_digitally_signed(sct, certificate, issuer_key_id).b
|
|
280
|
+
|
|
290
281
|
ct_keyring.verify(key_id: sct.log_id, signature: sct.signature, data: digitally_signed)
|
|
291
282
|
end
|
|
292
283
|
|
|
@@ -328,16 +319,25 @@ module Sigstore
|
|
|
328
319
|
|
|
329
320
|
[issuer_key_id, len1, len2, len3, tbs_cert].pack("a32 CCC a#{tbs_cert_len}")
|
|
330
321
|
else
|
|
331
|
-
raise Error::Unimplemented, "only x509_entry and precert_entry supported, given #{sct
|
|
322
|
+
raise Error::Unimplemented, "only x509_entry and precert_entry supported, given #{sct.entry_type.inspect}"
|
|
332
323
|
end
|
|
333
324
|
|
|
334
|
-
[
|
|
325
|
+
[
|
|
326
|
+
sct.version,
|
|
327
|
+
0,
|
|
328
|
+
sct.timestamp,
|
|
329
|
+
sct.entry_type,
|
|
330
|
+
signed_entry,
|
|
331
|
+
sct.extensions_bytes&.bytesize.to_i,
|
|
332
|
+
sct.extensions_bytes
|
|
333
|
+
].pack(<<~PACK)
|
|
335
334
|
C # version
|
|
336
335
|
C # signature_type
|
|
337
336
|
Q> # timestamp
|
|
338
337
|
n # entry_type
|
|
339
338
|
a#{signed_entry.bytesize} # signed_entry
|
|
340
339
|
n # extensions length
|
|
340
|
+
a#{sct.extensions_bytes&.bytesize.to_i} # extension
|
|
341
341
|
PACK
|
|
342
342
|
end
|
|
343
343
|
|
data/lib/sigstore/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sigstore
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- The Sigstore Authors
|
|
8
8
|
- Samuel Giddins
|
|
9
|
-
autorequire:
|
|
9
|
+
autorequire:
|
|
10
10
|
bindir: exe
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date:
|
|
12
|
+
date: 2026-03-10 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: logger
|
|
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'
|
|
14
28
|
- !ruby/object:Gem::Dependency
|
|
15
29
|
name: net-http
|
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -53,9 +67,9 @@ dependencies:
|
|
|
53
67
|
- - ">="
|
|
54
68
|
- !ruby/object:Gem::Version
|
|
55
69
|
version: '0'
|
|
56
|
-
description:
|
|
70
|
+
description:
|
|
57
71
|
email:
|
|
58
|
-
-
|
|
72
|
+
-
|
|
59
73
|
- segiddins@segiddins.me
|
|
60
74
|
executables: []
|
|
61
75
|
extensions: []
|
|
@@ -106,7 +120,7 @@ metadata:
|
|
|
106
120
|
allowed_push_host: https://rubygems.org
|
|
107
121
|
homepage_uri: https://github.com/sigstore/sigstore-ruby
|
|
108
122
|
rubygems_mfa_required: 'true'
|
|
109
|
-
post_install_message:
|
|
123
|
+
post_install_message:
|
|
110
124
|
rdoc_options: []
|
|
111
125
|
require_paths:
|
|
112
126
|
- lib
|
|
@@ -114,7 +128,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
114
128
|
requirements:
|
|
115
129
|
- - ">="
|
|
116
130
|
- !ruby/object:Gem::Version
|
|
117
|
-
version: 3.
|
|
131
|
+
version: 3.2.0
|
|
118
132
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
133
|
requirements:
|
|
120
134
|
- - ">="
|
|
@@ -122,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
122
136
|
version: '0'
|
|
123
137
|
requirements: []
|
|
124
138
|
rubygems_version: 3.5.22
|
|
125
|
-
signing_key:
|
|
139
|
+
signing_key:
|
|
126
140
|
specification_version: 4
|
|
127
141
|
summary: A pure-ruby implementation of sigstore signature verification
|
|
128
142
|
test_files: []
|