r509-ca-http 0.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.
Files changed (41) hide show
  1. data/README.md +122 -0
  2. data/Rakefile +38 -0
  3. data/doc/R509.html +117 -0
  4. data/doc/R509/CertificateAuthority.html +117 -0
  5. data/doc/R509/CertificateAuthority/Http.html +131 -0
  6. data/doc/R509/CertificateAuthority/Http/Factory.html +115 -0
  7. data/doc/R509/CertificateAuthority/Http/Factory/CsrFactory.html +189 -0
  8. data/doc/R509/CertificateAuthority/Http/Factory/SpkiFactory.html +189 -0
  9. data/doc/R509/CertificateAuthority/Http/Server.html +133 -0
  10. data/doc/R509/CertificateAuthority/Http/SubjectParser.html +265 -0
  11. data/doc/R509/CertificateAuthority/Http/ValidityPeriodConverter.html +207 -0
  12. data/doc/_index.html +206 -0
  13. data/doc/class_list.html +53 -0
  14. data/doc/css/common.css +1 -0
  15. data/doc/css/full_list.css +57 -0
  16. data/doc/css/style.css +328 -0
  17. data/doc/file.README.html +209 -0
  18. data/doc/file_list.html +55 -0
  19. data/doc/frames.html +28 -0
  20. data/doc/index.html +209 -0
  21. data/doc/js/app.js +214 -0
  22. data/doc/js/full_list.js +173 -0
  23. data/doc/js/jquery.js +4 -0
  24. data/doc/method_list.html +92 -0
  25. data/doc/top-level-namespace.html +112 -0
  26. data/lib/r509/certificateauthority/http/factory.rb +15 -0
  27. data/lib/r509/certificateauthority/http/server.rb +237 -0
  28. data/lib/r509/certificateauthority/http/subjectparser.rb +33 -0
  29. data/lib/r509/certificateauthority/http/validityperiodconverter.rb +16 -0
  30. data/lib/r509/certificateauthority/http/version.rb +7 -0
  31. data/lib/r509/certificateauthority/http/views/test_issue.erb +85 -0
  32. data/lib/r509/certificateauthority/http/views/test_revoke.erb +31 -0
  33. data/lib/r509/certificateauthority/http/views/test_unrevoke.erb +26 -0
  34. data/spec/fixtures/test_ca.cer +22 -0
  35. data/spec/fixtures/test_ca.key +28 -0
  36. data/spec/fixtures/test_config.yaml +18 -0
  37. data/spec/http_spec.rb +250 -0
  38. data/spec/spec_helper.rb +22 -0
  39. data/spec/subject_parser_spec.rb +51 -0
  40. data/spec/validity_period_converter_spec.rb +79 -0
  41. metadata +165 -0
