flores 0.0.6 → 0.0.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e913b4627dc9ac067842992242889c14e47d760
4
- data.tar.gz: 4102c950d2497218313c0126a77c853800fc6c65
3
+ metadata.gz: 0109a1c44c651b3d002eef3719f0888c5723dee9
4
+ data.tar.gz: bd9a91773854cbbbeaf7833297d9a20f3c11d3dc
5
5
  SHA512:
6
- metadata.gz: af96bea45346f686ee75242aa2b09f7d9906b5fd6a85b3710ea1e8be4622c33da75bb007b13eaea19ce3e16afbb355ad26ebf23dbe1c0fa7b33610655fedc986
7
- data.tar.gz: 2a2612427e400a93ac1ad7910c1b2d2d9637a9f42c3e8a4c7c96ac1042fb4cbda6df50d060283de7c3c0122d6c8ecfdc84de68bb515b6d5f7ce5a0e34dfc0b0a
6
+ metadata.gz: be9b7d18c06c04a310af5d11ec0ce5676be812bc8474e4743696d842fe369b8aed382d8b00504dc3201d60e80c8374de02282730c7c481d5ec4d9be5dbbdf406
7
+ data.tar.gz: f7e581e34aac90f9f96add6014949e0ab5fa486c5674056e8413c19c85634b6ef12fbaa05dcc0768ea9e3cf8fa1766688a9d2903635df9e46ccebb048d311751
@@ -2,7 +2,7 @@ Gem::Specification.new do |spec|
2
2
  files = %x(git ls-files).split("\n")
3
3
 
4
4
  spec.name = "flores"
5
- spec.version = "0.0.6"
5
+ spec.version = "0.0.7"
6
6
  spec.summary = "Fuzz, randomize, and stress your tests"
7
7
  spec.description = <<-DESCRIPTION
8
8
  Add fuzzing, randomization, and stress to your tests.
@@ -17,6 +17,7 @@
17
17
 
18
18
  require "flores/namespace"
19
19
  require "flores/random"
20
+ require "flores/pki/csr"
20
21
  require "English"
21
22
  require "openssl"
22
23
 
@@ -65,233 +66,4 @@ module Flores::PKI
65
66
  return [certificate, key]
66
67
  end
67
68
  end
