devcert 1.0.0

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