Class: DevPKI::CLI

Inherits:
Thor
  • Object
show all
Defined in:
lib/devpki/cli.rb,
lib/devpki/cli/ocsp.rb

Instance Method Summary collapse

Instance Method Details

#ocsp(*cer_files) ⇒ Object

Raises:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/devpki/cli/ocsp.rb', line 28

def ocsp(*cer_files)

  raise InvalidOption.new("Please specify at least one CA and subject certificate file.") if cer_files.empty?

  chain_cert_files = []
  if options[:"chain-certs"] != nil
    chain_cert_files=options[:"chain-certs"].split(",")
  end

  ca_subj_map = {}
  cer_files.each do |ca_subj_pair|

    raise InvalidOption.new("\"#{ca_subj_pair}\" is an invalid CA and subject pair. Please pass a pair with format similar to \"ca.cer:a.cer[,b.cer...]\"") if not ca_subj_pair.include?(":")

    ca_subjlist_split = ca_subj_pair.split(":")

    ca_file = ca_subjlist_split.first

    raise InvalidOption.new("No subject certificates specified for CA file \"#{ca_file}\". Please pass a pair with format similar to \"ca.cer:a.cer[,b.cer...]\"") if ca_subjlist_split.length != 2
    subj_files = ca_subjlist_split.last.split(",")

    ca_subj_map[ca_file] = subj_files
  end

  ca_subj_map.each_pair do |ca_file,subj_file_list|
    raise InvalidOption.new("CA certificate file \"#{ca_file}\" does not exist.") if not File.exist?(ca_file)
    subj_file_list.each do |subj_file|
      raise InvalidOption.new("Subject certificate file \"#{subj_file}\" does not exist.") if not File.exist?(subj_file)
    end
  end

  method = options[:method].upcase
  raise InvalidOption.new("GET method not implemented yet.") if method == "GET"

  ###### -------------- move this to DevPKI::OCSP

  cert_ids = []
  store = OpenSSL::X509::Store.new
  chain_cert_files.each do |chain_cert_file|
    puts "Adding #{chain_cert_file} to store"
    store.add_file(chain_cert_file)
  end
  #store.set_default_paths
  #store.add_file("root.crt")

  ca_subj_map.each_pair do |ca_file, subj_file_list|
    ca_cert = OpenSSL::X509::Certificate.new File.read(ca_file)
    store.add_cert(ca_cert)

    subj_file_list.each do |subj_file|
      subj_cert = OpenSSL::X509::Certificate.new File.read(subj_file)

      cert_ids << OpenSSL::OCSP::CertificateId.new(subj_cert, ca_cert)
    end
  end

  request = OpenSSL::OCSP::Request.new
  cert_ids.each do |cert_id|
    request.add_certid(cert_id)
  end

  request_uri = URI(options[:uri])
  request_uri.path = "/" if request_uri.path.to_s.empty?

  http_req = Net::HTTP::Post.new(request_uri.path)
  http_req.content_type = "application/ocsp-request"
  http_req.body = request.to_der

  http_response = Net::HTTP.new(request_uri.host, request_uri.port).start do |http|
    http.request(http_req)
  end

  ## ----

  if http_response.code != "200"
    raise StandardError, "Invalid response code from OCSP responder: #{http_response.code}"
  end

  response = OpenSSL::OCSP::Response.new(http_response.body)
  puts "Status: #{response.status}"
  puts "Status string: #{response.status_string}"

  # Statuses from http://tools.ietf.org/html/rfc2560 section 4.2.1
  #
  # successful            (0),  --Response has valid confirmations
  # malformedRequest      (1),  --Illegal confirmation request
  # internalError         (2),  --Internal error in issuer
  # tryLater              (3),  --Try again later
  #                             --(4) is not used
  # sigRequired           (5),  --Must sign the request
  # unauthorized          (6)   --Request unauthorized
  if response.status != 0
    raise StandardError, "Not a successful status"
  end

  # response.basic.status will be populated, if response.status == 0
  cert_ids.each_with_index do |cert_id,ix|

    # SingleResponse structure from http://tools.ietf.org/html/rfc2560 section 4.2.1
    #
    # SingleResponse ::= SEQUENCE {
    # certID                       CertID,
    # certStatus                   CertStatus,
    # thisUpdate                   GeneralizedTime,
    # nextUpdate         [0]       EXPLICIT GeneralizedTime OPTIONAL,
    # singleExtensions   [1]       EXPLICIT Extensions OPTIONAL }
    # single_response = response.basic.status[ix]

    # Find the single response that matches current cert_id
    single_response = nil
    response.basic.status.each do |single_response_candidate|
      if single_response_candidate[0].serial.to_s == cert_id.serial.to_s
        single_response = single_response_candidate
        break