68
-
69
- # A certificate signing request.
70
- #
71
- # From here, you can configure a certificate to be created based on your
72
- # desired configuration.
73
- #
74
- # Example making a root CA:
75
- #
76
- # key = OpenSSL::PKey::RSA.generate(4096, 65537)
77
- # csr = Flores::PKI::CertificateSigningRequest.new
78
- # csr.subject = "OU=Fancy Pants Inc."
79
- # certificate = csr.create_root(key)
80
- #
81
- # Example making an intermediate CA:
82
- #
83
- # root_key = OpenSSL::PKey::RSA.generate(4096, 65537)
84
- # root_csr = Flores::PKI::CertificateSigningRequest.new
85
- # root_csr.subject = "OU=Fancy Pants Inc."
86
- # root_csr.public_key = root_key.public
87
- # root_certificate = csr.create_root(root_key)
88
- #
89
- # intermediate_key = OpenSSL::PKey::RSA.generate(4096, 65537)
90
- # intermediate_csr = Flores::PKI::CertificateSigningRequest.new
91
- # intermediate_csr.public_key = intermediate_key.public
92
- # intermediate_csr.subject = "OU=Fancy Pants Inc. Intermediate 1"
93
- # intermediate_certificate = csr.create_intermediate(root_certificate, root_key)
94
- class CertificateSigningRequest
95
- # raised when an invalid signing configuration is given
96
- class InvalidRequest < StandardError; end
97
-
98
- # raised when invalid data is present in a certificate request
99
- class InvalidData < StandardError; end
100
-
101
- # raised when an invalid subject (format, or whatever) is given in a certificate request
102
- class InvalidSubject < InvalidData; end
103
-
104
- # raised when an invalid time value is given for a certificate request
105
- class InvalidTime < InvalidData; end
106
-
107
- def initialize
108
- self.serial = Flores::PKI.random_serial
109
- self.digest_method = default_digest_method
110
- end
111
-
112
- private
113
-
114
- def validate_subject(value)
115
- OpenSSL::X509::Name.parse(value)
116
- rescue OpenSSL::X509::NameError => e
117
- raise InvalidSubject, "Invalid subject '#{value}'. (#{e})"
118
- rescue TypeError => e
119
- # Bug(?) in MRI 2.1.6(?)
120
- raise InvalidSubject, "Invalid subject '#{value}'. (#{e})"
121
- end
122
-
123
- def subject=(value)
124
- @subject = validate_subject(value)
125
- end
126
-
127
- attr_reader :subject
128
-
129
- def subject_alternates=(values)
130
- @subject_alternates = values
131
- end
132
-
133
- attr_reader :subject_alternates
134
-
135
- def public_key=(value)
136
- @public_key = validate_public_key(value)
137
- end
138
-
139
- def validate_public_key(value)
140
- raise InvalidData, "public key must be a OpenSSL::PKey::PKey" unless value.is_a? OpenSSL::PKey::PKey
141
- value
142
- end
143
-
144
- attr_reader :public_key
145
-
146
- def start_time=(value)
147
- @start_time = validate_time(value)
148
- end
149
-
150
- attr_reader :start_time
151
-
152
- def expire_time=(value)
153
- @expire_time = validate_time(value)
154
- end
155
-
156
- attr_reader :expire_time
157
-
158
- def validate_time(value)
159
- raise InvalidTime, "#{value.inspect} (class #{value.class.name})" unless value.is_a?(Time)
160
- value
161
- end
162
-
163
- def certificate
164
- return @certificate if @certificate
165
- @certificate = OpenSSL::X509::Certificate.new
166
-
167
- # RFC5280
168
- # > 4.1.2.1. Version
169
- # > version MUST be 3 (value is 2).
170
- #
171
- # Version value of '2' means a v3 certificate.
172
- @certificate.version = 2
173
-
174
- @certificate.subject = subject
175
- @certificate.not_before = start_time
176
- @certificate.not_after = expire_time
177
- @certificate.public_key = public_key
178
- @certificate
179
- end
180
-
181
- def default_digest_method
182
- OpenSSL::Digest::SHA256.new
183
- end
184
-
185
- def self_signed?
186
- @signing_certificate.nil?
187
- end
188
-
189
- def validate!
190
- if self_signed?
191
- if @signing_key.nil?
192
- raise InvalidRequest, "No signing_key given. Cannot sign key."
193
- end
194
- elsif @signing_certificate.nil? && @signing_key
195
- raise InvalidRequest, "signing_key given, but no signing_certificate is set"
196
- elsif @signing_certificate && @signing_key.nil?
197
- raise InvalidRequest, "signing_certificate given, but no signing_key is set"
198
- end
199
- end
200
-
201
- def create
202
- validate!
203
- extensions = OpenSSL::X509::ExtensionFactory.new
204
- extensions.subject_certificate = certificate
205
- extensions.issuer_certificate = self_signed? ? certificate : signing_certificate
206
-
207
- certificate.issuer = extensions.issuer_certificate.subject
208
- certificate.add_extension(extensions.create_extension("subjectKeyIdentifier", "hash", false))
209
-
210
- # RFC 5280 4.2.1.1. Authority Key Identifier
211
- # This is "who signed this key"
212
- certificate.add_extension(extensions.create_extension("authorityKeyIdentifier", "keyid:always", false))
213
- #certificate.add_extension(extensions.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always", false))
214
-
215
- if want_signature_ability?
216
- # Create a CA.
217
- certificate.add_extension(extensions.create_extension("basicConstraints", "CA:TRUE", true))
218
- # Rough googling seems to indicate at least keyCertSign is required for CA and intermediate certs.
219
- certificate.add_extension(extensions.create_extension("keyUsage", "keyCertSign, cRLSign, digitalSignature", true))
220
- else
221
- # Create a client+server certificate
222
- #
223
- # It feels weird to create a certificate that's valid as both server and client, but a brief inspection of major
224
- # web properties (apple.com, google.com, yahoo.com, github.com, fastly.com, mozilla.com, amazon.com) reveals that
225
- # major web properties have certificates with both clientAuth and serverAuth extended key usages. Further,
226
- # these major server certificates all have digitalSignature and keyEncipherment for key usage.
227
- #
228
- # Here's the command I used to check this:
229
- # echo mozilla.com apple.com github.com google.com yahoo.com fastly.com elastic.co amazon.com \
230
- # | xargs -n1 sh -c 'openssl s_client -connect $1:443 \
231
- # | sed -ne "/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p" \
232
- # | openssl x509 -text -noout | sed -ne "/X509v3 extensions/,/Signature Algorithm/p" | sed -e "s/^/$1 /"' - \
233
- # | grep -A2 'Key Usage'
234
- certificate.add_extension(extensions.create_extension("keyUsage", "digitalSignature, keyEncipherment", true))
235
- certificate.add_extension(extensions.create_extension("extendedKeyUsage", "clientAuth, serverAuth", false))
236
- end
237
-
238
- if @subject_alternates
239
- certificate.add_extension(extensions.create_extension("subjectAltName", @subject_alternates.join(",")))
240
- end
241
-
242
- certificate.serial = OpenSSL::BN.new(serial)
243
- certificate.sign(signing_key, digest_method)
244
- certificate
245
- end
246
-
247
- # Set the certificate which is going to be signing this request.
248
- def signing_certificate=(certificate)
249
- raise InvalidData, "signing_certificate must be an OpenSSL::X509::Certificate" unless certificate.is_a?(OpenSSL::X509::Certificate)
250
- @signing_certificate = certificate
251
- end
252
- attr_reader :signing_certificate
253
-
254
- attr_reader :signing_key
255
- def signing_key=(private_key)
256
- raise InvalidData, "signing_key must be an OpenSSL::PKey::PKey (or a subclass)" unless private_key.is_a?(OpenSSL::PKey::PKey)
257
- @signing_key = private_key
258
- end
259
-
260
- def want_signature_ability=(value)
261
- raise InvalidData, "want_signature_ability must be a boolean" unless value == true || value == false
262
- @want_signature_ability = value
263
- end
264
-
265
- def want_signature_ability?
266
- @want_signature_ability == true
267
- end
268
-
269
- attr_reader :digest_method
270
- def digest_method=(value)
271
- raise InvalidData, "digest_method must be a OpenSSL::Digest (or a subclass)" unless value.is_a?(OpenSSL::Digest)
272
- @digest_method = value
273
- end
274
-
275
- attr_reader :serial
276
- def serial=(value)
277
- begin
278
- Integer(value)
279
- rescue
280
- raise InvalidData, "Invalid serial value. Must be a number (or a String containing only nubers)"
281
- end
282
- @serial = value
283
- end
284
-
285
- public(:serial, :serial=)
286
- public(:subject, :subject=)
287
- public(:subject_alternates, :subject_alternates=)
288
- public(:public_key, :public_key=)
289
- public(:start_time, :start_time=)
290
- public(:expire_time, :expire_time=)
291
- public(:digest_method, :digest_method=)
292
- public(:want_signature_ability?, :want_signature_ability=)
293
- public(:signing_key, :signing_key=)
294
- public(:signing_certificate, :signing_certificate=)
295
- public(:create)
296
- end # class CertificateSigningRequest
297
69
  end # Flores::PKI
