ruby-tls 1.0.3 → 2.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 +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
|
-
[](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
|
+
[](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
|