mypki 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +8 -0
- data/LICENSE +21 -0
- data/README.md +83 -0
- data/Rakefile +2 -0
- data/exe/mypki +57 -0
- data/lib/mypki.rb +5 -0
- data/lib/mypki/adapters/http_persistent.rb +13 -0
- data/lib/mypki/adapters/httpclient.rb +12 -0
- data/lib/mypki/adapters/net_http.rb +19 -0
- data/lib/mypki/adapters/openssl.rb +8 -0
- data/lib/mypki/configuration.rb +78 -0
- data/lib/mypki/core.rb +76 -0
- data/lib/mypki/jruby.rb +14 -0
- data/lib/mypki/loaders/ca.rb +39 -0
- data/lib/mypki/loaders/p12.rb +31 -0
- data/lib/mypki/loaders/pem.rb +61 -0
- data/lib/mypki/loaders/ssh.rb +52 -0
- data/lib/mypki/prompter.rb +43 -0
- data/lib/mypki/prompters/cli.rb +23 -0
- data/lib/mypki/prompters/iruby.rb +69 -0
- data/lib/mypki/prompters/jruby.rb +13 -0
- data/lib/mypki/version.rb +3 -0
- data/mypki.gemspec +30 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 29b0e1ca9d86843362a76292865c1d5e0e7c4b39
|
4
|
+
data.tar.gz: f38a3df9e6f9e2ff3ad5eaf999b822bb91bee9ff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 09ec984e8eeb92bc2210c66f7465d62a1420016a0a6f4cb161b3037eb19be248cf2a34d4776ad1fa9d0561b27ca0c9ca322004f881b0f5c4dd8e53c9403e3eff
|
7
|
+
data.tar.gz: 819fa0717376a6af75f66af6b71838b5065f92f0c7ba72a9270ffb3ff00c5510da991483b80e304831d8723353dd0d8d1bd8dd8822def4c4604878ced4c6b393
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) [year] [fullname]
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# MyPKI
|
2
|
+
|
3
|
+
PKI-enables Ruby's OpenSSL libraries, which PKI-enables most libraries and gems written in Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```
|
8
|
+
$ gem install mypki
|
9
|
+
```
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Simply `require 'mypki'` at the top of your script. If you don't want to PKI-enable everything, see the *Adapters* section.
|
14
|
+
|
15
|
+
## Configuration
|
16
|
+
|
17
|
+
By default, MyPKI saves PKI information in the `.mypki`. Passwords are never saved to the configuration. If a configuration does not exist, MyPKI will prompt you for your PKI path the first time it is run. Should you need to change things, run the mypki utility:
|
18
|
+
|
19
|
+
```
|
20
|
+
$ mypki --reconfigure
|
21
|
+
```
|
22
|
+
|
23
|
+
MyPKI support P12s, PEMs, and key pairs. The P12 loader supports a `password` field in it's section of the ~/.mypki configuration file. I don't recommend keeping your password in the MyPKI config file.
|
24
|
+
|
25
|
+
## Headless configuration
|
26
|
+
|
27
|
+
If you are using MyPKI on a server, then you can specify the location of the MyPKI configuration file by setting the `MYPKI_CONFIG` environment variable.
|
28
|
+
|
29
|
+
## The MyPKI Utility
|
30
|
+
|
31
|
+
MyPKI ships with a command-line utility that will PKI-enable any other command-line tool written in Ruby. For example, to PKI the `geminabox` uploader:
|
32
|
+
|
33
|
+
```
|
34
|
+
$ mypki gem inabox my-gem-1.0.0.gem
|
35
|
+
```
|
36
|
+
|
37
|
+
IF you do this often, you can alias a command like this:
|
38
|
+
|
39
|
+
```
|
40
|
+
alias gem='mypki gem'
|
41
|
+
```
|
42
|
+
|
43
|
+
## Adapters
|
44
|
+
|
45
|
+
By default, MyPKI PKI-enables all OpenSSL contexts. If you would only like to PKI-enable a particular library, you can use an adapter. For example:
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
require 'mypki/adapters/httpclient'
|
49
|
+
```
|
50
|
+
|
51
|
+
The following adapters are available:
|
52
|
+
|
53
|
+
* httpclient
|
54
|
+
* http_persistent
|
55
|
+
* net_http
|
56
|
+
* openssl (default)
|
57
|
+
|
58
|
+
## How does MyPKI work?
|
59
|
+
|
60
|
+
MyPKI modifies `OpenSSL::SSL::SSLContext` to use your PKCS#12 certificate and key. If you're working in a context where you want to be specific about what is PKI-enabled, you can. Here's an example of using my PKI to PKI-enable only `HTTPClient`.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
require 'mypki/core'
|
64
|
+
require 'httpclient'
|
65
|
+
|
66
|
+
class HTTPClient
|
67
|
+
class SSLSocketWrap
|
68
|
+
def create_openssl_socket socket
|
69
|
+
context = MyPKI::Context.new
|
70
|
+
@context.set_context(context)
|
71
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
## A note about PEMs
|
78
|
+
|
79
|
+
If you're trying to configure with PEM and are getting errors, make sure that your client certificates (and **only** your client certificates) are included in your PEM. For example:
|
80
|
+
|
81
|
+
```
|
82
|
+
$ openssl pkcs12 -in pki.p12 -clcerts -out pki.pem
|
83
|
+
```
|
data/Rakefile
ADDED
data/exe/mypki
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#! /usr/bin/env ruby
|
2
|
+
require 'mypki'
|
3
|
+
require 'trollop'
|
4
|
+
|
5
|
+
args = []
|
6
|
+
|
7
|
+
while arg = ARGV.first
|
8
|
+
if arg == '--'
|
9
|
+
ARGV.shift
|
10
|
+
break
|
11
|
+
elsif arg.start_with? '-'
|
12
|
+
args << ARGV.shift
|
13
|
+
else
|
14
|
+
break
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
options = Trollop::options args do
|
19
|
+
version MyPKI::VERSION
|
20
|
+
banner "MyPKI #{MyPKI::VERSION}: PKI-enables Ruby's OpenSSL libraries, which PKI-enables most libraries and gems written in Ruby.\n\nUSAGE:"
|
21
|
+
opt :ssh, "Also configure ssh to use your PKI"
|
22
|
+
opt :init, "Don't lazy-load, immediately prompting for a password if needed"
|
23
|
+
opt :no_verify, "Don't verify the certificate", depends: [:reconfigure]
|
24
|
+
opt :reconfigure, "Configure MyPKI, deleting any existing configuration"
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:reconfigure]
|
28
|
+
MyPKI.init reconfigure: true, no_verify: options[:no_verify]
|
29
|
+
end
|
30
|
+
|
31
|
+
if options[:init]
|
32
|
+
MyPKI.init
|
33
|
+
end
|
34
|
+
|
35
|
+
ARGV.shift args.size
|
36
|
+
|
37
|
+
if ARGV.empty?
|
38
|
+
Trollop::die "you must specify a program or option"
|
39
|
+
end
|
40
|
+
|
41
|
+
executable = ARGV.shift
|
42
|
+
|
43
|
+
path = if File.file?(executable) && File.executable?(executable)
|
44
|
+
executable
|
45
|
+
elsif ENV['PATH']
|
46
|
+
path = ENV['PATH'].split(File::PATH_SEPARATOR).find do |p|
|
47
|
+
abs_path = File.join(p, executable)
|
48
|
+
File.file?(abs_path) && File.executable?(abs_path)
|
49
|
+
end
|
50
|
+
path && File.expand_path(executable, path)
|
51
|
+
end
|
52
|
+
|
53
|
+
if path.nil?
|
54
|
+
raise "Could not find #{executable}"
|
55
|
+
else
|
56
|
+
exec "/usr/bin/env ruby -r mypki #{path} #{ARGV.join(' ')}"
|
57
|
+
end
|
data/lib/mypki.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'net/http/persistent'
|
2
|
+
|
3
|
+
class Net::HTTP::Persistent
|
4
|
+
alias :old_initialize :initialize
|
5
|
+
|
6
|
+
def initialize *args
|
7
|
+
old_initialize *args
|
8
|
+
context = MyPKI::Context.new
|
9
|
+
self.private_key = context.key
|
10
|
+
self.certificate = context.cert
|
11
|
+
self.verify_mode = context.verify_mode
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'mypki/core'
|
2
|
+
require 'httpclient'
|
3
|
+
|
4
|
+
class HTTPClient
|
5
|
+
class SSLSocketWrap
|
6
|
+
def create_openssl_socket socket
|
7
|
+
context = MyPKI::Context.new
|
8
|
+
@context.set_context(context)
|
9
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, context)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'mypki/core'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
class Net::HTTP
|
5
|
+
alias :old_initialize :initialize
|
6
|
+
|
7
|
+
def initialize *args, &block
|
8
|
+
old_initialize *args, &block
|
9
|
+
context = MyPKI::Context.new
|
10
|
+
|
11
|
+
SSL_ATTRIBUTES.each do |method|
|
12
|
+
if [context,self].all? {|o| o.respond_to? method}
|
13
|
+
if value = context.send(method)
|
14
|
+
send "#{method}=", value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
|
3
|
+
module MyPKI
|
4
|
+
class Configuration < Hash
|
5
|
+
include Prompter
|
6
|
+
attr_reader :options
|
7
|
+
|
8
|
+
def initialize path, **options
|
9
|
+
@options = options
|
10
|
+
@path = File.expand_path path
|
11
|
+
@loaders = loaders.map{|loader| loader.new options}
|
12
|
+
|
13
|
+
if File.exist? @path
|
14
|
+
begin
|
15
|
+
merge! MultiJson.load(File.read(@path))
|
16
|
+
@loaders.each {|l| l.load self}
|
17
|
+
rescue => ex
|
18
|
+
raise "Bad MyPKI configuration (#{ex.message})"
|
19
|
+
end
|
20
|
+
else
|
21
|
+
retriable on_retry: proc { warn $! } do
|
22
|
+
clear
|
23
|
+
create
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def create
|
29
|
+
pki = file_prompt 'Path to your PKI: '
|
30
|
+
|
31
|
+
@loaders.each do |loader|
|
32
|
+
loader.configure self, pki
|
33
|
+
loader.load self unless options[:no_verify]
|
34
|
+
end
|
35
|
+
|
36
|
+
if Instance.cert.nil? and Instance.key.nil? and not options[:no_verify]
|
37
|
+
fail 'Unknown PKI type - add an extension or try another file'
|
38
|
+
end
|
39
|
+
|
40
|
+
if File.writable? File.dirname(@path)
|
41
|
+
File.write @path, MultiJson.dump(Hash[keys.zip(values)], :pretty => true)
|
42
|
+
else
|
43
|
+
warn "warning: #{File.dirname(@path)} is not writable! MyPKI will not save a configuration."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class << self
|
48
|
+
attr_accessor :loaders
|
49
|
+
end
|
50
|
+
|
51
|
+
def loaders
|
52
|
+
self.class.loaders ||= []
|
53
|
+
end
|
54
|
+
|
55
|
+
module Loader
|
56
|
+
# options passed to MyPKI.init
|
57
|
+
attr_reader :options
|
58
|
+
|
59
|
+
def initialize options
|
60
|
+
@options = options
|
61
|
+
end
|
62
|
+
|
63
|
+
# once a config is established, it is passed to each loader to load
|
64
|
+
# whatever they need
|
65
|
+
def load config
|
66
|
+
end
|
67
|
+
|
68
|
+
# runs during configuration, before anything has been loaded,
|
69
|
+
# to give loaders a chance to insert anything they need into the config
|
70
|
+
def configure config, path
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.included klass
|
74
|
+
(Configuration.loaders ||= []) << klass
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/mypki/core.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'metaid'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
require 'mypki/version'
|
6
|
+
require 'mypki/prompter'
|
7
|
+
require 'mypki/configuration'
|
8
|
+
|
9
|
+
require 'mypki/loaders/p12'
|
10
|
+
require 'mypki/loaders/pem'
|
11
|
+
require 'mypki/loaders/ca'
|
12
|
+
require 'mypki/loaders/ssh'
|
13
|
+
|
14
|
+
module MyPKI
|
15
|
+
Instance = OpenSSL::SSL::SSLContext.new
|
16
|
+
|
17
|
+
Instance.instance_eval do
|
18
|
+
@verify_mode = OpenSSL::SSL::VERIFY_NONE
|
19
|
+
@immutable_attributes = [:verify_mode].to_set
|
20
|
+
|
21
|
+
meta_eval do
|
22
|
+
(instance_methods - methods).each do |method|
|
23
|
+
if method['=']
|
24
|
+
getter, setter = method[0..-2].to_sym, method
|
25
|
+
alias_method :"original_#{setter}", setter
|
26
|
+
|
27
|
+
# keep track of set attributes
|
28
|
+
define_method setter do |*args, &block|
|
29
|
+
@immutable_attributes << getter
|
30
|
+
send :"original_#{setter}", *args, &block
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Context
|
38
|
+
def self.new **options
|
39
|
+
if Instance.key.nil? or Instance.cert.nil?
|
40
|
+
Configuration.new ENV['MYPKI_CONFIG']||'~/.mypki', **options
|
41
|
+
end
|
42
|
+
|
43
|
+
context = Instance.dup
|
44
|
+
|
45
|
+
context.instance_eval do
|
46
|
+
# make immutable attributes immutable
|
47
|
+
@immutable_attributes.each do |getter|
|
48
|
+
meta_def("#{getter}=") {|*a,&b| send getter}
|
49
|
+
end
|
50
|
+
|
51
|
+
# don't allow set_params to bypass setters
|
52
|
+
meta_def :set_params do |params|
|
53
|
+
params.each {|k,v| send "#{k}=", v}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module_function
|
62
|
+
|
63
|
+
def init reconfigure: false, **options
|
64
|
+
if reconfigure
|
65
|
+
path = File.expand_path(ENV['MYPKI_CONFIG'] || '~/.mypki')
|
66
|
+
File.delete path if File.exist? path
|
67
|
+
end
|
68
|
+
|
69
|
+
Context.new **options; true
|
70
|
+
end
|
71
|
+
|
72
|
+
def dn
|
73
|
+
init
|
74
|
+
Instance.cert.subject.to_s
|
75
|
+
end
|
76
|
+
end
|
data/lib/mypki/jruby.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
java_import 'javax.crypto.JceSecurity'
|
2
|
+
|
3
|
+
# disable SNI since our servers don't support them
|
4
|
+
java.lang.System.set_property 'jsse.enableSNIExtension', 'false'
|
5
|
+
|
6
|
+
# we must remove cryptographic restrictions to communicate
|
7
|
+
# with our servers
|
8
|
+
begin
|
9
|
+
isRestricted = JceSecurity.java_class.declared_field 'isRestricted'
|
10
|
+
isRestricted.accessible = true
|
11
|
+
isRestricted.set_value nil, false
|
12
|
+
rescue => ex
|
13
|
+
warn "Could not remove cryptographic restrictions: #{ex.message}"
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'retriable/core_ext/kernel'
|
2
|
+
|
3
|
+
module MyPKI
|
4
|
+
class CA
|
5
|
+
include Prompter
|
6
|
+
include Configuration::Loader
|
7
|
+
|
8
|
+
DEFAULT_PATH = '/etc/pki/tls/certs/ca-bundle.crt'
|
9
|
+
|
10
|
+
def configure config, path
|
11
|
+
if File.readable? DEFAULT_PATH
|
12
|
+
config['ca'] = DEFAULT_PATH
|
13
|
+
elsif config['ca'].nil?
|
14
|
+
prompt = "Path to CA chains (press enter to skip): "
|
15
|
+
path = file_prompt prompt, required: false
|
16
|
+
|
17
|
+
if path.nil?
|
18
|
+
config['ca'] = ''
|
19
|
+
else
|
20
|
+
if File.directory? path
|
21
|
+
fail "'#{path}' is a directory"
|
22
|
+
elsif not File.readable? path
|
23
|
+
fail "Cannot read '#{path}'"
|
24
|
+
else
|
25
|
+
config['ca'] = path
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def load config
|
32
|
+
unless config['ca'].empty?
|
33
|
+
Instance.cert_store = OpenSSL::X509::Store.new
|
34
|
+
Instance.cert_store.add_file config['ca']
|
35
|
+
Instance.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'retriable/core_ext/kernel'
|
2
|
+
|
3
|
+
module MyPKI
|
4
|
+
class P12
|
5
|
+
include Prompter
|
6
|
+
include Configuration::Loader
|
7
|
+
|
8
|
+
def configure config, path
|
9
|
+
if path.end_with? '.p12' or path.end_with? '.pfx'
|
10
|
+
config['p12'] = { 'path' => path }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def load config
|
15
|
+
if config['p12']
|
16
|
+
p12 = File.read config['p12']['path']
|
17
|
+
|
18
|
+
begin
|
19
|
+
retriable do
|
20
|
+
password = config['p12']['password']
|
21
|
+
password ||= pass_prompt 'Enter P12 pass phrase:'
|
22
|
+
pki = OpenSSL::PKCS12.new(p12, password)
|
23
|
+
Instance.key, Instance.cert = pki.key, pki.certificate
|
24
|
+
end
|
25
|
+
rescue OpenSSL::PKCS12::PKCS12Error
|
26
|
+
fail "Error: bad password"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'retriable/core_ext/kernel'
|
2
|
+
|
3
|
+
module MyPKI
|
4
|
+
class PEM
|
5
|
+
include Prompter
|
6
|
+
include Configuration::Loader
|
7
|
+
|
8
|
+
def configure config, path
|
9
|
+
if %w[pem id_rsa key crt cert].any? {|ext| path.end_with? ext}
|
10
|
+
contents = File.read path
|
11
|
+
config['pem'] = {}
|
12
|
+
has_cert = false
|
13
|
+
|
14
|
+
if contents['PRIVATE KEY']
|
15
|
+
config['pem']['path'] = path
|
16
|
+
end
|
17
|
+
|
18
|
+
if contents['BEGIN CERTIFICATE']
|
19
|
+
has_cert = true
|
20
|
+
end
|
21
|
+
|
22
|
+
if config['pem']['path']
|
23
|
+
unless has_cert
|
24
|
+
config['pem']['cert'] = file_prompt('Path to your certificate: ')
|
25
|
+
end
|
26
|
+
elsif has_cert
|
27
|
+
config['pem']['cert'] = path
|
28
|
+
config['pem']['path'] = file_prompt('Path to your private key: ')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def load config
|
34
|
+
if config['pem']
|
35
|
+
pem = File.read config['pem']['path']
|
36
|
+
cert = (config['pem']['cert'])? File.read(config['pem']['cert']) : pem
|
37
|
+
|
38
|
+
begin
|
39
|
+
Instance.cert = OpenSSL::X509::Certificate.new cert
|
40
|
+
rescue OpenSSL::X509::CertificateError
|
41
|
+
config['pem'] = {}
|
42
|
+
Instance.key = Instance.cert = nil
|
43
|
+
fail "No certificate found! Regenerate with --nocerts or provide a .key and .crt file separately."
|
44
|
+
end
|
45
|
+
|
46
|
+
begin
|
47
|
+
retriable do
|
48
|
+
if pem['ENCRYPTED']
|
49
|
+
password = pass_prompt('PEM Passphrase:')
|
50
|
+
Instance.key = OpenSSL::PKey::RSA.new pem, password
|
51
|
+
else
|
52
|
+
Instance.key = OpenSSL::PKey::RSA.new pem
|
53
|
+
end
|
54
|
+
end
|
55
|
+
rescue OpenSSL::PKey::RSAError
|
56
|
+
fail "Error: bad password"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module MyPKI
|
5
|
+
class SSH
|
6
|
+
include Prompter
|
7
|
+
include Configuration::Loader
|
8
|
+
|
9
|
+
def initialize options
|
10
|
+
super options
|
11
|
+
|
12
|
+
if options[:ssh]
|
13
|
+
Prompter.send :alias_method, :original, :pass_prompt
|
14
|
+
Prompter.send(:define_method, :pass_prompt) do |*a|
|
15
|
+
options[:password] = original *a
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def load config
|
21
|
+
if options[:ssh] and Instance.cert and Instance.key
|
22
|
+
ssh_path = File.expand_path '~/.ssh/'
|
23
|
+
FileUtils.mkdir ssh_path unless File.exist? ssh_path
|
24
|
+
FileUtils.chmod 0700, ssh_path
|
25
|
+
|
26
|
+
key_path = File.expand_path '~/.ssh/id_rsa'
|
27
|
+
pub_path = File.expand_path '~/.ssh/id_rsa.pub'
|
28
|
+
authorized_keys = File.expand_path '~/.ssh/authorized_keys'
|
29
|
+
|
30
|
+
if password = options.delete(:password)
|
31
|
+
pass = Shellwords.escape password
|
32
|
+
result = `echo "#{Instance.key.to_pem}" | \
|
33
|
+
openssl pkcs8 -topk8 -out #{key_path} -v2 des3 -passout pass:#{pass} 2>&1`
|
34
|
+
else
|
35
|
+
result = `echo "#{Instance.key.to_pem}" | \
|
36
|
+
openssl pkcs8 -topk8 -out #{key_path} -v2 des3 -nocrypt 2>&1`
|
37
|
+
end
|
38
|
+
|
39
|
+
fail result unless result.empty?
|
40
|
+
FileUtils.chmod 0600, key_path
|
41
|
+
|
42
|
+
if password
|
43
|
+
`ssh-keygen -f #{key_path} -y -P #{pass} > #{pub_path}`
|
44
|
+
else
|
45
|
+
`ssh-keygen -f #{key_path} -y > #{pub_path}`
|
46
|
+
end
|
47
|
+
|
48
|
+
FileUtils.cp pub_path, authorized_keys
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'retriable/core_ext/kernel'
|
2
|
+
|
3
|
+
module MyPKI
|
4
|
+
module Prompter
|
5
|
+
def prompter
|
6
|
+
if defined? IRuby and defined? IRuby::VERSION
|
7
|
+
require 'mypki/prompters/iruby'
|
8
|
+
@prompter = IRubyPrompter.new
|
9
|
+
elsif RUBY_PLATFORM == 'java'
|
10
|
+
require 'mypki/prompters/jruby'
|
11
|
+
@prompter = JRubyPrompter.new
|
12
|
+
else
|
13
|
+
require 'mypki/prompters/cli'
|
14
|
+
@prompter = CommandLinePrompter.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def file_prompt prompt, required: true
|
19
|
+
file = prompter.file_prompt(prompt)
|
20
|
+
|
21
|
+
if file.nil? or file.empty?
|
22
|
+
required ? fail('cancelled') : nil
|
23
|
+
else
|
24
|
+
expanded = File.expand_path file.strip
|
25
|
+
|
26
|
+
if File.directory? expanded
|
27
|
+
fail "'#{expanded}' is a directory"
|
28
|
+
end
|
29
|
+
|
30
|
+
unless File.readable? expanded
|
31
|
+
fail "Cannot read '#{expanded}'"
|
32
|
+
end
|
33
|
+
|
34
|
+
expanded
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def pass_prompt prompt
|
39
|
+
pass = prompter.pass_prompt(prompt)
|
40
|
+
pass.strip if pass
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module MyPKI
|
2
|
+
module Prompter
|
3
|
+
class CommandLinePrompter
|
4
|
+
def file_prompt prompt
|
5
|
+
require 'readline'
|
6
|
+
Readline.completion_append_character = ''
|
7
|
+
|
8
|
+
Readline.completion_proc = proc do |key|
|
9
|
+
path = File.expand_path key
|
10
|
+
path += '/' if key.end_with? '/'
|
11
|
+
Dir["#{path}*"]
|
12
|
+
end
|
13
|
+
|
14
|
+
Readline.readline(prompt, true)
|
15
|
+
end
|
16
|
+
|
17
|
+
def pass_prompt prompt
|
18
|
+
require 'highline/import'
|
19
|
+
ask("#{prompt} ") {|q| q.echo = false}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'shellwords'
|
2
|
+
|
3
|
+
module MyPKI
|
4
|
+
module Prompter
|
5
|
+
class IRubyPrompter
|
6
|
+
def file_prompt prompt
|
7
|
+
|
8
|
+
if prompt['PKI']
|
9
|
+
title = 'Configure MyPKI'
|
10
|
+
prompt = 'Your script requires access to a PKI-enabled resource. Please select the PKI certificate you would like to use to access that resource.'
|
11
|
+
label = :certificate
|
12
|
+
|
13
|
+
elsif prompt['certificate']
|
14
|
+
title = 'Missing Certificate'
|
15
|
+
prompt = "You're missing a certificate. If you have a certificate to go with your key, upload it below. Press Cancel to start over."
|
16
|
+
label = :certificate
|
17
|
+
|
18
|
+
elsif prompt['private key']
|
19
|
+
title = 'Missing Private Key'
|
20
|
+
prompt = "You're missing a private key. If you have a private key to go with your certificate, upload it below. Press Cancel to start over."
|
21
|
+
label = :private_key
|
22
|
+
|
23
|
+
elsif prompt['CA']
|
24
|
+
title = 'Add Trust Chains (Optional)'
|
25
|
+
prompt = 'Please select the trust chain you would like to use to authenticate servers. Hit cancel if you do not want to verify servers.'
|
26
|
+
label = :chains
|
27
|
+
|
28
|
+
else
|
29
|
+
abort "IRuby integration is missing a corresponding graphical prompt for '#{prompt}'"
|
30
|
+
end
|
31
|
+
|
32
|
+
iruby_file_prompt title, prompt, label
|
33
|
+
end
|
34
|
+
|
35
|
+
def pass_prompt prompt
|
36
|
+
response = IRuby.popup 'PKI Password' do
|
37
|
+
password
|
38
|
+
cancel
|
39
|
+
button
|
40
|
+
end
|
41
|
+
response[:password] if response
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def iruby_file_prompt title, prompt, label
|
47
|
+
response = IRuby.popup title do
|
48
|
+
text prompt
|
49
|
+
html { br; br }
|
50
|
+
file label
|
51
|
+
cancel
|
52
|
+
button
|
53
|
+
end
|
54
|
+
|
55
|
+
if response && response[label]
|
56
|
+
filename = File.join(Dir.home, ".#{response[label][:name]}")
|
57
|
+
File.write(filename, response[label][:data])
|
58
|
+
|
59
|
+
if RUBY_PLATFORM['cygwin']
|
60
|
+
win_path = `cygpath -d #{Shellwords.escape(filename)}`.strip
|
61
|
+
`ATTRIB +H #{win_path.gsub("\\", "\\\\")}`
|
62
|
+
end
|
63
|
+
|
64
|
+
filename
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'mypki/prompters/readline'
|
2
|
+
require 'mypki/jruby/jline-2.12.1.jar'
|
3
|
+
java_import 'jline.console.ConsoleReader'
|
4
|
+
|
5
|
+
module MyPKI
|
6
|
+
module Prompter
|
7
|
+
class JRubyPrompter < ReadlinePrompter
|
8
|
+
def pass_prompt prompt
|
9
|
+
ConsoleReader.new.read_line prompt, ' '.ord
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/mypki.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mypki/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "mypki"
|
8
|
+
spec.version = MyPKI::VERSION
|
9
|
+
spec.authors = ["Kyle King"]
|
10
|
+
spec.email = ["kylejking@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{PKI-enable Ruby}
|
13
|
+
spec.description = %q{PKI-enables Ruby's OpenSSL libraries, which PKI-enables most libraries and gems written in Ruby.}
|
14
|
+
spec.homepage = "https://github.com/jupyter-gallery/mypki"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'multi_json'
|
22
|
+
spec.add_dependency 'trollop'
|
23
|
+
spec.add_dependency 'metaid'
|
24
|
+
spec.add_dependency 'highline'
|
25
|
+
spec.add_dependency 'retriable'
|
26
|
+
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
spec.add_development_dependency "bundler"
|
29
|
+
spec.add_development_dependency "rake"
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mypki
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 4.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kyle King
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-05-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: multi_json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: trollop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: metaid
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: highline
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: retriable
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: bundler
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
description: PKI-enables Ruby's OpenSSL libraries, which PKI-enables most libraries
|
126
|
+
and gems written in Ruby.
|
127
|
+
email:
|
128
|
+
- kylejking@gmail.com
|
129
|
+
executables:
|
130
|
+
- mypki
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- ".gitignore"
|
135
|
+
- Gemfile
|
136
|
+
- LICENSE
|
137
|
+
- README.md
|
138
|
+
- Rakefile
|
139
|
+
- exe/mypki
|
140
|
+
- lib/mypki.rb
|
141
|
+
- lib/mypki/adapters/http_persistent.rb
|
142
|
+
- lib/mypki/adapters/httpclient.rb
|
143
|
+
- lib/mypki/adapters/net_http.rb
|
144
|
+
- lib/mypki/adapters/openssl.rb
|
145
|
+
- lib/mypki/configuration.rb
|
146
|
+
- lib/mypki/core.rb
|
147
|
+
- lib/mypki/jruby.rb
|
148
|
+
- lib/mypki/loaders/ca.rb
|
149
|
+
- lib/mypki/loaders/p12.rb
|
150
|
+
- lib/mypki/loaders/pem.rb
|
151
|
+
- lib/mypki/loaders/ssh.rb
|
152
|
+
- lib/mypki/prompter.rb
|
153
|
+
- lib/mypki/prompters/cli.rb
|
154
|
+
- lib/mypki/prompters/iruby.rb
|
155
|
+
- lib/mypki/prompters/jruby.rb
|
156
|
+
- lib/mypki/version.rb
|
157
|
+
- mypki.gemspec
|
158
|
+
homepage: https://github.com/jupyter-gallery/mypki
|
159
|
+
licenses: []
|
160
|
+
metadata: {}
|
161
|
+
post_install_message:
|
162
|
+
rdoc_options: []
|
163
|
+
require_paths:
|
164
|
+
- lib
|
165
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: '0'
|
170
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
|
+
requirements:
|
172
|
+
- - ">="
|
173
|
+
- !ruby/object:Gem::Version
|
174
|
+
version: '0'
|
175
|
+
requirements: []
|
176
|
+
rubyforge_project:
|
177
|
+
rubygems_version: 2.4.5.1
|
178
|
+
signing_key:
|
179
|
+
specification_version: 4
|
180
|
+
summary: PKI-enable Ruby
|
181
|
+
test_files: []
|