Class: Sigstore::Internal::X509::Certificate

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/sigstore/internal/x509.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(x509_certificate) ⇒ Certificate

Returns a new instance of Certificate.



96
97
98
99
100
101
102
103
104
105
# File 'lib/sigstore/internal/x509.rb', line 96

def initialize(x509_certificate)
  unless x509_certificate.is_a?(OpenSSL::X509::Certificate)
    raise ArgumentError,
          "Invalid certificate: #{x509_certificate.inspect}"
  end

  @openssl = x509_certificate

  raise Error::InvalidCertificate, "invalid X.509 version: #{version.inspect}" if version != 2 # v3
end

Instance Attribute Details

#opensslObject (readonly)

Returns the value of attribute openssl.



94
95
96
# File 'lib/sigstore/internal/x509.rb', line 94

def openssl
  @openssl
end

Class Method Details

.read(certificate_bytes) ⇒ Object



107
108
109
110
111
# File 'lib/sigstore/internal/x509.rb', line 107

def self.read(certificate_bytes)
  new(OpenSSL::X509::Certificate.new(certificate_bytes))
rescue OpenSSL::X509::CertificateError => e
  raise Error::InvalidCertificate, e.message
end

Instance Method Details

#==(other) ⇒ Object



168
169
170
# File 'lib/sigstore/internal/x509.rb', line 168

def ==(other)
  openssl == other.openssl
end

#ca?Boolean

Returns:

  • (Boolean)

Raises:



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/sigstore/internal/x509.rb', line 190

def ca?
  basic_constraints = extension(Extension::BasicConstraints)
  return false unless basic_constraints

  unless basic_constraints.critical?
    raise Error::InvalidCertificate,
          "invalid X.509 certificate: non-critical BasicConstraints in CA"
  end

  key_usage = extension(Extension::KeyUsage)
  raise Error::InvalidCertificate, "no keyUsage in #{openssl.inspect}" unless key_usage

  ca = basic_constraints.ca
  key_cert_sign = key_usage.key_cert_sign

  return true if ca && key_cert_sign

  return false unless key_cert_sign || ca

  raise Error::InvalidCertificate,
        "invalid X.509 certificate: inconsistent CA/KeyCertSign in BasicConstraints/KeyUsage " \
        "(#{ca.inspect}, #{key_cert_sign.inspect}):" \
        "\n#{openssl.extensions.map(&:to_h).pretty_inspect}" \
        "\n#{key_usage.pretty_inspect}"
end

#extension(cls) ⇒ Object



158
159
160
161
162
163
# File 'lib/sigstore/internal/x509.rb', line 158

def extension(cls)
  openssl.extensions.each do |ext|
    return cls.new(ext) if ext.oid == cls.oid || ext.oid == cls.oid.short_name || ext.oid == cls.oid.oid
  end
  nil
end

#leaf?Boolean

Returns:

  • (Boolean)


172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/sigstore/internal/x509.rb', line 172

def leaf?
  return false if ca?

  key_usage = extension(Extension::KeyUsage) ||
              raise(Error::InvalidCertificate,
                    "no keyUsage in #{openssl.extensions.map(&:to_h)}")

  unless key_usage.digital_signature
    raise Error::InvalidCertificate,
          "invalid certificate for Sigstore purposes: missing digital signature usage: #{key_usage.to_h}"
  end

  extended_key_usage = extension(Extension::ExtendedKeyUsage)
  return false unless extended_key_usage

  extended_key_usage.code_signing?
end

#preissuer?Boolean

Returns:

  • (Boolean)


216
217
218
219
220
221
# File 'lib/sigstore/internal/x509.rb', line 216

def preissuer?
  extended_key_usage = extension(Extension::ExtendedKeyUsage)
  return false unless extended_key_usage

  extended_key_usage.precert?
end

#tbs_certificate_derObject



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/sigstore/internal/x509.rb', line 113

def tbs_certificate_der
  if openssl.respond_to?(:tbs_bytes)
    cert = openssl.dup
    short_name = Extension::PrecertificateSignedCertificateTimestamps.oid.short_name
    cert.extensions = cert.extensions.reject! do |ext|
      ext.oid == short_name
    end || raise(Error::InvalidCertificate,
                 "No PrecertificateSignedCertificateTimestamps found for the certificate")
    return cert.tbs_bytes
  end

  extension(Extension::PrecertificateSignedCertificateTimestamps) ||
    raise(Error::InvalidCertificate,
          "No PrecertificateSignedCertificateTimestamps found for the certificate")

  # This uglyness is needed because there is no way to force modifying an X509 certificate
  # in a way that it will be serialized with the modifications.
  seq = OpenSSL::ASN1.decode(to_der)
  unless seq.is_a?(OpenSSL::ASN1::Sequence) && seq.value.size == 3
    raise Error::InvalidCertificate,
          "invalid X.509 certificate: #{seq.class} #{seq.value.size}"
  end
  seq = seq.value[0]
  unless seq.is_a?(OpenSSL::ASN1::Sequence)
    raise Error::InvalidCertificate,
          "invalid X.509 certificate: #{seq.inspect}"
  end

  seq.value = seq.value.map! do |v|
    next v unless v.tag == 3

    v.value = v.value.map! do |v2|
      v2.value = v2.value.map! do |v3|
        next if v3.first.oid == Extension::PrecertificateSignedCertificateTimestamps.oid.oid

        v3
      end.compact! || raise(Error::InvalidCertificate, "no SCTs found")
      v2
    end
    v
  end

  seq.to_der
end