Module: CsrPeek

Defined in:
lib/csr_peek.rb,
lib/csr_peek/csr.rb,
lib/csr_peek/names.rb,
lib/csr_peek/policy.rb,
lib/csr_peek/version.rb,
lib/csr_peek/key_facts.rb,
lib/csr_peek/extensions.rb,
lib/csr_peek/certificate.rb,
lib/csr_peek/inspectable.rb

Overview

CsrPeek reads certificate signing requests and X.509 certificates and hands back the useful parts safely: subject, common name, Subject Alternative Names, key type and size, and public-key fingerprints. Malformed input returns nil rather than raising, so a bad upload never becomes a 500.

csr = CsrPeek.parse(pem_string)
csr&.common_name        # => "example.com"
csr&.dns_names          # => ["example.com", "www.example.com"]
csr&.weak_key?          # => false

When you want to know why something failed to parse, use the bang variants (parse!, parse_certificate!), which raise CsrPeek::ParseError with a reason.

Every name and SAN value comes straight from the (attacker-controlled) input and may contain control characters, newlines, or markup. Escape it before rendering it into HTML, logs, or shell - CsrPeek reports faithfully, it does not sanitize.

Defined Under Namespace

Modules: Extensions, Inspectable, KeyFacts, Names Classes: Certificate, Csr, Error, ParseError, Policy

Constant Summary collapse

MAX_INPUT_BYTES =

Upper bound on the input we will look at, so untrusted data cannot exhaust memory or CPU. A real CSR or certificate is a few kilobytes; a full chain is tens. 1 MiB is very generous while still bounding the work.

1 << 20
MAX_CHAIN_CERTIFICATES =

Cap on the number of certificates parse_certificates will return from one bundle, so a pathological input cannot make us parse unboundedly many.

100
BEGIN_CERTIFICATE =
"-----BEGIN CERTIFICATE-----"
END_CERTIFICATE =
"-----END CERTIFICATE-----"
EXT_REQUEST_OIDS =

OIDs under which a CSR carries its requested X.509 extensions.

%w[extReq msExtReq].freeze
VERSION =
"0.1.0"

Class Method Summary collapse

Class Method Details

.parse(input) ⇒ Object

Parse a PEM or DER CSR. Returns a CsrPeek::Csr, or nil on invalid input.



54
55
56
57
58
# File 'lib/csr_peek.rb', line 54

def parse(input)
  parse!(input)
rescue ParseError
  nil
end

.parse!(input) ⇒ Object

Like #parse, but raises CsrPeek::ParseError instead of returning nil.

Raises:



61
62
63
64
65
66
67
68
69
# File 'lib/csr_peek.rb', line 61

def parse!(input)
  validate_size!(input)
  raise ParseError, "input is empty" if blank?(input)

  request = decode(input, "certificate signing request") do
    OpenSSL::X509::Request.new(input)
  end
  Csr.from_openssl(request)
end

.parse_certificate(input) ⇒ Object

Parse a PEM or DER X.509 certificate. Returns a CsrPeek::Certificate, or nil on invalid input.



73
74
75
76
77
# File 'lib/csr_peek.rb', line 73

def parse_certificate(input)
  parse_certificate!(input)
rescue ParseError
  nil
end

.parse_certificate!(input) ⇒ Object

Like #parse_certificate, but raises CsrPeek::ParseError instead of nil.

Raises:



80
81
82
83
84
85
86
87
88
# File 'lib/csr_peek.rb', line 80

def parse_certificate!(input)
  validate_size!(input)
  raise ParseError, "input is empty" if blank?(input)

  certificate = decode(input, "X.509 certificate") do
    OpenSSL::X509::Certificate.new(input)
  end
  Certificate.from_openssl(certificate)
end

.parse_certificates(input) ⇒ Object

Parse every certificate in a PEM bundle (a chain, a fullchain.pem), in file order, as an array of CsrPeek::Certificate. Unparseable blocks are skipped. A single PEM or DER certificate yields a one-element array; junk yields []. Use this instead of #parse_certificate, which only sees the first block.



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/csr_peek.rb', line 94

def parse_certificates(input)
  return [] unless within_size?(input)
  return [] if blank?(input)

  certificates = extract_pem_certificates(input.to_s)
  return certificates unless certificates.empty?

  # No PEM blocks at all: try the whole input as one (DER) certificate.
  cert = parse_certificate(input)
  cert ? [cert] : []
end