r509-ca-http 0.1

Sign up to get free protection for your applications and to get access to all the features.
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>