sigstore 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +7 -0
- data/CODEOWNERS +6 -0
- data/LICENSE +201 -0
- data/README.md +26 -0
- data/data/_store/prod/root.json +165 -0
- data/data/_store/prod/trusted_root.json +114 -0
- data/data/_store/staging/root.json +107 -0
- data/data/_store/staging/trusted_root.json +87 -0
- data/lib/sigstore/error.rb +43 -0
- data/lib/sigstore/internal/json.rb +53 -0
- data/lib/sigstore/internal/key.rb +183 -0
- data/lib/sigstore/internal/keyring.rb +42 -0
- data/lib/sigstore/internal/merkle.rb +117 -0
- data/lib/sigstore/internal/set.rb +42 -0
- data/lib/sigstore/internal/util.rb +52 -0
- data/lib/sigstore/internal/x509.rb +460 -0
- data/lib/sigstore/models.rb +272 -0
- data/lib/sigstore/oidc.rb +149 -0
- data/lib/sigstore/policy.rb +104 -0
- data/lib/sigstore/rekor/checkpoint.rb +114 -0
- data/lib/sigstore/rekor/client.rb +136 -0
- data/lib/sigstore/signer.rb +280 -0
- data/lib/sigstore/trusted_root.rb +116 -0
- data/lib/sigstore/tuf/config.rb +46 -0
- data/lib/sigstore/tuf/error.rb +49 -0
- data/lib/sigstore/tuf/file.rb +96 -0
- data/lib/sigstore/tuf/keys.rb +42 -0
- data/lib/sigstore/tuf/roles.rb +106 -0
- data/lib/sigstore/tuf/root.rb +53 -0
- data/lib/sigstore/tuf/snapshot.rb +45 -0
- data/lib/sigstore/tuf/targets.rb +84 -0
- data/lib/sigstore/tuf/timestamp.rb +39 -0
- data/lib/sigstore/tuf/trusted_metadata_set.rb +193 -0
- data/lib/sigstore/tuf/updater.rb +267 -0
- data/lib/sigstore/tuf.rb +158 -0
- data/lib/sigstore/verifier.rb +492 -0
- data/lib/sigstore/version.rb +19 -0
- data/lib/sigstore.rb +44 -0
- metadata +128 -0
@@ -0,0 +1,87 @@
|
|
1
|
+
{
|
2
|
+
"mediaType": "application/vnd.dev.sigstore.trustedroot+json;version=0.1",
|
3
|
+
"tlogs": [
|
4
|
+
{
|
5
|
+
"baseUrl": "https://rekor.sigstage.dev",
|
6
|
+
"hashAlgorithm": "SHA2_256",
|
7
|
+
"publicKey": {
|
8
|
+
"rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEDODRU688UYGuy54mNUlaEBiQdTE9nYLr0lg6RXowI/QV/RE1azBn4Eg5/2uTOMbhB1/gfcHzijzFi9Tk+g1Prg==",
|
9
|
+
"keyDetails": "PKIX_ECDSA_P256_SHA_256",
|
10
|
+
"validFor": {
|
11
|
+
"start": "2021-01-12T11:53:27.000Z"
|
12
|
+
}
|
13
|
+
},
|
14
|
+
"logId": {
|
15
|
+
"keyId": "0y8wo8MtY5wrdiIFohx7sHeI5oKDpK5vQhGHI6G+pJY="
|
16
|
+
}
|
17
|
+
}
|
18
|
+
],
|
19
|
+
"certificateAuthorities": [
|
20
|
+
{
|
21
|
+
"subject": {
|
22
|
+
"organization": "sigstore.dev",
|
23
|
+
"commonName": "sigstore"
|
24
|
+
},
|
25
|
+
"uri": "https://fulcio.sigstage.dev",
|
26
|
+
"certChain": {
|
27
|
+
"certificates": [
|
28
|
+
{
|
29
|
+
"rawBytes": "MIICGTCCAaCgAwIBAgITJta/okfgHvjabGm1BOzuhrwA1TAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDQxNDIxMzg0MFoXDTMyMDMyMjE2NTA0NVowNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASosAySWJQ/tK5r8T5aHqavk0oI+BKQbnLLdmOMRXHQF/4Hx9KtNfpcdjH9hNKQSBxSlLFFN3tvFCco0qFBzWYwZtsYsBe1l91qYn/9VHFTaEVwYQWIJEEvrs0fvPuAqjajezB5MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRxhjCmFHxib/n31vQFGn9f/+tvrDAfBgNVHSMEGDAWgBT/QjK6aH2rOnCv3AzUGuI+h49mZTAKBggqhkjOPQQDAwNnADBkAjAM1lbKkcqQlE/UspMTbWNo1y2TaJ44tx3l/FJFceTSdDZ+0W1OHHeU4twie/lq8XgCMHQxgEv26xNNiAGyPXbkYgrDPvbOqp0UeWX4mJnLSrBr3aN/KX1SBrKQu220FmVL0Q=="
|
30
|
+
},
|
31
|
+
{
|
32
|
+
"rawBytes": "MIIB9jCCAXugAwIBAgITDdEJvluliE0AzYaIE4jTMdnFTzAKBggqhkjOPQQDAzAqMRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIyMDMyNTE2NTA0NloXDTMyMDMyMjE2NTA0NVowKjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMo9BUNk9QIYisYysC24+2OytoV72YiLonYcqR3yeVnYziPt7Xv++CYE8yoCTiwedUECCWKOcvQKRCJZb9ht4Hzy+VvBx36hK+C6sECCSR0x6pPSiz+cTk1f788ZjBlUZaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP9CMrpofas6cK/cDNQa4j6Hj2ZlMB8GA1UdIwQYMBaAFP9CMrpofas6cK/cDNQa4j6Hj2ZlMAoGCCqGSM49BAMDA2kAMGYCMQD+kojuzMwztNay9Ibzjuk//ZL5m6T2OCsm45l1lY004pcb984L926BowodoirFMcMCMQDIJtFHhP/1D3a+M3dAGomOb6O4CmTry3TTPbPsAFnv22YA0Y+P21NVoxKDjdu0tkw="
|
33
|
+
}
|
34
|
+
]
|
35
|
+
},
|
36
|
+
"validFor": {
|
37
|
+
"start": "2022-04-14T21:38:40.000Z"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
],
|
41
|
+
"ctlogs": [
|
42
|
+
{
|
43
|
+
"baseUrl": "https://ctfe.sigstage.dev/test",
|
44
|
+
"hashAlgorithm": "SHA2_256",
|
45
|
+
"publicKey": {
|
46
|
+
"rawBytes": "MIICCgKCAgEA27A2MPQXm0I0v7/Ly5BIauDjRZF5Jor9vU+QheoE2UIIsZHcyYq3slHzSSHy2lLj1ZD2d91CtJ492ZXqnBmsr4TwZ9jQ05tW2mGIRI8u2DqN8LpuNYZGz/f9SZrjhQQmUttqWmtu3UoLfKz6NbNXUnoo+NhZFcFRLXJ8VporVhuiAmL7zqT53cXR3yQfFPCUDeGnRksnlhVIAJc3AHZZSHQJ8DEXMhh35TVv2nYhTI3rID7GwjXXw4ocz7RGDD37ky6p39Tl5NB71gT1eSqhZhGHEYHIPXraEBd5+3w9qIuLWlp5Ej/K6Mu4ELioXKCUimCbwy+Cs8UhHFlqcyg4AysOHJwIadXIa8LsY51jnVSGrGOEBZevopmQPNPtyfFY3dmXSS+6Z3RD2Gd6oDnNGJzpSyEk410Ag5uvNDfYzJLCWX9tU8lIxNwdFYmIwpd89HijyRyoGnoJ3entd63cvKfuuix5r+GHyKp1Xm1L5j5AWM6P+z0xigwkiXnt+adexAl1J9wdDxv/pUFEESRF4DG8DFGVtbdH6aR1A5/vD4krO4tC1QYUSeyL5Mvsw8WRqIFHcXtgybtxylljvNcGMV1KXQC8UFDmpGZVDSHx6v3e/BHMrZ7gjoCCfVMZ/cFcQi0W2AIHPYEMH/C95J2r4XbHMRdYXpovpOoT5Ca78gsCAwEAAQ==",
|
47
|
+
"keyDetails": "PKCS1_RSA_PKCS1V5",
|
48
|
+
"validFor": {
|
49
|
+
"start": "2021-03-14T00:00:00.000Z",
|
50
|
+
"end": "2022-07-31T00:00:00.000Z"
|
51
|
+
}
|
52
|
+
},
|
53
|
+
"logId": {
|
54
|
+
"keyId": "G3wUKk6ZK6ffHh/FdCRUE2wVekyzHEEIpSG4savnv0w="
|
55
|
+
}
|
56
|
+
},
|
57
|
+
{
|
58
|
+
"baseUrl": "https://ctfe.sigstage.dev/2022",
|
59
|
+
"hashAlgorithm": "SHA2_256",
|
60
|
+
"publicKey": {
|
61
|
+
"rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEh99xuRi6slBFd8VUJoK/rLigy4bYeSYWO/fE6Br7r0D8NpMI94+A63LR/WvLxpUUGBpY8IJA3iU2telag5CRpA==",
|
62
|
+
"keyDetails": "PKIX_ECDSA_P256_SHA_256",
|
63
|
+
"validFor": {
|
64
|
+
"start": "2022-07-01T00:00:00.000Z",
|
65
|
+
"end": "2022-07-31T00:00:00.000Z"
|
66
|
+
}
|
67
|
+
},
|
68
|
+
"logId": {
|
69
|
+
"keyId": "++JKOMQt7SJ3ynUHnCfnDhcKP8/58J4TueMqXuk3HmA="
|
70
|
+
}
|
71
|
+
},
|
72
|
+
{
|
73
|
+
"baseUrl": "https://ctfe.sigstage.dev/2022-2",
|
74
|
+
"hashAlgorithm": "SHA2_256",
|
75
|
+
"publicKey": {
|
76
|
+
"rawBytes": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8gEDKNme8AnXuPBgHjrtXdS6miHqc24CRblNEOFpiJRngeq8Ko73Y+K18yRYVf1DXD4AVLwvKyzdNdl5n0jUSQ==",
|
77
|
+
"keyDetails": "PKIX_ECDSA_P256_SHA_256",
|
78
|
+
"validFor": {
|
79
|
+
"start": "2022-07-01T00:00:00.000Z"
|
80
|
+
}
|
81
|
+
},
|
82
|
+
"logId": {
|
83
|
+
"keyId": "KzC83GiIyeLh2CYpXnQfSDkxlgLynDPLXkNA/rKshno="
|
84
|
+
}
|
85
|
+
}
|
86
|
+
]
|
87
|
+
}
|
@@ -0,0 +1,43 @@
|
|
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 Error < StandardError
|
19
|
+
class InvalidSignature < Error; end
|
20
|
+
class InvalidBundle < Error; end
|
21
|
+
class InvalidCertificate < Error; end
|
22
|
+
class NoCertificate < Error; end
|
23
|
+
class NoTrustedRoot < Error; end
|
24
|
+
class NoBundle < Error; end
|
25
|
+
class NoSignature < Error; end
|
26
|
+
class InvalidKey < Error; end
|
27
|
+
class InvalidCheckpoint < Error; end
|
28
|
+
class InvalidVerificationInput < Error; end
|
29
|
+
|
30
|
+
class Signing < Error; end
|
31
|
+
class InvalidIdentityToken < Error; end
|
32
|
+
|
33
|
+
class MissingRekorEntry < Error; end
|
34
|
+
class InvalidRekorEntry < Error; end
|
35
|
+
class FailedRekorLookup < Error; end
|
36
|
+
class FailedRekorPost < Error; end
|
37
|
+
|
38
|
+
class Unimplemented < Error; end
|
39
|
+
|
40
|
+
class UnsupportedPlatform < Error; end
|
41
|
+
class UnsupportedKeyType < Error; end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,53 @@
|
|
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::Internal
|
18
|
+
module JSON
|
19
|
+
# Implements https://wiki.laptop.org/go/Canonical_JSON
|
20
|
+
#
|
21
|
+
# TODO: This is a naive implementation. Performance can be improved by
|
22
|
+
# serializing into a buffer instead of concatenating strings.
|
23
|
+
def self.canonical_generate(data)
|
24
|
+
case data
|
25
|
+
when NilClass
|
26
|
+
"null"
|
27
|
+
when TrueClass
|
28
|
+
"true"
|
29
|
+
when FalseClass
|
30
|
+
"false"
|
31
|
+
when Integer
|
32
|
+
data.to_s
|
33
|
+
when String
|
34
|
+
"\"#{data.gsub(/(["\\])/, '\\\\\1')}\""
|
35
|
+
when Array
|
36
|
+
contents = data.map { |v| canonical_generate(v) }.join(",")
|
37
|
+
"[#{contents}]"
|
38
|
+
when Hash
|
39
|
+
contents = data.sort_by do |k, _|
|
40
|
+
raise ArgumentError, "Non-string key in hash" unless k.is_a?(String)
|
41
|
+
|
42
|
+
k.encode("utf-16").codepoints
|
43
|
+
end
|
44
|
+
contents.map! do |k, v|
|
45
|
+
"#{canonical_generate(k)}:#{canonical_generate(v)}"
|
46
|
+
end
|
47
|
+
"{#{contents.join(",")}}"
|
48
|
+
else
|
49
|
+
raise ArgumentError, "Unsupported data type: #{data.class}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,183 @@
|
|
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 "util"
|
18
|
+
|
19
|
+
module Sigstore
|
20
|
+
module Internal
|
21
|
+
class Key
|
22
|
+
include Loggable
|
23
|
+
|
24
|
+
def self.from_key_details(key_details, key_bytes)
|
25
|
+
case key_details
|
26
|
+
when Common::V1::PublicKeyDetails::PKIX_ECDSA_P256_SHA_256
|
27
|
+
key_type = "ecdsa"
|
28
|
+
key_schema = "ecdsa-sha2-nistp256"
|
29
|
+
when Common::V1::PublicKeyDetails::PKCS1_RSA_PKCS1V5
|
30
|
+
key_type = "rsa"
|
31
|
+
key_schema = "rsa-pkcs1v15-sha256"
|
32
|
+
else
|
33
|
+
raise Error::UnsupportedKeyType, "Unsupported key type #{key_details}"
|
34
|
+
end
|
35
|
+
|
36
|
+
read(key_type, key_schema, key_bytes, key_id: OpenSSL::Digest::SHA256.hexdigest(key_bytes))
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.read(key_type, schema, key_bytes, key_id: nil)
|
40
|
+
case key_type
|
41
|
+
when "ecdsa", "ecdsa-sha2-nistp256"
|
42
|
+
pkey = OpenSSL::PKey::EC.new(key_bytes)
|
43
|
+
EDCSA.new(key_type, schema, pkey, key_id:)
|
44
|
+
when "ed25519"
|
45
|
+
pkey = ED25519.pkey_from_der([key_bytes].pack("H*"))
|
46
|
+
ED25519.new(key_type, schema, pkey, key_id:)
|
47
|
+
when "rsa"
|
48
|
+
pkey = OpenSSL::PKey::RSA.new(key_bytes)
|
49
|
+
RSA.new(key_type, schema, pkey, key_id:)
|
50
|
+
else
|
51
|
+
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
|
+
end
|
57
|
+
rescue OpenSSL::PKey::PKeyError => e
|
58
|
+
raise OpenSSL::PKey::PKeyError, "Invalid key: #{e} for #{key_type} #{schema} #{key_id}"
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :key_type, :schema, :key_id
|
62
|
+
|
63
|
+
def initialize(key_type, schema, key, key_id: nil)
|
64
|
+
@key_type = key_type
|
65
|
+
@key = key
|
66
|
+
@schema = schema
|
67
|
+
@key_id = key_id
|
68
|
+
end
|
69
|
+
|
70
|
+
def to_pem
|
71
|
+
@key.to_pem
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_der
|
75
|
+
@key.to_der
|
76
|
+
end
|
77
|
+
|
78
|
+
def verify(algo, signature, data)
|
79
|
+
@key.verify(algo, signature, data)
|
80
|
+
rescue OpenSSL::PKey::PKeyError => e
|
81
|
+
logger.debug { "Verification failed: #{e}" }
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
def public_to_der
|
86
|
+
@key.public_to_der
|
87
|
+
end
|
88
|
+
|
89
|
+
class EDCSA < Key
|
90
|
+
def initialize(...)
|
91
|
+
super
|
92
|
+
unless @key_type == "ecdsa" || @key_type == "ecdsa-sha2-nistp256"
|
93
|
+
raise ArgumentError,
|
94
|
+
"key_type must be edcsa, given #{@key_type}"
|
95
|
+
end
|
96
|
+
unless @key.is_a?(OpenSSL::PKey::EC)
|
97
|
+
raise ArgumentError,
|
98
|
+
"key must be an OpenSSL::PKey::EC, is #{@key.inspect}"
|
99
|
+
end
|
100
|
+
|
101
|
+
case @schema
|
102
|
+
when "ecdsa-sha2-nistp256"
|
103
|
+
unless @key.group.curve_name == "prime256v1"
|
104
|
+
raise ArgumentError, "Expected prime256v1 curve, got #{@key.group.curve_name}"
|
105
|
+
end
|
106
|
+
else
|
107
|
+
raise ArgumentError, "Unsupported schema #{schema}"
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class RSA < Key
|
113
|
+
def initialize(...)
|
114
|
+
super
|
115
|
+
raise ArgumentError, "key_type must be rsa, given #{@key_type}" unless @key_type == "rsa"
|
116
|
+
|
117
|
+
unless @key.is_a?(OpenSSL::PKey::RSA)
|
118
|
+
raise ArgumentError, "key must be an OpenSSL::PKey::RSA, given #{@key.inspect}"
|
119
|
+
end
|
120
|
+
|
121
|
+
case @schema
|
122
|
+
when "rsassa-pss-sha256"
|
123
|
+
raise Error::UnsupportedPlatform, "RSA-PSS verification unsupported" unless @key.respond_to?(:verify_pss)
|
124
|
+
when "rsa-pkcs1v15-sha256"
|
125
|
+
# supported
|
126
|
+
else
|
127
|
+
raise ArgumentError, "Unsupported schema #{schema}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def verify(_algo, signature, data)
|
132
|
+
case @schema
|
133
|
+
when "rsassa-pss-sha256"
|
134
|
+
@key.verify_pss("sha256", signature, data, salt_length: :auto, mgf1_hash: "SHA256")
|
135
|
+
when "rsa-pkcs1v15-sha256"
|
136
|
+
super
|
137
|
+
else
|
138
|
+
raise ArgumentError, "Unsupported schema #{schema}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class ED25519 < Key
|
144
|
+
def self.pkey_from_der(raw)
|
145
|
+
if OpenSSL::PKey.respond_to?(:new_raw_public_key)
|
146
|
+
OpenSSL::PKey.new_raw_public_key("ed25519", raw)
|
147
|
+
else
|
148
|
+
pem = <<~PEM
|
149
|
+
-----BEGIN PUBLIC KEY-----
|
150
|
+
MCowBQYDK2VwAyEA#{Internal::Util.base64_encode(raw)}
|
151
|
+
-----END PUBLIC KEY-----
|
152
|
+
PEM
|
153
|
+
OpenSSL::PKey.read(pem)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def initialize(...)
|
158
|
+
super
|
159
|
+
unless @key_type == "ed25519"
|
160
|
+
raise ArgumentError,
|
161
|
+
"key_type must be ed25519, given #{@key_type}"
|
162
|
+
end
|
163
|
+
unless @key.is_a?(OpenSSL::PKey::PKey) && @key.oid == "ED25519"
|
164
|
+
raise ArgumentError,
|
165
|
+
"key must be an OpenSSL::PKey::PKey with oid ED25519, is #{@key.inspect}"
|
166
|
+
end
|
167
|
+
raise ArgumentError, "schema must be #{schema}" unless @schema == schema
|
168
|
+
|
169
|
+
case @schema
|
170
|
+
when "ed25519"
|
171
|
+
# supported
|
172
|
+
else
|
173
|
+
raise ArgumentError, "Unsupported schema #{schema}"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def verify(_algo, signature, data)
|
178
|
+
super(nil, signature, data)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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 Internal
|
19
|
+
class Keyring
|
20
|
+
def initialize(keys:)
|
21
|
+
@keyring = {}
|
22
|
+
keys.each do |key|
|
23
|
+
raise Error, "Duplicate key id #{key.key_id} in keyring" if @keyring.key?(key.key_id)
|
24
|
+
|
25
|
+
@keyring[key.key_id] = key
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def verify(key_id:, signature:, data:)
|
30
|
+
key = @keyring.fetch(key_id) { raise KeyError, "key not found: #{key_id.inspect}, known: #{@keyring.keys}" }
|
31
|
+
|
32
|
+
return true if key.verify("SHA256", signature, data)
|
33
|
+
|
34
|
+
raise(Error::InvalidSignature,
|
35
|
+
"invalid signature: #{signature.inspect} over #{data.inspect} with key #{key_id.inspect}")
|
36
|
+
rescue OpenSSL::PKey::PKeyError => e
|
37
|
+
raise(Error::InvalidSignature,
|
38
|
+
"#{e}: invalid signature: #{signature.inspect} over #{data.inspect} with key #{key_id.inspect}")
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,117 @@
|
|
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 "util"
|
18
|
+
|
19
|
+
module Sigstore
|
20
|
+
module Internal
|
21
|
+
module Merkle
|
22
|
+
class MissingInclusionProofError < StandardError; end
|
23
|
+
class MissingHashError < StandardError; end
|
24
|
+
class InvalidInclusionProofError < StandardError; end
|
25
|
+
class InclusionProofSizeError < InvalidInclusionProofError; end
|
26
|
+
|
27
|
+
def self.verify_merkle_inclusion(entry)
|
28
|
+
inclusion_proof = entry.inclusion_proof
|
29
|
+
raise MissingInclusionProofError, "Rekor entry has no inclusion proof" unless inclusion_proof
|
30
|
+
|
31
|
+
leaf_hash = hash_leaf(entry.canonicalized_body)
|
32
|
+
verify_inclusion(inclusion_proof.log_index, inclusion_proof.tree_size,
|
33
|
+
inclusion_proof.hashes,
|
34
|
+
inclusion_proof.root_hash, leaf_hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.verify_inclusion(index, tree_size, proof, root, leaf_hash)
|
38
|
+
calc_hash = root_from_inclusion_proof(index, tree_size, proof, leaf_hash)
|
39
|
+
|
40
|
+
return if calc_hash == root
|
41
|
+
|
42
|
+
raise InvalidInclusionProofError,
|
43
|
+
"Inclusion proof contains invalid root hash: " \
|
44
|
+
"expected #{root.unpack1("H*")}, calculated #{calc_hash.unpack1("H*")}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.root_from_inclusion_proof(log_index, tree_size, proof, leaf_hash)
|
48
|
+
if log_index >= tree_size
|
49
|
+
raise InclusionProofSizeError,
|
50
|
+
"Log index #{log_index} is greater than tree size #{tree_size}"
|
51
|
+
end
|
52
|
+
|
53
|
+
if leaf_hash.bytesize != 32
|
54
|
+
raise InvalidInclusionProofError,
|
55
|
+
"Leaf hash has wrong size, expected 32 bytes, got #{leaf_hash.size}"
|
56
|
+
end
|
57
|
+
|
58
|
+
if proof.any? { |i| i.bytesize != 32 }
|
59
|
+
raise InvalidInclusionProofError,
|
60
|
+
"Proof hashes have wrong sizes, expected 32 bytes, got #{proof.inspect}"
|
61
|
+
end
|
62
|
+
|
63
|
+
inner, border = decompose_inclusion_proof(log_index, tree_size)
|
64
|
+
|
65
|
+
if proof.size != inner + border
|
66
|
+
raise InclusionProofSizeError,
|
67
|
+
"Inclusion proof has wrong size, expected #{inner + border} hashes, got #{proof.size}"
|
68
|
+
end
|
69
|
+
|
70
|
+
intermediate_result = chain_inner(
|
71
|
+
leaf_hash,
|
72
|
+
(proof[...inner] || raise(MissingHashError, "missing left hashes")),
|
73
|
+
log_index
|
74
|
+
)
|
75
|
+
|
76
|
+
chain_border_right(
|
77
|
+
intermediate_result,
|
78
|
+
proof[inner..] || raise(MissingHashError, "missing right hashes")
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.decompose_inclusion_proof(log_index, tree_size)
|
83
|
+
inner = (log_index ^ (tree_size - 1)).bit_length
|
84
|
+
border = (log_index >> inner).to_s(2).count("1")
|
85
|
+
|
86
|
+
[inner, border]
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.hash_leaf(data)
|
90
|
+
data = "\u0000#{data}".b
|
91
|
+
OpenSSL::Digest.new("SHA256").digest(data)
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.chain_inner(seed, hashes, log_index)
|
95
|
+
hashes.each_with_index do |hash, i|
|
96
|
+
seed = if ((log_index >> i) & 1).zero?
|
97
|
+
hash_children(seed, hash)
|
98
|
+
else
|
99
|
+
hash_children(hash, seed)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
seed
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.chain_border_right(seed, hashes)
|
106
|
+
hashes.reduce(seed) do |acc, hash|
|
107
|
+
hash_children(hash, acc)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.hash_children(left, right)
|
112
|
+
data = "\u0001#{left}#{right}".b
|
113
|
+
OpenSSL::Digest.new("SHA256").digest(data)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,42 @@
|
|
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 Internal
|
19
|
+
module SET
|
20
|
+
def self.verify_set(keyring:, entry:)
|
21
|
+
raise Error, "invalid log entry: no inclusion promise" unless entry.inclusion_promise
|
22
|
+
|
23
|
+
signed_entry_timestamp = entry.inclusion_promise.signed_entry_timestamp
|
24
|
+
log_id = Util.hex_encode(entry.log_id.key_id)
|
25
|
+
|
26
|
+
# https://www.rfc-editor.org/rfc/rfc8785
|
27
|
+
canonical_entry = ::JSON.dump({
|
28
|
+
body: Internal::Util.base64_encode(entry.canonicalized_body),
|
29
|
+
integratedTime: entry.integrated_time,
|
30
|
+
logID: log_id,
|
31
|
+
logIndex: entry.log_index
|
32
|
+
})
|
33
|
+
|
34
|
+
keyring.verify(
|
35
|
+
key_id: log_id,
|
36
|
+
signature: signed_entry_timestamp,
|
37
|
+
data: canonical_entry
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,52 @@
|
|
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 Internal
|
19
|
+
module Util
|
20
|
+
module_function
|
21
|
+
|
22
|
+
def hash_algorithm_name(algorithm)
|
23
|
+
case algorithm
|
24
|
+
when Common::V1::HashAlgorithm::SHA2_256
|
25
|
+
"sha256"
|
26
|
+
when Common::V1::HashAlgorithm::SHA2_384
|
27
|
+
"sha384"
|
28
|
+
when Common::V1::HashAlgorithm::SHA2_512
|
29
|
+
"sha512"
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Unrecognized hash algorithm #{algorithm}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def hex_encode(string)
|
36
|
+
string.unpack1("H*")
|
37
|
+
end
|
38
|
+
|
39
|
+
def hex_decode(string)
|
40
|
+
[string].pack("H*")
|
41
|
+
end
|
42
|
+
|
43
|
+
def base64_encode(string)
|
44
|
+
[string].pack("m0")
|
45
|
+
end
|
46
|
+
|
47
|
+
def base64_decode(string)
|
48
|
+
string.unpack1("m0")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|