ruby-tls 1.0.3 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +100 -71
- data/lib/ruby-tls.rb +5 -7
- data/lib/ruby-tls/ssl.rb +582 -0
- data/lib/ruby-tls/version.rb +3 -3
- data/ruby-tls.gemspec +30 -32
- data/spec/client.crt +31 -31
- data/spec/client.key +51 -51
- data/spec/comms_spec.rb +110 -156
- data/spec/verify_spec.rb +155 -120
- metadata +37 -47
- data/EM-LICENSE +0 -60
- data/Rakefile +0 -19
- data/ext/Rakefile +0 -18
- data/ext/tls/page.cpp +0 -102
- data/ext/tls/page.h +0 -61
- data/ext/tls/ssl.cpp +0 -594
- data/ext/tls/ssl.h +0 -130
- data/lib/ruby-tls/connection.rb +0 -124
- data/lib/ruby-tls/ext.rb +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bc9634a3d7fd408edb071d71b01d231173cc73a
|
4
|
+
data.tar.gz: 4594c48be9ef0577869259c2d6113fd23a996c8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13c78e049178f73860d056c76900feb1aa9ab5f814db1d7e52566ff4761b1b1f3440a43683cb12ece6ef740b4193be5b0803ca9ce9c61fedb14eef8f3bcec06f
|
7
|
+
data.tar.gz: f9bb0ab94cc497721fe5fbf4354ccbac997d9249dc13539b1bc09114b0aecfb02ca597093ea67b8ddcda726ab81bb4a7cc6bee7396f0a1c4d719435155335588
|
data/README.md
CHANGED
@@ -1,71 +1,100 @@
|
|
1
|
-
# ruby-tls
|
2
|
-
|
3
|
-
Ruby-TLS decouples the management of encrypted communications, putting you in charge of the transport layer. It can be used as an alternative to Ruby's SSLSocket.
|
4
|
-
|
5
|
-
[![Build Status](https://travis-ci.org/cotag/ruby-tls.png?branch=master)](https://travis-ci.org/cotag/ruby-tls)
|
6
|
-
|
7
|
-
|
8
|
-
## Install the gem
|
9
|
-
|
10
|
-
Install it with [RubyGems](https://rubygems.org/)
|
11
|
-
|
12
|
-
gem install ruby-tls
|
13
|
-
|
14
|
-
or add this to your Gemfile if you use [Bundler](http://gembundler.com/):
|
15
|
-
|
16
|
-
gem "ruby-tls"
|
17
|
-
|
18
|
-
|
19
|
-
Windows users will require an installation of OpenSSL (32bit or 64bit matching the Ruby installation)
|
20
|
-
|
21
|
-
|
22
|
-
## Usage
|
23
|
-
|
24
|
-
```ruby
|
25
|
-
require 'rubygems'
|
26
|
-
require 'ruby-tls'
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
#
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
1
|
+
# ruby-tls
|
2
|
+
|
3
|
+
Ruby-TLS decouples the management of encrypted communications, putting you in charge of the transport layer. It can be used as an alternative to Ruby's SSLSocket.
|
4
|
+
|
5
|
+
[![Build Status](https://travis-ci.org/cotag/ruby-tls.png?branch=master)](https://travis-ci.org/cotag/ruby-tls)
|
6
|
+
|
7
|
+
|
8
|
+
## Install the gem
|
9
|
+
|
10
|
+
Install it with [RubyGems](https://rubygems.org/)
|
11
|
+
|
12
|
+
gem install ruby-tls
|
13
|
+
|
14
|
+
or add this to your Gemfile if you use [Bundler](http://gembundler.com/):
|
15
|
+
|
16
|
+
gem "ruby-tls"
|
17
|
+
|
18
|
+
|
19
|
+
Windows users will require an installation of OpenSSL (32bit or 64bit matching the Ruby installation)
|
20
|
+
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require 'rubygems'
|
26
|
+
require 'ruby-tls'
|
27
|
+
|
28
|
+
class transport
|
29
|
+
def initialize
|
30
|
+
is_server = true
|
31
|
+
callback_obj = self
|
32
|
+
options = {
|
33
|
+
verify_peer: true,
|
34
|
+
private_key: '/file/path.pem',
|
35
|
+
cert_chain: '/file/path.crt',
|
36
|
+
ciphers: 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!CAMELLIA:@STRENGTH' # (default)
|
37
|
+
}
|
38
|
+
@ssl_layer = RubyTls::SSL::Box.new(is_server, callback_obj, options)
|
39
|
+
end
|
40
|
+
|
41
|
+
def close_cb
|
42
|
+
puts "The transport layer should be shutdown"
|
43
|
+
end
|
44
|
+
|
45
|
+
def dispatch_cb(data)
|
46
|
+
puts "Clear text data that has been decrypted"
|
47
|
+
end
|
48
|
+
|
49
|
+
def transmit_cb(data)
|
50
|
+
puts "Encrypted data for transmission to remote"
|
51
|
+
# @tcp.send data
|
52
|
+
end
|
53
|
+
|
54
|
+
def handshake_cb
|
55
|
+
puts "initial handshake has completed"
|
56
|
+
end
|
57
|
+
|
58
|
+
def verify_cb(cert)
|
59
|
+
# Return true or false
|
60
|
+
is_cert_valid? cert
|
61
|
+
end
|
62
|
+
|
63
|
+
def start_tls
|
64
|
+
# Start SSL negotiation when you are ready
|
65
|
+
@ssl_layer.start
|
66
|
+
end
|
67
|
+
|
68
|
+
def send(data)
|
69
|
+
@ssl_layer.encrypt(data)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Create a new TLS connection
|
75
|
+
#
|
76
|
+
connection = transport.new
|
77
|
+
|
78
|
+
#
|
79
|
+
# Init the handshake
|
80
|
+
#
|
81
|
+
connection.start_tls
|
82
|
+
|
83
|
+
#
|
84
|
+
# Start sending data to the remote, this will trigger the
|
85
|
+
# transmit_cb with encrypted data to send.
|
86
|
+
#
|
87
|
+
connection.send('client request')
|
88
|
+
|
89
|
+
#
|
90
|
+
# Similarly when data is received from the remote it should be
|
91
|
+
# passed to connection.decrypt where the dispatch_cb will be
|
92
|
+
# called with clear text
|
93
|
+
#
|
94
|
+
```
|
95
|
+
|
96
|
+
|
97
|
+
## License and copyright
|
98
|
+
|
99
|
+
MIT
|
100
|
+
|
data/lib/ruby-tls.rb
CHANGED
data/lib/ruby-tls/ssl.rb
ADDED
@@ -0,0 +1,582 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'ffi-compiler/loader'
|
3
|
+
require 'thread'
|
4
|
+
require 'thread_safe'
|
5
|
+
|
6
|
+
|
7
|
+
module RubyTls
|
8
|
+
module SSL
|
9
|
+
extend FFI::Library
|
10
|
+
if FFI::Platform.windows?
|
11
|
+
ffi_lib 'libeay32', 'ssleay32'
|
12
|
+
else
|
13
|
+
ffi_lib 'ssl'
|
14
|
+
end
|
15
|
+
|
16
|
+
attach_function :SSL_library_init, [], :int
|
17
|
+
attach_function :SSL_load_error_strings, [], :void
|
18
|
+
attach_function :ERR_load_crypto_strings, [], :void
|
19
|
+
|
20
|
+
|
21
|
+
# Common structures
|
22
|
+
typedef :pointer, :user_data
|
23
|
+
typedef :pointer, :bio
|
24
|
+
typedef :pointer, :evp_key
|
25
|
+
typedef :pointer, :evp_key_pointer
|
26
|
+
typedef :pointer, :x509
|
27
|
+
typedef :pointer, :x509_pointer
|
28
|
+
typedef :pointer, :ssl
|
29
|
+
typedef :pointer, :ssl_ctx
|
30
|
+
typedef :int, :buffer_length
|
31
|
+
typedef :int, :pass_length
|
32
|
+
typedef :int, :read_write_flag
|
33
|
+
|
34
|
+
|
35
|
+
# Multi-threaded support
|
36
|
+
callback :locking_cb, [:int, :int, :string, :int], :void
|
37
|
+
callback :thread_id_cb, [], :ulong
|
38
|
+
attach_function :CRYPTO_num_locks, [], :int
|
39
|
+
attach_function :CRYPTO_set_locking_callback, [:locking_cb], :void
|
40
|
+
attach_function :CRYPTO_set_id_callback, [:thread_id_cb], :void
|
41
|
+
|
42
|
+
|
43
|
+
# InitializeDefaultCredentials
|
44
|
+
attach_function :BIO_new_mem_buf, [:string, :buffer_length], :bio
|
45
|
+
attach_function :EVP_PKEY_free, [:evp_key], :void
|
46
|
+
|
47
|
+
callback :pem_password_cb, [:pointer, :buffer_length, :read_write_flag, :user_data], :pass_length
|
48
|
+
attach_function :PEM_read_bio_PrivateKey, [:bio, :evp_key_pointer, :pem_password_cb, :user_data], :evp_key
|
49
|
+
|
50
|
+
attach_function :X509_free, [:x509], :void
|
51
|
+
attach_function :PEM_read_bio_X509, [:bio, :x509_pointer, :pem_password_cb, :user_data], :x509
|
52
|
+
|
53
|
+
attach_function :BIO_free, [:bio], :int
|
54
|
+
|
55
|
+
# CONSTANTS
|
56
|
+
SSL_ST_OK = 0x03
|
57
|
+
attach_function :SSL_state, [:ssl], :int
|
58
|
+
def self.SSL_is_init_finished(ssl)
|
59
|
+
SSL_state(ssl) == SSL_ST_OK
|
60
|
+
end
|
61
|
+
|
62
|
+
# GetPeerCert
|
63
|
+
attach_function :SSL_get_peer_certificate, [:ssl], :x509
|
64
|
+
|
65
|
+
|
66
|
+
# PutPlaintext
|
67
|
+
attach_function :SSL_write, [:ssl, :buffer_in, :buffer_length], :int
|
68
|
+
attach_function :SSL_get_error, [:ssl, :int], :int
|
69
|
+
|
70
|
+
|
71
|
+
# GetCiphertext
|
72
|
+
attach_function :BIO_read, [:bio, :buffer_out, :buffer_length], :int
|
73
|
+
|
74
|
+
# CanGetCiphertext
|
75
|
+
attach_function :BIO_ctrl, [:bio, :int, :long, :pointer], :long
|
76
|
+
BIO_CTRL_PENDING = 10 # opt - is their more data buffered?
|
77
|
+
def self.BIO_pending(bio)
|
78
|
+
BIO_ctrl(bio, BIO_CTRL_PENDING, 0, nil)
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# GetPlaintext
|
83
|
+
attach_function :SSL_accept, [:ssl], :int
|
84
|
+
attach_function :SSL_read, [:ssl, :buffer_out, :buffer_length], :int
|
85
|
+
attach_function :SSL_pending, [:ssl], :int
|
86
|
+
|
87
|
+
# PutCiphertext
|
88
|
+
attach_function :BIO_write, [:bio, :buffer_in, :buffer_length], :int
|
89
|
+
|
90
|
+
# SelectALPNCallback
|
91
|
+
# TODO:: SSL_select_next_proto
|
92
|
+
|
93
|
+
# Deconstructor
|
94
|
+
attach_function :SSL_get_shutdown, [:ssl], :int
|
95
|
+
attach_function :SSL_shutdown, [:ssl], :int
|
96
|
+
attach_function :SSL_clear, [:ssl], :void
|
97
|
+
attach_function :SSL_free, [:ssl], :void
|
98
|
+
|
99
|
+
|
100
|
+
# Constructor
|
101
|
+
attach_function :BIO_s_mem, [], :pointer
|
102
|
+
attach_function :BIO_new, [:pointer], :bio
|
103
|
+
attach_function :SSL_new, [:ssl_ctx], :ssl
|
104
|
+
# r, w
|
105
|
+
attach_function :SSL_set_bio, [:ssl, :bio, :bio], :void
|
106
|
+
|
107
|
+
# TODO:: SSL_CTX_set_alpn_select_cb
|
108
|
+
# Will have to put a try catch around these and support when available
|
109
|
+
|
110
|
+
attach_function :SSL_set_ex_data, [:ssl, :int, :string], :int
|
111
|
+
callback :verify_callback, [:int, :x509], :int
|
112
|
+
attach_function :SSL_set_verify, [:ssl, :int, :verify_callback], :void
|
113
|
+
attach_function :SSL_connect, [:ssl], :int
|
114
|
+
|
115
|
+
# Verify callback
|
116
|
+
attach_function :X509_STORE_CTX_get_current_cert, [:pointer], :x509
|
117
|
+
attach_function :SSL_get_ex_data_X509_STORE_CTX_idx, [], :int
|
118
|
+
attach_function :X509_STORE_CTX_get_ex_data, [:pointer, :int], :ssl
|
119
|
+
attach_function :PEM_write_bio_X509, [:bio, :x509], :int
|
120
|
+
|
121
|
+
|
122
|
+
# SSL Context Class
|
123
|
+
# Constructor
|
124
|
+
attach_function :SSLv23_server_method, [], :pointer
|
125
|
+
attach_function :SSLv23_client_method, [], :pointer
|
126
|
+
attach_function :SSL_CTX_new, [:pointer], :ssl_ctx
|
127
|
+
|
128
|
+
attach_function :SSL_CTX_ctrl, [:ssl_ctx, :int, :ulong, :pointer], :long
|
129
|
+
SSL_CTRL_OPTIONS = 32
|
130
|
+
def self.SSL_CTX_set_options(ssl_ctx, op)
|
131
|
+
SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_OPTIONS, op, nil)
|
132
|
+
end
|
133
|
+
SSL_CTRL_MODE = 33
|
134
|
+
def self.SSL_CTX_set_mode(ssl_ctx, op)
|
135
|
+
SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_MODE, op, nil)
|
136
|
+
end
|
137
|
+
SSL_CTRL_SET_SESS_CACHE_SIZE = 42
|
138
|
+
def self.SSL_CTX_sess_set_cache_size(ssl_ctx, op)
|
139
|
+
SSL_CTX_ctrl(ssl_ctx, SSL_CTRL_SET_SESS_CACHE_SIZE, op, nil)
|
140
|
+
end
|
141
|
+
|
142
|
+
attach_function :SSL_CTX_use_PrivateKey_file, [:ssl_ctx, :string, :int], :int
|
143
|
+
attach_function :SSL_CTX_use_PrivateKey, [:ssl_ctx, :pointer], :int
|
144
|
+
attach_function :ERR_print_errors_fp, [:pointer], :void # Pointer == File Handle
|
145
|
+
attach_function :SSL_CTX_use_certificate_chain_file, [:ssl_ctx, :string], :int
|
146
|
+
attach_function :SSL_CTX_use_certificate, [:ssl_ctx, :x509], :int
|
147
|
+
attach_function :SSL_CTX_set_cipher_list, [:ssl_ctx, :string], :int
|
148
|
+
attach_function :SSL_CTX_set_session_id_context, [:ssl_ctx, :string, :buffer_length], :int
|
149
|
+
|
150
|
+
# TODO:: SSL_CTX_set_alpn_protos
|
151
|
+
|
152
|
+
|
153
|
+
# Deconstructor
|
154
|
+
attach_function :SSL_CTX_free, [:ssl_ctx], :void
|
155
|
+
|
156
|
+
|
157
|
+
PrivateMaterials = <<-keystr
|
158
|
+
-----BEGIN RSA PRIVATE KEY-----
|
159
|
+
MIICXAIBAAKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxwVDWV
|
160
|
+
Igdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t39hJ/
|
161
|
+
AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQIDAQAB
|
162
|
+
AoGALA89gIFcr6BIBo8N5fL3aNHpZXjAICtGav+kTUpuxSiaym9cAeTHuAVv8Xgk
|
163
|
+
H2Wbq11uz+6JMLpkQJH/WZ7EV59DPOicXrp0Imr73F3EXBfR7t2EQDYHPMthOA1D
|
164
|
+
I9EtCzvV608Ze90hiJ7E3guGrGppZfJ+eUWCPgy8CZH1vRECQQDv67rwV/oU1aDo
|
165
|
+
6/+d5nqjeW6mWkGqTnUU96jXap8EIw6B+0cUKskwx6mHJv+tEMM2748ZY7b0yBlg
|
166
|
+
w4KDghbFAkEAz2h8PjSJG55LwqmXih1RONSgdN9hjB12LwXL1CaDh7/lkEhq0PlK
|
167
|
+
PCAUwQSdM17Sl0Xxm2CZiekTSlwmHrtqXQJAF3+8QJwtV2sRJp8u2zVe37IeH1cJ
|
168
|
+
xXeHyjTzqZ2803fnjN2iuZvzNr7noOA1/Kp+pFvUZUU5/0G2Ep8zolPUjQJAFA7k
|
169
|
+
xRdLkzIx3XeNQjwnmLlncyYPRv+qaE3FMpUu7zftuZBnVCJnvXzUxP3vPgKTlzGa
|
170
|
+
dg5XivDRfsV+okY5uQJBAMV4FesUuLQVEKb6lMs7rzZwpeGQhFDRfywJzfom2TLn
|
171
|
+
2RdJQQ3dcgnhdVDgt5o1qkmsqQh8uJrJ9SdyLIaZQIc=
|
172
|
+
-----END RSA PRIVATE KEY-----
|
173
|
+
-----BEGIN CERTIFICATE-----
|
174
|
+
MIID6TCCA1KgAwIBAgIJANm4W/Tzs+s+MA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD
|
175
|
+
VQQGEwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYw
|
176
|
+
FAYDVQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsG
|
177
|
+
A1UEAxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2lu
|
178
|
+
ZWVyaW5nQHN0ZWFtaGVhdC5uZXQwHhcNMDYwNTA1MTcwNjAzWhcNMjQwMjIwMTcw
|
179
|
+
NjAzWjCBqjELMAkGA1UEBhMCVVMxETAPBgNVBAgTCE5ldyBZb3JrMREwDwYDVQQH
|
180
|
+
EwhOZXcgWW9yazEWMBQGA1UEChMNU3RlYW1oZWF0Lm5ldDEUMBIGA1UECxMLRW5n
|
181
|
+
aW5lZXJpbmcxHTAbBgNVBAMTFG9wZW5jYS5zdGVhbWhlYXQubmV0MSgwJgYJKoZI
|
182
|
+
hvcNAQkBFhllbmdpbmVlcmluZ0BzdGVhbWhlYXQubmV0MIGfMA0GCSqGSIb3DQEB
|
183
|
+
AQUAA4GNADCBiQKBgQDCYYhcw6cGRbhBVShKmbWm7UVsEoBnUf0cCh8AX+MKhMxw
|
184
|
+
VDWVIgdskntn3cSJjRtmgVJHIK0lpb/FYHQB93Ohpd9/Z18pDmovfFF9nDbFF0t3
|
185
|
+
9hJ/AqSzFB3GiVPoFFZJEE1vJqh+3jzsSF5K56bZ6azz38VlZgXeSozNW5bXkQID
|
186
|
+
AQABo4IBEzCCAQ8wHQYDVR0OBBYEFPJvPd1Fcmd8o/Tm88r+NjYPICCkMIHfBgNV
|
187
|
+
HSMEgdcwgdSAFPJvPd1Fcmd8o/Tm88r+NjYPICCkoYGwpIGtMIGqMQswCQYDVQQG
|
188
|
+
EwJVUzERMA8GA1UECBMITmV3IFlvcmsxETAPBgNVBAcTCE5ldyBZb3JrMRYwFAYD
|
189
|
+
VQQKEw1TdGVhbWhlYXQubmV0MRQwEgYDVQQLEwtFbmdpbmVlcmluZzEdMBsGA1UE
|
190
|
+
AxMUb3BlbmNhLnN0ZWFtaGVhdC5uZXQxKDAmBgkqhkiG9w0BCQEWGWVuZ2luZWVy
|
191
|
+
aW5nQHN0ZWFtaGVhdC5uZXSCCQDZuFv087PrPjAMBgNVHRMEBTADAQH/MA0GCSqG
|
192
|
+
SIb3DQEBBQUAA4GBAC1CXey/4UoLgJiwcEMDxOvW74plks23090iziFIlGgcIhk0
|
193
|
+
Df6hTAs7H3MWww62ddvR8l07AWfSzSP5L6mDsbvq7EmQsmPODwb6C+i2aF3EDL8j
|
194
|
+
uw73m4YIGI0Zw2XdBpiOGkx2H56Kya6mJJe/5XORZedh1wpI7zki01tHYbcy
|
195
|
+
-----END CERTIFICATE-----
|
196
|
+
keystr
|
197
|
+
|
198
|
+
|
199
|
+
BuiltinPasswdCB = FFI::Function.new(:int, [:pointer, :int, :int, :pointer]) do |buffer, len, flag, data|
|
200
|
+
buffer.write_string('kittycat')
|
201
|
+
8
|
202
|
+
end
|
203
|
+
|
204
|
+
CRYPTO_LOCK = 0x1
|
205
|
+
LockingCB = FFI::Function.new(:void, [:int, :int, :string, :int]) do |mode, type, file, line|
|
206
|
+
if (mode & CRYPTO_LOCK) != 0
|
207
|
+
SSL_LOCKS[type].lock
|
208
|
+
else
|
209
|
+
# Unlock a lock
|
210
|
+
SSL_LOCKS[type].unlock
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
ThreadIdCB = FFI::Function.new(:ulong, []) do
|
215
|
+
Thread.current.object_id
|
216
|
+
end
|
217
|
+
|
218
|
+
|
219
|
+
# INIT CODE
|
220
|
+
@init_required ||= false
|
221
|
+
unless @init_required
|
222
|
+
self.SSL_load_error_strings
|
223
|
+
self.SSL_library_init
|
224
|
+
self.ERR_load_crypto_strings
|
225
|
+
|
226
|
+
|
227
|
+
# Setup multi-threaded support
|
228
|
+
SSL_LOCKS = []
|
229
|
+
num_locks = self.CRYPTO_num_locks
|
230
|
+
num_locks.times { SSL_LOCKS << Mutex.new }
|
231
|
+
|
232
|
+
self.CRYPTO_set_locking_callback(LockingCB)
|
233
|
+
self.CRYPTO_set_id_callback(ThreadIdCB)
|
234
|
+
|
235
|
+
|
236
|
+
bio = self.BIO_new_mem_buf(PrivateMaterials, PrivateMaterials.bytesize)
|
237
|
+
|
238
|
+
# Get the private key structure
|
239
|
+
pointer = FFI::MemoryPointer.new(:pointer)
|
240
|
+
self.PEM_read_bio_PrivateKey(bio, pointer, BuiltinPasswdCB, nil)
|
241
|
+
DEFAULT_PRIVATE = pointer.get_pointer(0)
|
242
|
+
|
243
|
+
# Get the certificate structure
|
244
|
+
pointer = FFI::MemoryPointer.new(:pointer)
|
245
|
+
self.PEM_read_bio_X509(bio, pointer, nil, nil)
|
246
|
+
DEFAULT_CERT = pointer.get_pointer(0)
|
247
|
+
|
248
|
+
self.BIO_free(bio)
|
249
|
+
|
250
|
+
@init_required = true
|
251
|
+
end
|
252
|
+
|
253
|
+
|
254
|
+
|
255
|
+
|
256
|
+
# Save RAM by releasing read and write buffers when they're empty
|
257
|
+
SSL_MODE_RELEASE_BUFFERS = 0x00000010
|
258
|
+
SSL_OP_ALL = 0x80000BFF
|
259
|
+
SSL_FILETYPE_PEM = 1
|
260
|
+
|
261
|
+
class Context
|
262
|
+
CIPHERS = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-RC4-SHA:ECDHE-RSA-AES128-SHA:AES128-GCM-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!CAMELLIA:@STRENGTH'.freeze
|
263
|
+
SESSION = 'ruby-tls'.freeze
|
264
|
+
|
265
|
+
def initialize(server, options = {})
|
266
|
+
@is_server = server
|
267
|
+
@ssl_ctx = SSL.SSL_CTX_new(server ? SSL.SSLv23_server_method : SSL.SSLv23_client_method)
|
268
|
+
SSL.SSL_CTX_set_options(@ssl_ctx, SSL::SSL_OP_ALL)
|
269
|
+
SSL.SSL_CTX_set_mode(@ssl_ctx, SSL::SSL_MODE_RELEASE_BUFFERS)
|
270
|
+
|
271
|
+
if @is_server
|
272
|
+
set_private_key(options[:private_key] || SSL::DEFAULT_PRIVATE)
|
273
|
+
set_certificate(options[:cert_chain] || SSL::DEFAULT_CERT)
|
274
|
+
end
|
275
|
+
|
276
|
+
SSL.SSL_CTX_set_cipher_list(@ssl_ctx, options[:ciphers] || CIPHERS)
|
277
|
+
|
278
|
+
if @is_server
|
279
|
+
SSL.SSL_CTX_sess_set_cache_size(@ssl_ctx, 128)
|
280
|
+
SSL.SSL_CTX_set_session_id_context(@ssl_ctx, SESSION, 8)
|
281
|
+
else
|
282
|
+
set_private_key(options[:private_key])
|
283
|
+
set_certificate(options[:cert_chain])
|
284
|
+
end
|
285
|
+
|
286
|
+
# TODO:: Check for ALPN support
|
287
|
+
end
|
288
|
+
|
289
|
+
def cleanup
|
290
|
+
if @ssl_ctx
|
291
|
+
SSL.SSL_CTX_free(@ssl_ctx)
|
292
|
+
@ssl_ctx = nil
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
attr_reader :is_server
|
297
|
+
attr_reader :ssl_ctx
|
298
|
+
|
299
|
+
|
300
|
+
private
|
301
|
+
|
302
|
+
|
303
|
+
def set_private_key(key)
|
304
|
+
err = if key.is_a? FFI::Pointer
|
305
|
+
SSL.SSL_CTX_use_PrivateKey(@ssl_ctx, key)
|
306
|
+
elsif key && File.file?(key)
|
307
|
+
SSL.SSL_CTX_use_PrivateKey_file(@ssl_ctx, key, SSL_FILETYPE_PEM)
|
308
|
+
else
|
309
|
+
1
|
310
|
+
end
|
311
|
+
|
312
|
+
# Check for errors
|
313
|
+
if err <= 0
|
314
|
+
# TODO:: ERR_print_errors_fp or ERR_print_errors
|
315
|
+
# So we can properly log the issue
|
316
|
+
cleanup
|
317
|
+
raise 'invalid private key or file not found'
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def set_certificate(cert)
|
322
|
+
err = if cert.is_a? FFI::Pointer
|
323
|
+
SSL.SSL_CTX_use_certificate(@ssl_ctx, cert)
|
324
|
+
elsif cert && File.file?(cert)
|
325
|
+
SSL.SSL_CTX_use_certificate_chain_file(@ssl_ctx, cert)
|
326
|
+
else
|
327
|
+
1
|
328
|
+
end
|
329
|
+
|
330
|
+
if err <= 0
|
331
|
+
cleanup
|
332
|
+
raise 'invalid certificate or file not found'
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
|
339
|
+
|
340
|
+
class Box
|
341
|
+
READ_BUFFER = 2048
|
342
|
+
|
343
|
+
SSL_VERIFY_PEER = 0x01
|
344
|
+
SSL_VERIFY_CLIENT_ONCE = 0x04
|
345
|
+
def initialize(server, transport, options = {})
|
346
|
+
@ready = true
|
347
|
+
|
348
|
+
@handshake_completed = false
|
349
|
+
@handshake_signaled = false
|
350
|
+
@transport = transport
|
351
|
+
|
352
|
+
@read_buffer = FFI::MemoryPointer.new(:char, READ_BUFFER, false)
|
353
|
+
|
354
|
+
@is_server = server
|
355
|
+
@context = Context.new(server, options)
|
356
|
+
@bioRead = SSL.BIO_new(SSL.BIO_s_mem)
|
357
|
+
@bioWrite = SSL.BIO_new(SSL.BIO_s_mem)
|
358
|
+
@ssl = SSL.SSL_new(@context.ssl_ctx)
|
359
|
+
SSL.SSL_set_bio(@ssl, @bioRead, @bioWrite)
|
360
|
+
|
361
|
+
@write_queue = []
|
362
|
+
|
363
|
+
# TODO:: if server && options[:alpn_string]
|
364
|
+
# SSL_CTX_set_alpn_select_cb
|
365
|
+
|
366
|
+
InstanceLookup[@ssl.address] = self
|
367
|
+
|
368
|
+
if options[:verify_peer]
|
369
|
+
SSL.SSL_set_verify(@ssl, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, VerifyCB)
|
370
|
+
end
|
371
|
+
|
372
|
+
SSL.SSL_connect(@ssl) unless server
|
373
|
+
end
|
374
|
+
|
375
|
+
|
376
|
+
attr_reader :is_server
|
377
|
+
attr_reader :handshake_completed
|
378
|
+
|
379
|
+
|
380
|
+
def get_peer_cert
|
381
|
+
return '' unless @ready
|
382
|
+
SSL.SSL_get_peer_certificate(@ssl)
|
383
|
+
end
|
384
|
+
|
385
|
+
def start
|
386
|
+
return unless @ready
|
387
|
+
|
388
|
+
dispatch_cipher_text
|
389
|
+
end
|
390
|
+
|
391
|
+
def encrypt(data)
|
392
|
+
return unless @ready
|
393
|
+
|
394
|
+
wrote = put_plain_text data
|
395
|
+
if wrote < 0
|
396
|
+
@transport.close_cb
|
397
|
+
else
|
398
|
+
dispatch_cipher_text
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
SSL_ERROR_WANT_READ = 2
|
403
|
+
SSL_ERROR_SSL = 1
|
404
|
+
def decrypt(data)
|
405
|
+
return unless @ready
|
406
|
+
|
407
|
+
put_cipher_text data
|
408
|
+
|
409
|
+
if not SSL.SSL_is_init_finished(@ssl)
|
410
|
+
resp = @is_server ? SSL.SSL_accept(@ssl) : SSL.SSL_connect(@ssl)
|
411
|
+
|
412
|
+
if resp < 0
|
413
|
+
err_code = SSL.SSL_get_error(@ssl, resp)
|
414
|
+
if err_code != SSL_ERROR_WANT_READ
|
415
|
+
@transport.close_cb if err_code == SSL_ERROR_SSL
|
416
|
+
return
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
@handshake_completed = true
|
421
|
+
signal_handshake unless @handshake_signaled
|
422
|
+
end
|
423
|
+
|
424
|
+
while true do
|
425
|
+
size = get_plain_text(@read_buffer, READ_BUFFER)
|
426
|
+
if size > 0
|
427
|
+
@transport.dispatch_cb @read_buffer.read_string(size)
|
428
|
+
else
|
429
|
+
break
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
dispatch_cipher_text
|
434
|
+
end
|
435
|
+
|
436
|
+
def signal_handshake
|
437
|
+
@handshake_signaled = true
|
438
|
+
@transport.handshake_cb
|
439
|
+
end
|
440
|
+
|
441
|
+
SSL_RECEIVED_SHUTDOWN = 2
|
442
|
+
def cleanup
|
443
|
+
@ready = false
|
444
|
+
|
445
|
+
InstanceLookup.delete @ssl.address
|
446
|
+
|
447
|
+
if (SSL.SSL_get_shutdown(@ssl) & SSL_RECEIVED_SHUTDOWN) != 0
|
448
|
+
SSL.SSL_shutdown @ssl
|
449
|
+
else
|
450
|
+
SSL.SSL_clear @ssl
|
451
|
+
end
|
452
|
+
|
453
|
+
SSL.SSL_free @ssl
|
454
|
+
|
455
|
+
@context.cleanup
|
456
|
+
end
|
457
|
+
|
458
|
+
# Called from class level callback function
|
459
|
+
def verify(cert)
|
460
|
+
@transport.verify_cb(cert) == true ? 1 : 0
|
461
|
+
end
|
462
|
+
|
463
|
+
|
464
|
+
private
|
465
|
+
|
466
|
+
|
467
|
+
def get_plain_text(buffer, ready)
|
468
|
+
# Read the buffered clear text
|
469
|
+
size = SSL.SSL_read(@ssl, buffer, ready)
|
470
|
+
if size >= 0
|
471
|
+
size
|
472
|
+
else
|
473
|
+
SSL.SSL_get_error(@ssl, size) == SSL_ERROR_WANT_READ ? 0 : -1
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
|
478
|
+
InstanceLookup = ThreadSafe::Cache.new
|
479
|
+
VerifyCB = FFI::Function.new(:int, [:int, :pointer]) do |preverify_ok, x509_store|
|
480
|
+
x509 = SSL.X509_STORE_CTX_get_current_cert(x509_store)
|
481
|
+
ssl = SSL.X509_STORE_CTX_get_ex_data(x509_store, SSL.SSL_get_ex_data_X509_STORE_CTX_idx)
|
482
|
+
|
483
|
+
bio_out = SSL.BIO_new(SSL.BIO_s_mem)
|
484
|
+
SSL.PEM_write_bio_X509(bio_out, x509)
|
485
|
+
|
486
|
+
len = SSL.BIO_pending(bio_out)
|
487
|
+
buffer = FFI::MemoryPointer.new(:char, len, false)
|
488
|
+
size = SSL.BIO_read(bio_out, buffer, len)
|
489
|
+
|
490
|
+
# THis is the callback into the ruby class
|
491
|
+
result = InstanceLookup[ssl.address].verify(buffer.read_string(size))
|
492
|
+
|
493
|
+
SSL.BIO_free(bio_out)
|
494
|
+
result
|
495
|
+
end
|
496
|
+
|
497
|
+
|
498
|
+
def pending_data(bio)
|
499
|
+
SSL.BIO_pending(bio)
|
500
|
+
end
|
501
|
+
|
502
|
+
def get_cipher_text(buffer, length)
|
503
|
+
SSL.BIO_read(@bioWrite, buffer, length)
|
504
|
+
end
|
505
|
+
|
506
|
+
def put_cipher_text(data)
|
507
|
+
len = data.bytesize
|
508
|
+
wrote = SSL.BIO_write(@bioRead, data, len)
|
509
|
+
wrote == len
|
510
|
+
end
|
511
|
+
|
512
|
+
|
513
|
+
SSL_ERROR_WANT_WRITE = 3
|
514
|
+
def put_plain_text(data)
|
515
|
+
@write_queue.push(data) if data
|
516
|
+
return 0 unless SSL.SSL_is_init_finished(@ssl)
|
517
|
+
|
518
|
+
fatal = false
|
519
|
+
did_work = false
|
520
|
+
|
521
|
+
while !@write_queue.empty? do
|
522
|
+
data = @write_queue.pop
|
523
|
+
len = data.bytesize
|
524
|
+
|
525
|
+
wrote = SSL.SSL_write(@ssl, data, len)
|
526
|
+
|
527
|
+
if wrote > 0
|
528
|
+
did_work = true;
|
529
|
+
else
|
530
|
+
err_code = SSL.SSL_get_error(@ssl, wrote)
|
531
|
+
if (err_code != SSL_ERROR_WANT_READ) && (err_code != SSL_ERROR_WANT_WRITE)
|
532
|
+
fatal = true
|
533
|
+
else
|
534
|
+
# Not fatal - add back to the queue
|
535
|
+
@write_queue.unshift data
|
536
|
+
end
|
537
|
+
|
538
|
+
break
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
if did_work
|
543
|
+
1
|
544
|
+
elsif fatal
|
545
|
+
-1
|
546
|
+
else
|
547
|
+
0
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
|
552
|
+
CIPHER_DISPATCH_FAILED = 'Cipher text dispatch failed'.freeze
|
553
|
+
def dispatch_cipher_text
|
554
|
+
begin
|
555
|
+
did_work = false
|
556
|
+
|
557
|
+
# Get all the encrypted data and transmit it
|
558
|
+
pending = pending_data(@bioWrite)
|
559
|
+
if pending > 0
|
560
|
+
buffer = FFI::MemoryPointer.new(:char, pending, false)
|
561
|
+
|
562
|
+
resp = get_cipher_text(buffer, pending)
|
563
|
+
raise CIPHER_DISPATCH_FAILED unless resp > 0
|
564
|
+
|
565
|
+
@transport.transmit_cb(buffer.read_string(resp))
|
566
|
+
did_work = true
|
567
|
+
end
|
568
|
+
|
569
|
+
# Send any queued out going data
|
570
|
+
unless @write_queue.empty?
|
571
|
+
resp = put_plain_text nil
|
572
|
+
if resp > 0
|
573
|
+
did_work = true
|
574
|
+
elsif resp < 0
|
575
|
+
@transport.close_cb
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end while did_work
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|