devcert 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 793a477e3f93c1fee0c6f0ba7e5ae1f0259fc162
4
+ data.tar.gz: 84d588c31894cc0e1749b044907f88f3dbec542f
5
+ SHA512:
6
+ metadata.gz: 314254b2fbd1d4d6033e8def3419a7388d336b6e0eb332a2bd8dbb33bba47b64170bce0b4da5ffca86ecd7873b01953ecaed0c609869bb4598bf0bbcba7a3268
7
+ data.tar.gz: 8f25a98c9c67265fb78367c4da903b140880c04b7098f559d48a9960dcd422e8620317d0f06d375d23eee36dc5146aa0a6a137703a8eba1207c9d2df2eba6718
data/bin/devcert ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require 'devcert'
3
+ ::DevCert::CLI.start ARGV
@@ -0,0 +1,98 @@
1
+ require 'devcert/cli'
2
+ require 'thor'
3
+ require 'devcert/genca'
4
+ require 'devcert/export'
5
+ require 'devcert/issue'
6
+
7
+ module DevCert
8
+ class CLI < ::Thor
9
+ desc 'genca CA_NAME', 'Generate certification authority CA_NAME'
10
+ method_option(
11
+ :output,
12
+ type: :string,
13
+ default: ::Dir.pwd,
14
+ aliases: '-o',
15
+ desc: 'Output directory'
16
+ )
17
+ method_option(
18
+ :key_size,
19
+ type: :numeric,
20
+ default: 2048,
21
+ desc: 'RSA key size in bits'
22
+ )
23
+ method_option(
24
+ :validity,
25
+ type: :numeric,
26
+ default: 90,
27
+ desc: 'CA certificate validity in days'
28
+ )
29
+ def genca(ca_name)
30
+ ::DevCert::GenCA.generate_ca(
31
+ ca_name,
32
+ options[:output],
33
+ options[:key_size],
34
+ options[:validity]
35
+ )
36
+ end
37
+
38
+ desc 'export BUNDLE_PATH', 'Export private_key or certificate from bundle'
39
+ method_option(
40
+ :output,
41
+ type: :string,
42
+ default: ::Dir.pwd,
43
+ aliases: '-o',
44
+ desc: 'Output directory'
45
+ )
46
+ method_option(
47
+ :type,
48
+ enum: ['certificate', 'private_key'],
49
+ required: true,
50
+ aliases: '-t',
51
+ desc: 'Export type'
52
+ )
53
+ def export(bundle_path)
54
+ ::DevCert::Export.export(
55
+ ::File.absolute_path(bundle_path, ::Dir.pwd),
56
+ options[:type],
57
+ options[:output]
58
+ )
59
+ end
60
+
61
+ desc 'issue CA_BUNDLE_PATH', 'Issue certificates'
62
+ method_option(
63
+ :output,
64
+ type: :string,
65
+ default: ::Dir.pwd,
66
+ aliases: '-o',
67
+ desc: 'Output directory'
68
+ )
69
+ method_option(
70
+ :domains,
71
+ type: :array,
72
+ required: true,
73
+ aliases: '-d',
74
+ desc: 'Domain list'
75
+ )
76
+ method_option(
77
+ :key_size,
78
+ type: :numeric,
79
+ default: 2048,
80
+ desc: 'RSA key size in bits'
81
+ )
82
+ method_option(
83
+ :validity,
84
+ type: :numeric,
85
+ default: 90,
86
+ desc: 'Certificate validity in days'
87
+ )
88
+ def issue(ca_bundle_path)
89
+ ::DevCert::Issue.issue(
90
+ ::File.absolute_path(ca_bundle_path, ::Dir.pwd),
91
+ options[:domains],
92
+ options[:output],
93
+ options[:key_size],
94
+ options[:validity]
95
+ )
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,25 @@
1
+ require 'devcert/util'
2
+
3
+ module DevCert
4
+ module Export
5
+ def self.export(bundle_path, type, output_dir)
6
+ bundle = ::DevCert::Util.load_bundle bundle_path
7
+ case type
8
+ when 'private_key'
9
+ private_key_path = ::File.join(
10
+ output_dir,
11
+ "#{::DevCert::Util.normalize_name(bundle[:common_name])}_key.pem"
12
+ )
13
+ ::DevCert::Util.export(private_key_path, bundle[:private_key])
14
+ puts "file: #{private_key_path}"
15
+ when 'certificate'
16
+ certificate_path = ::File.join(
17
+ output_dir,
18
+ "#{::DevCert::Util.normalize_name(bundle[:common_name])}.crt"
19
+ )
20
+ ::DevCert::Util.export(certificate_path, bundle[:certificate])
21
+ puts "file: #{certificate_path}"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,72 @@
1
+ require 'openssl'
2
+ require 'devcert/util'
3
+
4
+ module DevCert
5
+ module GenCA
6
+ def self.generate_ca(common_name, output_dir, key_size, validity)
7
+ defaults = ::DevCert::Util.get_defaults
8
+
9
+ ca_key = ::OpenSSL::PKey::RSA.new key_size
10
+
11
+ ca_name = ::OpenSSL::X509::Name.new(
12
+ [
13
+ ['CN', common_name],
14
+ ['O', defaults[:organization]],
15
+ ['C', defaults[:country]],
16
+ ['ST', defaults[:state_name]],
17
+ ['L', defaults[:locality]]
18
+ ]
19
+ )
20
+
21
+ ca_cert = ::OpenSSL::X509::Certificate.new
22
+ ca_cert.serial = ::DevCert::Util.generate_serial
23
+ ca_cert.version = 2
24
+ ca_cert.not_before = ::Time.now
25
+ ca_cert.not_after = ::Time.now + 60 * 60 * 24 * validity
26
+
27
+ ca_cert.public_key = ca_key.public_key
28
+ ca_cert.subject = ca_name
29
+ ca_cert.issuer = ca_name
30
+
31
+ extension_factory = ::OpenSSL::X509::ExtensionFactory.new
32
+ extension_factory.subject_certificate = ca_cert
33
+ extension_factory.issuer_certificate = ca_cert
34
+
35
+ ca_cert.add_extension(
36
+ extension_factory.create_extension(
37
+ 'subjectKeyIdentifier',
38
+ 'hash'
39
+ )
40
+ )
41
+ ca_cert.add_extension(
42
+ extension_factory.create_extension(
43
+ 'basicConstraints',
44
+ 'CA:TRUE,pathlen:0',
45
+ true
46
+ )
47
+ )
48
+ ca_cert.add_extension(
49
+ extension_factory.create_extension(
50
+ 'keyUsage',
51
+ 'digitalSignature,cRLSign,keyCertSign',
52
+ true
53
+ )
54
+ )
55
+ ca_cert.add_extension(
56
+ extension_factory.create_extension(
57
+ 'extendedKeyUsage',
58
+ 'serverAuth,clientAuth'
59
+ )
60
+ )
61
+
62
+ ca_cert.sign(ca_key, ::OpenSSL::Digest::SHA256.new)
63
+
64
+ bundle_path = ::File.join(
65
+ output_dir,
66
+ "#{::DevCert::Util.normalize_name(common_name)}.devcert"
67
+ )
68
+ ::DevCert::Util.save_bundle bundle_path, common_name, ca_key, ca_cert
69
+ puts "devcert bundle: #{bundle_path}"
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,83 @@
1
+ require 'openssl'
2
+ require 'devcert/util'
3
+
4
+ module DevCert
5
+ module Issue
6
+ def self.issue(ca_bundle_path, domains, output_dir, key_size, validity)
7
+ ca_bundle = ::DevCert::Util.load_bundle ca_bundle_path
8
+ defaults = ::DevCert::Util.get_defaults
9
+ common_name = domains[0]
10
+
11
+ server_key = OpenSSL::PKey::RSA.new key_size
12
+
13
+ server_name = OpenSSL::X509::Name.new [
14
+ ['CN', common_name],
15
+ ['O', defaults[:organization]],
16
+ ['C', defaults[:country]],
17
+ ['ST', defaults[:state_name]],
18
+ ['L', defaults[:locality]]
19
+ ]
20
+
21
+ server_cert = OpenSSL::X509::Certificate.new
22
+ server_cert.serial = ::DevCert::Util.generate_serial
23
+ server_cert.version = 2
24
+ server_cert.not_before = Time.now
25
+ server_cert.not_after = Time.now + 60 * 60 * 24 * validity
26
+
27
+ server_cert.subject = server_name
28
+ server_cert.public_key = server_key.public_key
29
+ server_cert.issuer = ca_bundle[:certificate].subject
30
+
31
+ extension_factory = OpenSSL::X509::ExtensionFactory.new
32
+ extension_factory.subject_certificate = server_cert
33
+ extension_factory.issuer_certificate = ca_bundle[:certificate]
34
+
35
+ server_cert.add_extension(
36
+ extension_factory.create_extension(
37
+ 'basicConstraints',
38
+ 'CA:FALSE',
39
+ true
40
+ )
41
+ )
42
+ server_cert.add_extension(
43
+ extension_factory.create_extension(
44
+ 'keyUsage',
45
+ 'keyEncipherment,digitalSignature',
46
+ true
47
+ )
48
+ )
49
+ server_cert.add_extension(
50
+ extension_factory.create_extension(
51
+ 'extendedKeyUsage',
52
+ 'serverAuth,clientAuth'
53
+ )
54
+ )
55
+ server_cert.add_extension(
56
+ extension_factory.create_extension(
57
+ 'subjectKeyIdentifier',
58
+ 'hash'
59
+ )
60
+ )
61
+ server_cert.add_extension(
62
+ extension_factory.create_extension(
63
+ 'subjectAltName',
64
+ domains.map { |d| "DNS:#{d}" }.join(',')
65
+ )
66
+ )
67
+
68
+ server_cert.sign ca_bundle[:private_key], OpenSSL::Digest::SHA256.new
69
+
70
+ bundle_path = ::File.join(
71
+ output_dir,
72
+ "#{::DevCert::Util.normalize_name(common_name)}.devcert"
73
+ )
74
+ ::DevCert::Util.save_bundle(
75
+ bundle_path,
76
+ common_name,
77
+ server_key,
78
+ server_cert
79
+ )
80
+ puts "devcert bundle: #{bundle_path}"
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,67 @@
1
+ require 'yaml'
2
+ require 'openssl'
3
+ require 'securerandom'
4
+
5
+ module DevCert
6
+ module Util
7
+ def self.get_defaults
8
+ path = ::File.absolute_path 'defaults.yaml', ::Dir.pwd
9
+ data = \
10
+ if ::File.exist? path
11
+ ::YAML.load(::File.open(path)).fetch('devcert', {})
12
+ else
13
+ {}
14
+ end
15
+
16
+ {
17
+ organization: data.fetch('organization', 'Acme Ltd.'),
18
+ country: data.fetch('country', 'US'),
19
+ state_name: data.fetch('state_name', 'California'),
20
+ locality: data.fetch('locality', 'San Francisco')
21
+ }
22
+ end
23
+
24
+ def self.normalize_name(name)
25
+ name.gsub(/[ .-]/, '_')
26
+ end
27
+
28
+ def self.save_bundle(path, common_name, key, cert)
29
+ bundle = {
30
+ common_name: common_name,
31
+ private_key: key.to_der,
32
+ certificate: cert.to_der
33
+ }
34
+
35
+ open path, 'w' do |io|
36
+ io.write bundle.to_yaml
37
+ end
38
+ end
39
+
40
+ def self.export(path, entity)
41
+ open path, 'w' do |io|
42
+ io.write entity.to_pem
43
+ end
44
+ end
45
+
46
+ def self.load_bundle(path)
47
+ full_path = ::File.absolute_path path, __dir__
48
+ if ::File.exist? full_path
49
+ data = ::YAML.load ::File.open full_path
50
+ {
51
+ common_name: data[:common_name],
52
+ private_key: ::OpenSSL::PKey::RSA.new(data[:private_key]),
53
+ certificate: ::OpenSSL::X509::Certificate.new(data[:certificate])
54
+ }
55
+ else
56
+ raise "No bundle at #{full_path} exists!"
57
+ end
58
+ end
59
+
60
+ def self.generate_serial
61
+ machine_bytes = ['foo'].pack('p').size
62
+ machine_bits = machine_bytes * 8
63
+ machine_max_signed = 2**(machine_bits - 1) - 1
64
+ ::SecureRandom.random_number machine_max_signed
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module DevCert
2
+ VERSION = '1.0.0'
3
+ end
data/lib/devcert.rb ADDED
@@ -0,0 +1 @@
1
+ require 'devcert/cli'
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devcert
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Alexander Pyatkin
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-09-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.19.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.19.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Create development SSL/TLS certificates without a hassle
56
+ email: aspyatkin@gmail.com
57
+ executables:
58
+ - devcert
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - bin/devcert
63
+ - lib/devcert.rb
64
+ - lib/devcert/cli.rb
65
+ - lib/devcert/export.rb
66
+ - lib/devcert/genca.rb
67
+ - lib/devcert/issue.rb
68
+ - lib/devcert/util.rb
69
+ - lib/devcert/version.rb
70
+ homepage: https://github.com/aspyatkin/devcert
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '2.3'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.5.1
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Create development SSL/TLS certificates without a hassle
94
+ test_files: []