@@ -0,0 +1,232 @@
1
+ require "flores/namespace"
2
+
3
+ module Flores::PKI
4
+ # A certificate signing request.
5
+ #
6
+ # From here, you can configure a certificate to be created based on your
7
+ # desired configuration.
8
+ #
9
+ # Example making a root CA:
10
+ #
11
+ # key = OpenSSL::PKey::RSA.generate(4096, 65537)
12
+ # csr = Flores::PKI::CertificateSigningRequest.new
13
+ # csr.subject = "OU=Fancy Pants Inc."
14
+ # certificate = csr.create_root(key)
15
+ #
16
+ # Example making an intermediate CA:
17
+ #
18
+ # root_key = OpenSSL::PKey::RSA.generate(4096, 65537)
19
+ # root_csr = Flores::PKI::CertificateSigningRequest.new
20
+ # root_csr.subject = "OU=Fancy Pants Inc."
21
+ # root_csr.public_key = root_key.public
22
+ # root_certificate = csr.create_root(root_key)
23
+ #
24
+ # intermediate_key = OpenSSL::PKey::RSA.generate(4096, 65537)
25
+ # intermediate_csr = Flores::PKI::CertificateSigningRequest.new
26
+ # intermediate_csr.public_key = intermediate_key.public
27
+ # intermediate_csr.subject = "OU=Fancy Pants Inc. Intermediate 1"
28
+ # intermediate_certificate = csr.create_intermediate(root_certificate, root_key)
29
+ class CertificateSigningRequest
30
+ # raised when an invalid signing configuration is given
31
+ class InvalidRequest < StandardError; end
32
+
33
+ # raised when invalid data is present in a certificate request
34
+ class InvalidData < StandardError; end
35
+
36
+ # raised when an invalid subject (format, or whatever) is given in a certificate request
37
+ class InvalidSubject < InvalidData; end
38
+
39
+ # raised when an invalid time value is given for a certificate request
40
+ class InvalidTime < InvalidData; end
41
+
42
+ def initialize
43
+ self.serial = Flores::PKI.random_serial
44
+ self.digest_method = default_digest_method
45
+ end
46
+
47
+ private
48
+
49
+ def validate_subject(value)
50
+ OpenSSL::X509::Name.parse(value)
51
+ rescue OpenSSL::X509::NameError => e
52
+ raise InvalidSubject, "Invalid subject '#{value}'. (#{e})"
53
+ rescue TypeError => e
54
+ # Bug(?) in MRI 2.1.6(?)
55
+ raise InvalidSubject, "Invalid subject '#{value}'. (#{e})"
56
+ end
57
+
58
+ def subject=(value)
59
+ @subject = validate_subject(value)
60
+ end
61
+
62
+ attr_reader :subject
63
+
64
+ def subject_alternates=(values)
65
+ @subject_alternates = values
66
+ end
67
+
68
+ attr_reader :subject_alternates
69
+
70
+ def public_key=(value)
71
+ @public_key = validate_public_key(value)
72
+ end
73
+
74
+ def validate_public_key(value)
75
+ raise InvalidData, "public key must be a OpenSSL::PKey::PKey" unless value.is_a? OpenSSL::PKey::PKey
76
+ value
77
+ end
78
+
79
+ attr_reader :public_key
80
+
81
+ def start_time=(value)
82
+ @start_time = validate_time(value)
83
+ end
84
+
85
+ attr_reader :start_time
86
+
87
+ def expire_time=(value)
88
+ @expire_time = validate_time(value)
89
+ end
90
+
91
+ attr_reader :expire_time
92
+
93
+ def validate_time(value)
94
+ raise InvalidTime, "#{value.inspect} (class #{value.class.name})" unless value.is_a?(Time)
95
+ value
96
+ end
97
+
98
+ def certificate
99
+ return @certificate if @certificate
100
+ @certificate = OpenSSL::X509::Certificate.new
101
+
102
+ # RFC5280
103
+ # > 4.1.2.1. Version
104
+ # > version MUST be 3 (value is 2).
105
+ #
106
+ # Version value of '2' means a v3 certificate.
107
+ @certificate.version = 2
108
+
109
+ @certificate.subject = subject
110
+ @certificate.not_before = start_time
111
+ @certificate.not_after = expire_time
112
+ @certificate.public_key = public_key
113
+ @certificate
114
+ end
115
+
116
+ def default_digest_method
117
+ OpenSSL::Digest::SHA256.new
118
+ end
119
+
120
+ def self_signed?
121
+ @signing_certificate.nil?
122
+ end
123
+
124
+ def validate!
125
+ if self_signed?
126
+ if @signing_key.nil?
127
+ raise InvalidRequest, "No signing_key given. Cannot sign key."
128
+ end
129
+ elsif @signing_certificate.nil? && @signing_key
130
+ raise InvalidRequest, "signing_key given, but no signing_certificate is set"
131
+ elsif @signing_certificate && @signing_key.nil?
132
+ raise InvalidRequest, "signing_certificate given, but no signing_key is set"
133
+ end
134
+ end
135
+
136
+ def create
137
+ validate!
138
+ extensions = OpenSSL::X509::ExtensionFactory.new
139
+ extensions.subject_certificate = certificate
140
+ extensions.issuer_certificate = self_signed? ? certificate : signing_certificate
141
+
142
+ certificate.issuer = extensions.issuer_certificate.subject
143
+ certificate.add_extension(extensions.create_extension("subjectKeyIdentifier", "hash", false))
144
+
145
+ # RFC 5280 4.2.1.1. Authority Key Identifier
146
+ # This is "who signed this key"
147
+ certificate.add_extension(extensions.create_extension("authorityKeyIdentifier", "keyid:always", false))
148
+ #certificate.add_extension(extensions.create_extension("authorityKeyIdentifier", "keyid:always,issuer:always", false))
149
+
150
+ if want_signature_ability?
151
+ # Create a CA.
152
+ certificate.add_extension(extensions.create_extension("basicConstraints", "CA:TRUE", true))
153
+ # Rough googling seems to indicate at least keyCertSign is required for CA and intermediate certs.
154
+ certificate.add_extension(extensions.create_extension("keyUsage", "keyCertSign, cRLSign, digitalSignature", true))
155
+ else
156
+ # Create a client+server certificate
157
+ #
158
+ # It feels weird to create a certificate that's valid as both server and client, but a brief inspection of major
159
+ # web properties (apple.com, google.com, yahoo.com, github.com, fastly.com, mozilla.com, amazon.com) reveals that
160
+ # major web properties have certificates with both clientAuth and serverAuth extended key usages. Further,
161
+ # these major server certificates all have digitalSignature and keyEncipherment for key usage.
162
+ #
163
+ # Here's the command I used to check this:
164
+ # echo mozilla.com apple.com github.com google.com yahoo.com fastly.com elastic.co amazon.com \
165
+ # | xargs -n1 sh -c 'openssl s_client -connect $1:443 \
166
+ # | sed -ne "/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/p" \
167
+ # | openssl x509 -text -noout | sed -ne "/X509v3 extensions/,/Signature Algorithm/p" | sed -e "s/^/$1 /"' - \
168
+ # | grep -A2 'Key Usage'
169
+ certificate.add_extension(extensions.create_extension("keyUsage", "digitalSignature, keyEncipherment", true))
170
+ certificate.add_extension(extensions.create_extension("extendedKeyUsage", "clientAuth, serverAuth", false))
171
+ end
172
+
173
+ if @subject_alternates
174
+ certificate.add_extension(extensions.create_extension("subjectAltName", @subject_alternates.join(",")))
175
+ end
176
+
177
+ certificate.serial = OpenSSL::BN.new(serial)
178
+ certificate.sign(signing_key, digest_method)
179
+ certificate
180
+ end
181
+
182
+ # Set the certificate which is going to be signing this request.
183
+ def signing_certificate=(certificate)
184
+ raise InvalidData, "signing_certificate must be an OpenSSL::X509::Certificate" unless certificate.is_a?(OpenSSL::X509::Certificate)
185
+ @signing_certificate = certificate
186
+ end
187
+ attr_reader :signing_certificate
188
+
189
+ attr_reader :signing_key
190
+ def signing_key=(private_key)
191
+ raise InvalidData, "signing_key must be an OpenSSL::PKey::PKey (or a subclass)" unless private_key.is_a?(OpenSSL::PKey::PKey)
192
+ @signing_key = private_key
193
+ end
194
+
195
+ def want_signature_ability=(value)
196
+ raise InvalidData, "want_signature_ability must be a boolean" unless value == true || value == false
197
+ @want_signature_ability = value
198
+ end
199
+
200
+ def want_signature_ability?
201
+ @want_signature_ability == true
202
+ end
203
+
204
+ attr_reader :digest_method
205
+ def digest_method=(value)
206
+ raise InvalidData, "digest_method must be a OpenSSL::Digest (or a subclass)" unless value.is_a?(OpenSSL::Digest)
207
+ @digest_method = value
208
+ end
209
+
210
+ attr_reader :serial
211
+ def serial=(value)
212
+ begin
213
+ Integer(value)
214
+ rescue
215
+ raise InvalidData, "Invalid serial value. Must be a number (or a String containing only nubers)"
216
+ end
217
+ @serial = value
218
+ end
219
+
220
+ public(:serial, :serial=)
221
+ public(:subject, :subject=)
222
+ public(:subject_alternates, :subject_alternates=)
223
+ public(:public_key, :public_key=)
224
+ public(:start_time, :start_time=)
225
+ public(:expire_time, :expire_time=)
226
+ public(:digest_method, :digest_method=)
227
+ public(:want_signature_ability?, :want_signature_ability=)
228
+ public(:signing_key, :signing_key=)
229
+ public(:signing_certificate, :signing_certificate=)
230
+ public(:create)
231
+ end # class CertificateSigningRequest
232
+ end
@@ -160,7 +160,7 @@ module Flores::Random
160
160
  IPV6_SEGMENT = 1 << 16