@@ -0,0 +1,112 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.8.3
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ hasFrames = window.top.frames.main ? true : false;
19
+ relpath = '';
20
+ framesUrl = "frames.html#!" + escape(window.location.href);
21
+ </script>
22
+
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
25
+
26
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
27
+
28
+
29
+ </head>
30
+ <body>
31
+ <div id="header">
32
+ <div id="menu">
33
+
34
+ <a href="_index.html">Index</a> &raquo;
35
+
36
+
37
+ <span class="title">Top Level Namespace</span>
38
+
39
+
40
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
41
+ </div>
42
+
43
+ <div id="search">
44
+
45
+ <a class="full_list_link" id="class_list_link"
46
+ href="class_list.html">
47
+ Class List
48
+ </a>
49
+
50
+ <a class="full_list_link" id="method_list_link"
51
+ href="method_list.html">
52
+ Method List
53
+ </a>
54
+
55
+ <a class="full_list_link" id="file_list_link"
56
+ href="file_list.html">
57
+ File List
58
+ </a>
59
+
60
+ </div>
61
+ <div class="clear"></div>
62
+ </div>
63
+
64
+ <iframe id="search_frame"></iframe>
65
+
66
+ <div id="content"><h1>Top Level Namespace
67
+
68
+
69
+
70
+ </h1>
71
+
72
+ <dl class="box">
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+
81
+ </dl>
82
+ <div class="clear"></div>
83
+
84
+ <h2>Defined Under Namespace</h2>
85
+ <p class="children">
86
+
87
+
88
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="R509.html" title="R509 (module)">R509</a></span>
89
+
90
+
91
+
92
+
93
+ </p>
94
+
95
+
96
+
97
+
98
+
99
+
100
+
101
+
102
+
103
+ </div>
104
+
105
+ <div id="footer">
106
+ Generated on Thu Nov 8 14:58:26 2012 by
107
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
108
+ 0.8.3 (ruby-1.9.3).
109
+ </div>
110
+
111
+ </body>
112
+ </html>
@@ -0,0 +1,15 @@
1
+ module R509::CertificateAuthority::Http
2
+ module Factory
3
+ class CsrFactory
4
+ def build(options)
5
+ R509::Csr.new(options)
6
+ end
7
+ end
8
+
9
+ class SpkiFactory
10
+ def build(options)
11
+ R509::Spki.new(options)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,237 @@
1
+ require 'rubygems' if RUBY_VERSION < "1.9"
2
+ require 'sinatra/base'
3
+ require 'r509'
4
+ require "#{File.dirname(__FILE__)}/subjectparser"
5
+ require "#{File.dirname(__FILE__)}/validityperiodconverter"
6
+ require "#{File.dirname(__FILE__)}/factory"
7
+ require 'base64'
8
+ require 'yaml'
9
+ require 'logger'
10
+ require 'dependo'
11
+
12
+ module R509
13
+ module CertificateAuthority
14
+ module Http
15
+ class Server < Sinatra::Base
16
+ extend Dependo::Mixin
17
+ include Dependo::Mixin
18
+
19
+ configure do
20
+ disable :protection #disable Rack::Protection (for speed)
21
+ disable :logging
22
+ set :environment, :production
23
+
24
+ crls = {}
25
+ certificate_authorities = {}
26
+ config_pool.names.each do |name|
27
+ crls[name] = R509::Crl::Administrator.new(config_pool[name])
28
+ certificate_authorities[name] = R509::CertificateAuthority::Signer.new(config_pool[name])
29
+ end
30
+
31
+ set :crls, crls
32
+ set :certificate_authorities, certificate_authorities
33
+ set :subject_parser, R509::CertificateAuthority::Http::SubjectParser.new
34
+ set :validity_period_converter, R509::CertificateAuthority::Http::ValidityPeriodConverter.new
35
+ set :csr_factory, R509::CertificateAuthority::Http::Factory::CsrFactory.new
36
+ set :spki_factory, R509::CertificateAuthority::Http::Factory::SpkiFactory.new
37
+ end
38
+
39
+ before do
40
+ content_type :text
41
+ end
42
+
43
+ helpers do
44
+ def crl(name)
45
+ settings.crls[name]
46
+ end
47
+ def ca(name)
48
+ settings.certificate_authorities[name]
49
+ end
50
+ def subject_parser
51
+ settings.subject_parser
52
+ end
53
+ def validity_period_converter
54
+ settings.validity_period_converter
55
+ end
56
+ def csr_factory
57
+ settings.csr_factory
58
+ end
59
+ def spki_factory
60
+ settings.spki_factory
61
+ end
62
+ end
63
+
64
+ error do
65
+ log.error env["sinatra.error"].inspect
66
+ log.error env["sinatra.error"].backtrace.join("\n")
67
+ "Something is amiss with our CA. You should ... wait?"
68
+ end
69
+
70
+ error StandardError do
71
+ log.error env["sinatra.error"].inspect
72
+ log.error env["sinatra.error"].backtrace.join("\n")
73
+ env["sinatra.error"].inspect
74
+ end
75
+
76
+ get '/favicon.ico' do
77
+ log.debug "go away. no children."
78
+ "go away. no children"
79
+ end
80
+
81
+ get '/1/crl/:ca/get/?' do
82
+ log.info "Get CRL for #{params[:ca]}"
83
+
84
+ if not crl(params[:ca])
85
+ raise ArgumentError, "CA not found"
86
+ end
87
+
88
+ crl(params[:ca]).to_pem
89
+ end
90
+
91
+ get '/1/crl/:ca/generate/?' do
92
+ log.info "Generate CRL for #{params[:ca]}"
93
+
94
+ if not crl(params[:ca])
95
+ raise ArgumentError, "CA not found"
96
+ end
97
+
98
+ crl(params[:ca]).generate_crl
99
+ end
100
+
101
+ post '/1/certificate/issue/?' do
102
+ log.info "Issue Certificate"
103
+ raw = request.env["rack.input"].read
104
+ env["rack.input"].rewind
105
+ log.info raw
106
+
107
+ log.info params.inspect
108
+
109
+ if not params.has_key?("ca")
110
+ raise ArgumentError, "Must provide a CA"
111
+ end
112
+ if not ca(params["ca"])
113
+ raise ArgumentError, "CA not found"
114
+ end
115
+ if not params.has_key?("profile")
116
+ raise ArgumentError, "Must provide a CA profile"
117
+ end
118
+ if not params.has_key?("validityPeriod")
119
+ raise ArgumentError, "Must provide a validity period"
120
+ end
121
+ if not params.has_key?("csr") and not params.has_key?("spki")
122
+ raise ArgumentError, "Must provide a CSR or SPKI"
123
+ end
124
+
125
+ subject = subject_parser.parse(raw, "subject")
126
+ log.info subject.inspect
127
+ log.info subject.to_s
128
+ if subject.empty?
129
+ raise ArgumentError, "Must provide a subject"
130
+ end
131
+
132
+ if params.has_key?("extensions") and params["extensions"].has_key?("subjectAlternativeName")
133
+ san_names = params["extensions"]["subjectAlternativeName"].select { |name| not name.empty? }
134
+ else
135
+ san_names = []
136
+ end
137
+
138
+ data_hash = {
139
+ :subject => subject,
140
+ :san_names => san_names
141
+ }
142
+
143
+ validity_period = validity_period_converter.convert(params["validityPeriod"])
144
+
145
+ if params.has_key?("csr")
146
+ csr = csr_factory.build(:csr => params["csr"])
147
+ cert = ca(params["ca"]).sign(
148
+ :csr => csr,
149
+ :profile_name => params["profile"],
150
+ :data_hash => data_hash,
151
+ :not_before => validity_period[:not_before],
152
+ :not_after => validity_period[:not_after]
153
+ )
154
+ elsif params.has_key?("spki")
155
+ spki = spki_factory.build(:spki => params["spki"], :subject => subject)
156
+ cert = ca(params["ca"]).sign(
157
+ :spki => spki,
158
+ :profile_name => params["profile"],
159
+ :data_hash => data_hash,
160
+ :not_before => validity_period[:not_before],
161
+ :not_after => validity_period[:not_after]
162
+ )
163
+ else
164
+ raise ArgumentError, "Must provide a CSR or SPKI"
165
+ end
166
+
167
+ pem = cert.to_pem
168
+ log.info pem
169
+
170
+ pem
171
+ end
172
+
173
+ post '/1/certificate/revoke/?' do
174
+ ca = params[:ca]
175
+ serial = params[:serial]
176
+ reason = params[:reason]
177
+ log.info "Revoke for serial #{serial} on CA #{ca}"
178
+
179
+ if not ca
180
+ raise ArgumentError, "CA must be provided"
181
+ end
182
+ if not crl(ca)
183
+ raise ArgumentError, "CA not found"
184
+ end
185
+ if not serial
186
+ raise ArgumentError, "Serial must be provided"
187
+ end
188
+ if not reason
189
+ reason = 0
190
+ end
191
+
192
+ crl(ca).revoke_cert(serial.to_i, reason.to_i)
193
+
194
+ crl(ca).to_pem
195
+ end
196
+
197
+ post '/1/certificate/unrevoke/?' do
198
+ ca = params[:ca]
199
+ serial = params[:serial]
200
+ log.info "Unrevoke for serial #{serial} on CA #{ca}"
201
+
202
+ if not ca
203
+ raise ArgumentError, "CA must be provided"
204
+ end
205
+ if not crl(ca)
206
+ raise ArgumentError, "CA not found"
207
+ end
208
+ if not serial
209
+ raise ArgumentError, "Serial must be provided"
210
+ end
211
+
212
+ crl(ca).unrevoke_cert(serial.to_i)
213
+
214
+ crl(ca).to_pem
215
+ end
216
+
217
+ get '/test/certificate/issue/?' do
218
+ log.info "Loaded test issuance interface"
219
+ content_type :html
220
+ erb :test_issue
221
+ end
222
+
223
+ get '/test/certificate/revoke/?' do
224
+ log.info "Loaded test revoke interface"
225
+ content_type :html
226
+ erb :test_revoke
227
+ end
228
+
229
+ get '/test/certificate/unrevoke/?' do
230
+ log.info "Loaded test unrevoke interface"
231
+ content_type :html
232
+ erb :test_unrevoke
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,33 @@
1
+ module R509
2
+ module CertificateAuthority
3
+ module Http
4
+ class SubjectParser
5
+ def parse(raw, name="subject")
6
+ if raw.nil?
7
+ raise ArgumentError, "Must provide a query string"
8
+ end
9
+
10
+ subject = R509::Subject.new
11
+ raw.split(/[&;] */n).each { |pair|
12
+ key, value = pair.split('=', 2).map { |data| unescape(data) }
13
+ match = key.match(/#{name}\[(.*)\]/)
14
+ if not match.nil? and not value.empty?
15
+ subject[match[1]] = value
16
+ end
17
+ }
18
+ subject
19
+ end
20
+
21
+ if defined?(::Encoding)
22
+ def unescape(s, encoding = Encoding::UTF_8)
23
+ URI.decode_www_form_component(s, encoding)
24
+ end
25
+ else
26
+ def unescape(s, encoding = nil)
27
+ URI.decode_www_form_component(s, encoding)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ module R509::CertificateAuthority::Http
2
+ class ValidityPeriodConverter
3
+ def convert(validity_period)
4
+ if validity_period.nil?
5
+ raise ArgumentError, "Must provide validity period"
6
+ end
7
+ if validity_period.to_i <= 0
8
+ raise ArgumentError, "Validity period must be positive"
9
+ end
10
+ {
11
+ :not_before => Time.now - 6 * 60 * 60,
12
+ :not_after => Time.now + validity_period.to_i,
13
+ }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module R509
2
+ module CertificateAuthority
3
+ module Http
4
+ VERSION="0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,85 @@
1
+ <html>
2
+ <head>
3
+ <title>Issue</title>
4
+ </head>
5
+ <body>
6
+
7
+ <h1>Issue a certificate</h1>
8
+
9
+ <form method="post" action="/1/certificate/issue/">
10
+ <p>
11
+ CA:
12
+ <br />
13
+ <input type="text" name="ca" />
14
+ </p>
15
+ <p>
16
+ Profile:
17
+ <br />
18
+ <input type="text" name="profile" />
19
+ </p>
20
+ <p>
21
+ Validity Period (in seconds):
22
+ <br />
23
+ <input type="text" name="validityPeriod" />
24
+ </p>
25
+ <p>
26
+ C:
27
+ <br />
28
+ <input type="text" name="subject[C]" />
29
+ </p>
30
+ <p>
31
+ ST:
32
+ <br />
33
+ <input type="text" name="subject[ST]" />
34
+ </p>
35
+ <p>
36
+ L:
37
+ <br />
38
+ <input type="text" name="subject[L]" />
39
+ </p>
40
+ <p>
41
+ O:
42
+ <br />
43
+ <input type="text" name="subject[O]" />
44
+ </p>
45
+ <p>
46
+ OU:
47
+ <br />
48
+ <input type="text" name="subject[OU]" />
49
+ </p>
50
+ <p>
51
+ CN:
52
+ <br />
53
+ <input type="text" name="subject[CN]" />
54
+ </p>
55
+ <p>
56
+ emailAddress:
57
+ <br />
58
+ <input type="text" name="subject[emailAddress]" />
59
+ </p>
60
+ <p>
61
+ SAN:
62
+ <br />
63
+ <input type="text" name="extensions[subjectAlternativeName][]" />
64
+ <br />
65
+ <input type="text" name="extensions[subjectAlternativeName][]" />
66
+ <br />
67
+ <input type="text" name="extensions[subjectAlternativeName][]" />
68
+ <br />
69
+ <input type="text" name="extensions[subjectAlternativeName][]" />
70
+ <br />
71
+ <input type="text" name="extensions[subjectAlternativeName][]" />
72
+ <br />
73
+ </p>
74
+ <p>
75
+ CSR:
76
+ <br />
77
+ <textarea name="csr" rows="6" cols="80"></textarea>
78
+ </p>
79
+ <p>
80
+ <input type="submit" value="Gimme" />
81
+ </p>
82
+ </form>
83
+
84
+ </body>
85
+ </html>