161
161
 
162
162
  def self.ipv6_pack(length)
163
- length.times.collect { integer(0..IPV6_SEGMENT).to_s(16) }.join(":")
163
+ length.times.collect { integer(0...IPV6_SEGMENT).to_s(16) }.join(":")
164
164
  end
165
165
 
166
166
  def self.ipv6_abbreviation(length)
@@ -143,11 +143,28 @@ module Flores::RSpec::Analyze
143
143
  sample = instances.sample(1)
144
144
  [
145
145
  " #{percent_s(instances.length)} -> [#{instances.length}] #{error}",
146
- " Sample exception for #{sample.first[0]}",
147
- sample.first[1].to_s.gsub(/^/, " ")
146
+ " Sample failure",
147
+ " Inputs:",
148
+ *render_values(sample.first[0]).map { |x| " #{x}" },
149
+ " Exception:",
150
+ sample.first[1].to_s.gsub(/^/, " ")
148
151
  ]
149
152
  end # def error_summary
150
153
 
154
+ def render_values(values)
155
+ # values should be an RSpec::Core::MemoizedHelpers::ThreadsafeMemoized
156
+ lets = values.instance_eval { @memoized }
157
+ return ["<nothing>"] if lets.nil?
158
+
159
+ lets.sort_by { |k,v| v.to_s.size }.map do |k,v|
160
+ if v.to_s.size > 50
161
+ v = v.to_s[0, 50] + "..."
162
+ end
163
+ "#{k}=#{v}"
164
+ end
165
+ end
166
+
167
+
151
168
  def error_sample_states(error, instances)
152
169
  [
153
170
  " Samples causing #{error}:",
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flores
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Sissel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-31 00:00:00.000000000 Z
11
+ date: 2016-11-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |2
14
14
  Add fuzzing, randomization, and stress to your tests.
@@ -36,6 +36,7 @@ files:
36
36
  - flores.gemspec
37
37
  - lib/flores/namespace.rb
38
38
  - lib/flores/pki.rb
39
+ - lib/flores/pki/csr.rb
39
40
  - lib/flores/random.rb
40
41
  - lib/flores/rspec.rb
41
42
  - lib/flores/rspec/analyze.rb
@@ -67,8 +68,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
68
  version: '0'
68
69
  requirements: []
69
70
  rubyforge_project:
70
- rubygems_version: 2.4.8
71
+ rubygems_version: 2.5.1
71
72
  signing_key:
72
73
  specification_version: 4
73
74
  summary: Fuzz, randomize, and stress your tests
74
75
  test_files: []
76
+ has_rdoc: