WiKID 3.0.2 → 3.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZWU4YjE2MzE5NmE3YmUyY2M0MWMyMWVlMjIwODcwOTBhN2FjZTA1NA==
5
+ data.tar.gz: !binary |-
6
+ OWI0MzkwZWJkY2E1NWNkMDM1OGY2ZTBiMDBkMTI3YWQ2ODRjOWQxMg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ OTIxMGE5ZmI1NDAyZWM4MDBmMDU0NGY2ZjZhMDBiYjQ5MThlOGM2OTNmNzUz
10
+ ZmIwNTdiYWVlNDBiNmEzZjkwZDgzZDZjOGJhNzRlNjczMjVjMjJjYjI1ODFm
11
+ NWZiYmI3ZmJlNGZlNzYzZWQ1NmVhYTkzNzUwOWZiODhlYWRmMGI=
12
+ data.tar.gz: !binary |-
13
+ ZmU0ODBhNjUzMTU4NDllMWYwNmVlYTg5ODJhMzFjMDA3N2U5Yjk2MmU1MWM0
14
+ MWM1MGFlNTBmNTkyYmUyNmE3OTU4ZTdkYTZjOWYzMjAyZjA5ZWRkZDU1Mzcw
15
+ MmRkNjM1NDhlYzZjMjc3Zjc5NzE4YWJlMDAyODZhY2YzYjhlMWE=
data/doc/README CHANGED
@@ -46,7 +46,7 @@ NETWORK CLIENT SETUP
46
46
  Every WiKID network client needs a certificate from the WiKID server to talk
47
47
  via SSL with the WiKID server. Create a network client on the WiKID server for
48
48
  your Ruby network client and download the network client PKCS12 certificate to
49
- your Ruby server.
49
+ your Ruby server (accessible from the Network Client menu in the admin).
50
50
 
51
51
  To extract it from the .p12 file for use by Ruby, run:
52
52
 
@@ -1,591 +1,841 @@
1
- #!/usr/bin/env ruby
2
-
3
- # vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: :nodoc:
4
-
5
- module WiKID
6
-
7
- =begin rdoc
8
-
9
- == Title
10
-
11
- WiKID Strong Authentication module for Ruby
12
-
13
- http://sourceforge.net/projects/wikid-twofactor/
14
-
15
- == Synopsis
16
-
17
- This is the core SSL client for WiKID Authentication. Auth_WiKID manages
18
- communication between Network Clients (NC) and the WiKID Authentication
19
- Server (wAuth).
20
-
21
- == License
22
- Lesser GNU Public License
23
-
24
- This library is free software; you can redistribute it and/or
25
- modify it under the terms of the GNU Lesser General Public
26
- License as published by the Free Software Foundation; either
27
- version 2.1 of the License, or (at your option) any later version.
28
-
29
- This library is distributed in the hope that it will be useful,
30
- but WITHOUT ANY WARRANTY; without even the implied warranty of
31
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
32
- Lesser General Public License for more details.
33
-
34
- You should have received a copy of the GNU Lesser General Public
35
- License along with this library; if not, write to the Free Software
36
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
37
-
38
- == Author
39
- Greg Haygood <ghaygood@wikidsystems.com>
40
-
41
- == Copyright
42
- Copyright (c) 2001-2005 WiKID Systems, Inc. All rights reserved.
43
-
44
- == Version
45
- CVS: Id: WiKID.rb,v 1.0 2005/09/22 05:32:19 ghaygood Exp
46
-
47
- =end
48
-
49
- ## Not necessarily true, but only tested with 1.8.x
50
- raise "Please, use ruby 1.8.0 or later." if RUBY_VERSION < "1.8.0"
51
-
52
- require 'socket'
53
- require "rexml/document"
54
- include REXML
55
-
56
- SSLEnabled = begin
57
- require 'openssl'
58
- true
59
- rescue LoadError
60
- false
61
- end
62
-
63
- class Auth
64
-
65
- private
66
-
67
- =begin rdoc
68
-
69
- Idle time to allow before closing socket, and time limit on socket
70
- * open attempt
71
-
72
- =end
73
- @@timeout = 30
74
-
75
- =begin rdoc
76
-
77
- Controls whether debug messages will be printed
78
-
79
- =end
80
- @@DEBUG = false
81
-
82
- @@VERSION = "3.0.1"
83
-
84
- public
85
-
86
- =begin rdoc
87
-
88
- This constructor allows the Auth_WiKID module to be initialized from
89
- either a properties file or via explicit arguments.
90
-
91
- @param string host_or_file Either the IP address or hostname of
92
- the wAuth server, or the path to a
93
- properties file
94
- @param int port The SSL listener port for the wAuth
95
- daemon on the wAuth server
96
- @param string keyfile The PKCS12 keystore generated for this
97
- client by the wAuth server
98
- @param string pass The passphrase securing the keys in keyfile
99
- @param string cafile The certificate authority store for
100
- validating the wAuth server certificate
101
-
102
- The contents of the propertiesfile should contain the following
103
- key-value pairs:
104
- <ul>
105
- <li> host - The IP address or hostname of the wAuth server
106
- <li> port - The SSL listener port for the wAuth daemon on the
107
- wAuth server
108
- <li> keyfile - The PKCS12 keystore generated for client by
109
- the wAuth server
110
- <li> pass - The passphrase securing the keys in keyfile
111
- <li> cafile - The PEM-encoded certificate file for validating the wAuth
112
- server certificate
113
- </ul>
114
-
115
- =end
116
- def initialize(host_or_file, port, keyfile, keypass, cafile = '')
117
-
118
- unless SSLEnabled
119
- raise RuntimeError.new("Ruby/OpenSSL module is required for WiKID authentication.")
120
- end
121
-
122
- if (cafile.empty?)
123
- cafile = File.expand_path(File.join(File.dirname(__FILE__), "..", "share", "data", "WiKID-ca.pem"))
124
- end
125
-
126
- if (File.exist?(host_or_file))
127
- # props = parse_ini_file(host_or_file)
128
- props = Hash.new
129
-
130
- @host = props['host']
131
- @port = props['port']
132
- @keyfile = props['keyfile']
133
- @keypass = props['pass']
134
- @cafile = props['cafile']
135
- else
136
- @host = host_or_file.untaint
137
- @port = port.untaint
138
- @keyfile = keyfile.untaint
139
- @keypass = keypass.untaint
140
- unless (cafile.nil? || cafile.empty?)
141
- @cafile = cafile.untaint
142
- end
143
- end
144
- if (!@port.is_a?(Integer))
145
- @port = 0
146
- end
147
-
148
- _dprint("WiKID.rb initialized: host=#{@host}, port=#{@port}, keyfile=#{@keyfile}, cafile=#{@cafile}")
149
-
150
- ## simple hack to allow for testing during gem installation (prevents security errors since keys may not yet be available)
151
- unless port == -1
152
- checkKeys()
153
- end
154
-
155
- return true
156
- end
157
-
158
- =begin rdoc
159
-
160
- Class destructor, which just calls close().
161
-
162
-
163
- =end
164
- def _WiKID()
165
- close()
166
- end
167
-
168
- =begin rdoc
169
-
170
- This method simply closes the connection to the wAuth.
171
-
172
-
173
- =end
174
- def close()
175
- _dprint("Closing Auth_WiKID connection ...")
176
- unless $sslsocket.nil?
177
- unless $sslsocket.closed?
178
- $sslsocket.puts("QUIT");
179
- $sslsocket.flush
180
- $sslsocket.close
181
- end
182
- $sslsocket = nil
183
- @socket.shutdown
184
- end
185
- @isConnected = false
186
- end
187
-
188
- =begin rdoc
189
-
190
- This method checks that the certificates are readable.
191
-
192
-
193
- =end
194
- def checkKeys()
195
-
196
- data = nil
197
- if (@cafile.nil? || @cafile.empty? || !File.exists?(@cafile) || OpenSSL::X509::Certificate.new(File.read(@cafile)).nil?)
198
- raise SecurityError, "CA Public key NOT OK!"
199
- else
200
- _dprint("CA Public Key OK")
201
- end
202
-
203
- if (@keyfile.nil? || @keyfile.empty? || !File.exists?(@keyfile) || OpenSSL::X509::Certificate.new(File.read(@keyfile)).nil?)
204
- raise SecurityError, "Public key NOT OK!"
205
- else
206
- _dprint("Public Key OK")
207
- end
208
-
209
- if (!File.exists?(@keyfile) || OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass).nil?)
210
- raise SecurityError, "Private key NOT OK!"
211
- else
212
- _dprint("Private Key OK")
213
- end
214
-
215
- end
216
-
217
- =begin rdoc
218
-
219
- @param string mesg The message to send to the server
220
-
221
- @return string response The response from the server
222
-
223
- =end
224
-
225
- def _request(mesg)
226
- mesg.gsub!(/\n/, '')
227
- _dprint("send.request is: #{mesg.inspect}")
228
- _dprint("---------------------------------")
229
- $sslsocket.puts(mesg)
230
- $sslsocket.flush
231
-
232
- _dprint("checking response...")
233
- response = $sslsocket.gets.chomp!
234
- _dprint("send.response is: #{response.inspect}")
235
- unless response.nil?
236
- xml = Document.new response
237
- _dprint(xml.inspect);
238
- else
239
- _dprint('No response received.');
240
- xml = nil
241
- end
242
- return xml;
243
- end
244
-
245
- =begin rdoc
246
-
247
- =end
248
- def _ping()
249
- mesg = '<transaction> <type>1</type> <data> <value>TX</value> </data> </transaction>';
250
- xml = _request(mesg);
251
- end
252
-
253
- =begin rdoc
254
- This method initiates the connection to the wAuth server.
255
-
256
- @return boolean Whether the socket is connected
257
- =end
258
- def _startConnection()
259
- _dprint("startConnection() called.");
260
- valid_tag = "ACCEPT";
261
- # The client initiates the transaction
262
- mesg = "CONNECT: WiKID Ruby Client v#{@@VERSION}"
263
- mesg = "<transaction> <type>1</type> <data> <client-string>wClient Ruby #{@@VERSION}</client-string> <server-string>null</server-string> <result>null</result> </data> </transaction>
264
- "
265
-
266
- xml = _request(mesg);
267
- result = XPath.first(xml, '//data/result')
268
- if result == "ACCEPT"
269
- _dprint("wClient connection ACCEPTED")
270
- @isConnected = true
271
- else
272
- _dprint("wClient connection FAILED")
273
- @isConnected = false
274
- end
275
- return @isConnected
276
- end
277
-
278
- =begin rdoc
279
-
280
- This method reconnects to the wAuth server, if the socket handle is dead.
281
-
282
- @return boolean Whether the socket is connected
283
-
284
- =end
285
- def reconnect()
286
-
287
- _dprint("reconnect() called.")
288
-
289
- begin
290
-
291
- if ($sslsocket.nil? || $sslsocket.closed?)
292
- _dprint("Socket inactive. Reconnecting...")
293
-
294
- _dprint("Setting up SSL context ...")
295
- ctx = OpenSSL::SSL::SSLContext.new()
296
-
297
- # Options:
298
- # "cert", "key", "client_ca", "ca_file", "ca_path",
299
- # "timeout", "verify_mode", "verify_depth",
300
- # "verify_callback", "options", "cert_store", "extra_chain_cert"
301
-
302
- ctx.cert = OpenSSL::X509::Certificate.new(File.read(@keyfile))
303
- ctx.key = OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass)
304
-
305
- ctx.ca_file = @cafile
306
- ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
307
- #ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
308
- #ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
309
- ctx.timeout = @@timeout
310
-
311
- if ctx.cert.nil?
312
- warn "warning: peer certificate won't be verified this session."
313
- ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
314
- end
315
-
316
- _dprint("Opening socket to #{@host}:#{@port}...")
317
- @socket = TCPSocket.open(@host, @port)
318
- @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, @@timeout)
319
- @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, @@timeout)
320
-
321
- $sslsocket = OpenSSL::SSL::SSLSocket.new(@socket, ctx)
322
- #$sslsocket.sync_close = true
323
-
324
- # $sslsocket should be good now
325
- _dprint("Connecting SSL socket ...")
326
- $sslsocket.connect
327
-
328
- _startConnection()
329
-
330
- end
331
-
332
- if block_given?
333
- _dprint("Connecting SSL socket in block ...")
334
- $sslsocket.connect if $sslsocket.closed?
335
- yield
336
- _dprint("SSL connection block finished.")
337
- else
338
- _dprint("SSL connection wanting to do something else ...")
339
- # do something non-OO
340
- end
341
- rescue Exception => ex
342
- warn "Error reading from server: #{ex}"
343
- end
344
-
345
- end
346
-
347
- =begin rdoc
348
-
349
- Is the socket connected?
350
-
351
- @return boolean Status of handle: true indicates connection is active
352
-
353
- =end
354
- def isConnected()
355
- return @isConnected
356
- end
357
-
358
- =begin rdoc
359
-
360
- Creates an association between the userid and the device registered
361
- by the user.
362
-
363
- @param string username Users login ID in this authentication domain
364
- @param string regcode Registration code provided to user when
365
- setting up this domain on users device
366
- @param string domaincode 12 digit code representing this
367
- authentication domain
368
- @param string passcode Optional passcode provided by the user, to
369
- link this device to an existing registration
370
- @return int Result code from the registration attempt
371
-
372
-
373
- =end
374
- def registerUsername(username, regcode, domaincode, passcode = '')
375
-
376
- _dprint("registerUsername() called ...")
377
- valid_tag = "REGUSER:SUCESS"
378
-
379
- if (!passcode.nil? && passcode.length > 0)
380
- _dprint("Adding new device ...")
381
- command = "ADDREGUSER"
382
- type = 4;
383
- passcodeline = "<passcode>#{passcode}</passcode>";
384
- format = "add";
385
- else
386
- _dprint("Registering user ...")
387
- command = "REGUSER"
388
- type = 4;
389
- passcodeline = "";
390
- format = "new";
391
- end
392
-
393
- mesg = "#{command}:#{uname}\t#{regcode}\t#{domaincode}\t#{passcode}"
394
- mesg = <<XML
395
- <transaction>
396
- <type format="#{format}">#{type}</type>
397
- <data>
398
- <user-id>#{username}</user-id>
399
- <registration-code>#{regcode}</registration-code>
400
- <domaincode>#{domaincode}</domaincode>
401
- #{passcodeline}
402
- <error-code>null</error-code>
403
- <result>null</result>
404
- </data>
405
- </transaction>
406
- XML
407
-
408
- reconnect {
409
-
410
- _dprint("registerUsername() sending '#{mesg}' ...")
411
-
412
- xml = _request(mesg)
413
- response = XPath.first(xml, '//data/result')
414
- _dprint("response: '#{response}'")
415
- if response =~ /SUCC?ESS/
416
- _dprint("Registered!")
417
- return 0
418
- else
419
- err = XPath.first(xml, '//data/error-code')
420
- _dprint("Failed to register! Error: #{err}")
421
- return err
422
- end
423
- }
424
-
425
- end
426
-
427
- =begin rdoc
428
-
429
- Verifies credentials generated using the online mechanism.
430
-
431
- @param string username Users login ID in this authentication domain
432
- @param string passcode Passcode provided by the user
433
- @param string domaincode 12 digit code representing the
434
- authentication domain
435
- @return boolean 'true' indicates credentials were valid,
436
- 'false' if credentials were invalid or
437
- an error occurred
438
-
439
- =end
440
- def checkCredentials(username, passcode, domaincode = '127000000001')
441
-
442
- _dprint("checkCredentials(#{username}, #{passcode}, #{domaincode}) called ...")
443
-
444
- validCredentials = false
445
- offline_challenge = ''
446
- offline_response = ''
447
- chap_password = ''
448
- chap_challenge = ''
449
- valid_tag = "VERIFY:VALID"
450
-
451
- _dprint("Checking Credentials...")
452
-
453
- mesg = "VERIFY:" + username + "\t" + passcode + "\t" + domaincode
454
- mesg = <<XML
455
- <transaction>
456
- <type format="base">2</type>
457
- <data>
458
- <user-id>#{username}</user-id>
459
- <passcode>#{passcode}</passcode>
460
- <domaincode>#{domaincode}</domaincode>
461
- <offline-challenge encoding="none">#{offline_challenge}</offline-challenge>
462
- <offline-response encoding="none">#{offline_response}</offline-response>
463
- <chap-password encoding="none">#{chap_password}</chap-password>
464
- <chap-challenge encoding="none">#{chap_challenge}</chap-challenge>
465
- <result>null</result>
466
- </data>
467
- </transaction>
468
- XML
469
-
470
- reconnect {
471
-
472
- xml = _request(mesg)
473
- response = XPath.first(xml, '//data/result')
474
-
475
- if response =~ /VALID/
476
- validCredentials = true
477
- else
478
- validCredentials = false
479
- end
480
- _dprint("Read response: verdict = " + validCredentials.to_s)
481
- }
482
-
483
- _dprint("Returning Results...")
484
- return validCredentials
485
- end
486
-
487
- =begin rdoc
488
-
489
- Verifies the credentials via challenge-response.
490
-
491
- <b>!!! Not currently supported by the Open Source release of WiKID.</b>
492
-
493
- @ignore
494
- @return boolean 'true' indicates credentials were valid,
495
- 'false' if credentials were invalid or
496
- an error occurred
497
-
498
- =end
499
- def chapVerify(username, domaincode, wikidChallenge = '', chapPassword = '', chapChallenge = '')
500
-
501
- _dprint("chapVerify() called ...")
502
- reconnect()
503
- validCredentials = false
504
- valid_tag = "VERIFY:VALID"
505
- _dprint("Checking Chap Credentials")
506
-
507
- mesg = "CHAPOFFVERIFY:" + username + "\t" + "nil" + "\t" + domaincode + "\t" + wikidChallenge
508
-
509
- reconnect {
510
-
511
- $sslsocket.puts(chapPassword.length)
512
- $sslsocket.puts(chapPassword)
513
- $sslsocket.puts(chapChallenge.length)
514
- $sslsocket.puts(chapChallenge.length)
515
- $sslsocket.flush
516
-
517
- _dprint("Reading in...")
518
-
519
- inputLine = $sslsocket.gets.chomp!
520
- if (inputLine[0, valid_tag.length] == valid_tag)
521
- validCredentials = true
522
- end
523
- }
524
-
525
- return validCredentials
526
- end
527
-
528
- =begin rdoc
529
-
530
- Fetches a list of domains served by the currently connected server code.
531
-
532
- <b>!!! Not currently supported by the Open Source release of WiKID.</b>
533
-
534
-
535
- @ignore
536
- @return boolean 'true' indicates credentials were valid,
537
- 'false' if credentials were invalid or
538
- an error occurred
539
-
540
- =end
541
- def getDomains()
542
-
543
- _dprint("getDomains() called ...")
544
-
545
- valid_tag = "DOMAINLIST"
546
- _dprint("Getting Domains")
547
-
548
- mesg = <<XML
549
- <transaction>
550
- <type>3</type>
551
- <data>
552
- <domain-list>null</domain-list>
553
- </data>
554
- </transaction>
555
- XML
556
- reconnect {
557
- xml = _request(mesg)
558
- domains = XPath.match(xml, '//data/domain-list')
559
- }
560
- _dprint("Returning Results...")
561
- return domains
562
- end
563
-
564
- def setDebug(newval)
565
- @@DEBUG = (newval == true) ? true : false
566
- end
567
-
568
- =begin rdoc
569
-
570
- Prints a time-stamped (since the epoch) message if _@@DEBUG is true.
571
-
572
- @param string str Message to print out
573
-
574
- =end
575
- def _dprint(msg)
576
-
577
- if (@@DEBUG)
578
- show = Time.now.to_s + ': ' + msg
579
- show += '<br />' if !ENV['REQUEST_URI'].nil?
580
-
581
- puts show
582
- #STDERR.puts show
583
- #STDERR.flush()
584
- end
585
- return true
586
- end
587
-
588
- end
589
-
590
- end
591
-
1
+ #!/usr/bin/env ruby
2
+
3
+ ## vim: set expandtab tabstop=2 shiftwidth=2 softtabstop=2: :nodoc:
4
+
5
+ # @author WiKID Systems, Inc {info@wikidsystems.com}[mailto:info@wikidsystems.com]
6
+ # @author Twitter Handle: {@wikidsystems}[https://twitter.com/wikidsystems]
7
+ module WiKID
8
+
9
+
10
+ =begin rdoc
11
+
12
+ == Title
13
+
14
+ WiKID Strong Authentication module for Ruby
15
+
16
+ http://sourceforge.net/projects/wikid-twofactor/
17
+
18
+ == Synopsis
19
+
20
+ This is the core SSL client for WiKID Authentication. Auth_WiKID manages
21
+ communication between Network Clients (NC) and the WiKID Authentication
22
+ Server (wAuth).
23
+
24
+ == License
25
+ Lesser GNU Public License
26
+
27
+ This library is free software; you can redistribute it and/or
28
+ modify it under the terms of the GNU Lesser General Public
29
+ License as published by the Free Software Foundation; either
30
+ version 2.1 of the License, or (at your option) any later version.
31
+
32
+ This library is distributed in the hope that it will be useful,
33
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
34
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
35
+ Lesser General Public License for more details.
36
+
37
+ You should have received a copy of the GNU Lesser General Public
38
+ License along with this library; if not, write to the Free Software
39
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
40
+
41
+ == Author
42
+ Greg Haygood <ghaygood@wikidsystems.com>
43
+
44
+ == Copyright
45
+ Copyright (c) 2001-2015 WiKID Systems, Inc. All rights reserved.
46
+
47
+ =end
48
+
49
+ ## Not necessarily true, but only tested with 1.8.x
50
+ raise "Please, use ruby 1.8.0 or later." if RUBY_VERSION < "1.8.0"
51
+
52
+ require 'socket'
53
+ require 'rexml/document'
54
+ include REXML
55
+
56
+ SSLEnabled = begin
57
+ require 'openssl'
58
+ true
59
+ rescue LoadError
60
+ false
61
+ end
62
+ private_constant :SSLEnabled
63
+
64
+ class Auth
65
+
66
+ private
67
+
68
+ =begin rdoc
69
+
70
+ Idle time to allow before closing socket, and time limit on socket
71
+ * open attempt
72
+
73
+ =end
74
+ @@timeout = 30
75
+
76
+ =begin rdoc
77
+
78
+ Controls whether debug messages will be printed
79
+
80
+ =end
81
+ @@DEBUG = false
82
+
83
+ @@DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__), '..', 'share', 'data', 'WiKID-ca.pem'))
84
+
85
+ public
86
+
87
+ =begin rdoc
88
+
89
+ This constructor allows the Auth_WiKID module to be initialized from either a properties file or via explicit arguments.
90
+
91
+ @param [string] host_or_file Either the IP address or hostname of
92
+ the wAuth server, or the path to a
93
+ properties file
94
+ @param [int] port The SSL listener port for the wAuth
95
+ daemon on the wAuth server
96
+ @param [string] keyfile The PKCS12 keystore generated for this
97
+ client by the wAuth server
98
+ @param [string] keypass The passphrase securing the keys in keyfile
99
+ @param [string] cafile The certificate authority store for
100
+ validating the wAuth server certificate
101
+
102
+ The contents of the properties file should contain the following key-value pairs:
103
+ * host - The IP address or hostname of the wAuth server
104
+ * port - The SSL listener port for the wAuth daemon on the wAuth server
105
+ * keyfile - The PKCS12 keystore generated for client by the wAuth server
106
+ * pass - The passphrase securing the keys in keyfile
107
+ * cafile - The PEM-encoded certificate file for validating the wAuth server certificate
108
+
109
+ =end
110
+ def initialize(host_or_file, port, keyfile, keypass, cafile = @@DEFAULT_CA_FILE)
111
+
112
+ unless SSLEnabled
113
+ raise RuntimeError.new('Ruby/OpenSSL module is required for WiKID authentication.')
114
+ end
115
+
116
+ if (File.exist?(host_or_file))
117
+ # props = parse_ini_file(host_or_file)
118
+ props = Hash.new
119
+
120
+ @host = props['host']
121
+ @port = props['port']
122
+ @keyfile = props['keyfile']
123
+ @keypass = props['pass']
124
+ @cafile = props['cafile']
125
+ else
126
+ @host = host_or_file.untaint
127
+ @port = port.untaint
128
+ @keyfile = keyfile.untaint
129
+ @keypass = keypass.untaint
130
+ unless cafile.nil? || cafile.empty?
131
+ @cafile = cafile.untaint
132
+ end
133
+ end
134
+ if (!@port.is_a?(Integer))
135
+ @port = 0
136
+ end
137
+
138
+ _dprint("WiKID.rb initialized: host=#{@host}, port=#{@port}, keyfile=#{@keyfile}, cafile=#{@cafile}")
139
+
140
+ ## simple hack to allow for testing during gem installation (prevents security errors since keys may not yet be available)
141
+ unless port == -1
142
+ checkKeys()
143
+ end
144
+
145
+ return true
146
+ end
147
+
148
+ # @note Class destructor, which just calls close().
149
+ # @api private
150
+ def _WiKID()
151
+ close()
152
+ end
153
+ private :_WiKID
154
+
155
+ =begin rdoc
156
+
157
+ This method simply closes the connection to the wAuth service.
158
+
159
+ =end
160
+ def close()
161
+ _dprint('Closing Auth_WiKID connection ...')
162
+ unless $sslsocket.nil?
163
+ unless $sslsocket.closed?
164
+ $sslsocket.puts('QUIT');
165
+ $sslsocket.flush
166
+ $sslsocket.close
167
+ end
168
+ $sslsocket = nil
169
+ @socket.shutdown
170
+ end
171
+ @isConnected = false
172
+ end
173
+
174
+ =begin rdoc
175
+
176
+ This method checks that the certificates are readable and accessible.
177
+
178
+ =end
179
+ def checkKeys()
180
+
181
+ data = nil
182
+ if (@cafile.nil? || @cafile.empty? || !File.exists?(@cafile) || OpenSSL::X509::Certificate.new(File.read(@cafile)).nil?)
183
+ warn 'CA certificate NOT OK, running without peer verification'
184
+ else
185
+ _dprint('CA certificate OK')
186
+ end
187
+
188
+ if (@keyfile.nil? || @keyfile.empty? || !File.exists?(@keyfile) || OpenSSL::X509::Certificate.new(File.read(@keyfile)).nil?)
189
+ raise SecurityError, 'Public key NOT OK!'
190
+ else
191
+ _dprint('Public key OK')
192
+ end
193
+
194
+ if (!File.exists?(@keyfile) || OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass).nil?)
195
+ raise SecurityError, 'Private key NOT OK!'
196
+ else
197
+ _dprint('Private key OK')
198
+ end
199
+
200
+ end
201
+
202
+ =begin rdoc
203
+
204
+ Send the request and get the response back from the server.
205
+
206
+ @param [string] mesg The message to send to the server
207
+
208
+ @return [string] The response from the server
209
+
210
+ @api private
211
+ @private _request
212
+
213
+ =end
214
+ def _request(mesg)
215
+ mesg.gsub!(/\n/, '')
216
+ _dprint("send.request is: #{mesg.inspect}")
217
+ #puts "---------------------------------"
218
+ $sslsocket.puts(mesg)
219
+ $sslsocket.flush
220
+
221
+ _dprint("checking response...")
222
+ raw_response = $sslsocket.gets
223
+ _dprint("send.raw_response is: #{raw_response.inspect}")
224
+ response = raw_response.chomp unless raw_response.nil?
225
+ _dprint("send.response is: #{response.inspect}")
226
+ unless response.nil?
227
+ #puts "creating xml"
228
+ xml = Document.new response
229
+ #puts xml.inspect
230
+ else
231
+ #puts 'No response received.'
232
+ xml = nil
233
+ end
234
+ #puts "returning XML"
235
+ return xml
236
+ end
237
+ private :_request
238
+
239
+ =begin rdoc
240
+
241
+ Send a big ping transaction to the server to verify the connection is good.
242
+
243
+ @note This method should not be necessary in typical implementations, but is available nonetheless.
244
+
245
+ @return [string] the raw response from the server
246
+
247
+
248
+ =end
249
+ def ping()
250
+ mesg = '<transaction> <type>1</type> <data> <value>TX</value> </data> </transaction>'
251
+ xml = _request(mesg)
252
+ return xml
253
+ end
254
+
255
+ =begin rdoc
256
+
257
+ This method initiates the connection to the wAuth server.
258
+
259
+ @return [boolean] Whether the socket is connected
260
+ @api private
261
+ =end
262
+ def _startConnection()
263
+ _dprint("startConnection() called.")
264
+ valid_tag = "ACCEPT";
265
+ # The client initiates the transaction
266
+ mesg = "CONNECT: WiKID Ruby Client v#{VERSION}"
267
+ mesg = "<transaction> <type>1</type> <data> <client-string>wClient Ruby #{VERSION}</client-string> <server-string>null</server-string> <result>null</result> </data> </transaction>
268
+ "
269
+
270
+ xml = _request(mesg);
271
+ result = XPath.first(xml, '//data/result')
272
+ @isConnected = (result == 'ACCEPT')
273
+
274
+ return @isConnected
275
+ end
276
+ private :_startConnection
277
+
278
+ =begin rdoc
279
+
280
+ This method reconnects to the wAuth server, if the socket handle is dead.
281
+
282
+ @return [boolean] Whether the socket is connected
283
+
284
+ =end
285
+ def reconnect()
286
+
287
+ _dprint("reconnect() called.")
288
+
289
+ begin
290
+
291
+ if ($sslsocket.nil? || $sslsocket.closed?)
292
+ _dprint("Socket inactive. Reconnecting...")
293
+
294
+ #puts "Setting up SSL context ..."
295
+ ctx = OpenSSL::SSL::SSLContext.new()
296
+
297
+ # Options:
298
+ # "cert", "key", "client_ca", "ca_file", "ca_path",
299
+ # "timeout", "verify_mode", "verify_depth",
300
+ # "verify_callback", "options", "cert_store", "extra_chain_cert"
301
+
302
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(@keyfile))
303
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(@keyfile), @keypass)
304
+
305
+ if @cafile.nil? || @cafile.empty?
306
+ ctx.ca_file = nil # @cafile
307
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
308
+ else
309
+
310
+ ctx.ca_file = @cafile
311
+
312
+ # this next bit might be redundant?
313
+ ctx.cert_store = OpenSSL::X509::Store.new
314
+ ctx.cert_store.set_default_paths
315
+ ctx.cert_store.add_file(@cafile)
316
+
317
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER | OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT
318
+ end
319
+ # ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
320
+
321
+ ctx.timeout = @@timeout
322
+
323
+ if ctx.cert.nil?
324
+ _dprint("warning: peer certificate won't be verified this session.")
325
+ ctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
326
+ end
327
+
328
+ _dprint("Opening socket to #{@host}:#{@port}...")
329
+ @socket = TCPSocket.open(@host, @port)
330
+ _dprint("socket open")
331
+ #@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, @@timeout)
332
+
333
+ #@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, @@timeout)
334
+
335
+ $sslsocket = OpenSSL::SSL::SSLSocket.new(@socket, ctx)
336
+ _dprint("socket created")
337
+ #$sslsocket.sync_close = true
338
+
339
+ # $sslsocket should be good now
340
+ _dprint("Connecting SSL socket ...")
341
+ $sslsocket.connect
342
+
343
+ _startConnection()
344
+
345
+ end
346
+
347
+ if block_given?
348
+ #puts "Connecting SSL socket in block ..."
349
+ $sslsocket.connect if $sslsocket.closed?
350
+ yield
351
+ #puts "SSL connection block finished."
352
+ else
353
+ #puts "SSL connection wanting to do something else ..."
354
+ # do something non-OO
355
+ end
356
+ rescue Exception => ex
357
+ warn "Error reading from server: #{ex}"
358
+ end
359
+
360
+ end
361
+
362
+ =begin rdoc
363
+
364
+ Is the socket connected?
365
+
366
+ @return [boolean] Status of handle: true indicates connection is active
367
+
368
+ =end
369
+ def isConnected()
370
+ return @isConnected
371
+ end
372
+
373
+ =begin rdoc
374
+
375
+ Creates an association between the userid and the device registered
376
+ by the user.
377
+
378
+ @param [string] username Users login ID in this authentication domain
379
+ @param [string] regcode Registration code provided to user when
380
+ setting up this domain on users device
381
+ @param [string] domaincode 12 digit code representing this
382
+ authentication domain
383
+ @param [string] passcode Optional passcode provided by the user, to
384
+ link this device to an existing registration
385
+ @return [int] Result code from the registration attempt
386
+
387
+
388
+ =end
389
+
390
+ def registerUsername(username, regcode, domaincode, groupname = '', passcode = '')
391
+
392
+ _dprint('registerUsername() called ...')
393
+ valid_tag = 'REGUSER:SUCESS'
394
+
395
+ if (!passcode.nil? && passcode.length > 0)
396
+ _dprint('Adding new device ...')
397
+ command = 'ADDREGUSER'
398
+ type = 4;
399
+ passcodeline = "<passcode>#{passcode}</passcode>";
400
+ format = 'add';
401
+ else
402
+ _dprint('Registering user ...')
403
+ command = 'REGUSER'
404
+ type = 4;
405
+ passcodeline = '<passcode>null</passcode>';
406
+ format = 'new';
407
+ end
408
+
409
+ if (!groupname.nil? && groupname.length>0)
410
+ groupnameline="<groupName>#{groupname}</groupName>"
411
+ else
412
+ groupnameline='<groupName>null</groupName>'
413
+ end
414
+
415
+ #mesg = "#{command}:#{username}\t#{regcode}\t#{domaincode}\t#{passcode}"
416
+ mesg = <<XML
417
+ <transaction>
418
+ <type format="#{format}">#{type}</type>
419
+ <data>
420
+ <user-id>#{username}</user-id>
421
+ <registration-code>#{regcode}</registration-code>
422
+ <domaincode>#{domaincode}</domaincode>
423
+ #{passcodeline}
424
+ #{groupnameline}
425
+ <error-code>null</error-code>
426
+ <result>null</result>
427
+ </data>
428
+ </transaction>
429
+ XML
430
+
431
+ #puts mesg
432
+ reconnect {
433
+
434
+ _dprint("registerUsername() sending '#{mesg}' ...")
435
+
436
+ xml = _request(mesg)
437
+ response = XPath.first(xml, '//data/result')
438
+ _dprint("response: '#{response}'")
439
+ if response.to_s =~ /SUCC?ESS/
440
+ _dprint('Registered!')
441
+ return 0
442
+ else
443
+ err = XPath.first(xml, '//data/error-code/text()')
444
+ _dprint("Failed to register! Error: #{err}")
445
+ return err
446
+ end
447
+ }
448
+
449
+ end
450
+
451
+ =begin rdoc
452
+
453
+ Verifies the credentials that are generated using the standard authentication method.
454
+
455
+ @param [string] username Users login ID in this authentication domain
456
+ @param [string] passcode Passcode provided by the user
457
+ @param [string] domaincode 12 digit code representing the
458
+ authentication domain
459
+ @return [boolean] 'true' indicates credentials were valid,
460
+ 'false' if credentials were invalid or
461
+ an error occurred
462
+
463
+ =end
464
+ def checkCredentials(username, passcode, domaincode = '127000000001')
465
+
466
+ _dprint("checkCredentials(#{username}, #{passcode}, #{domaincode}) called ...")
467
+
468
+ validCredentials = false
469
+ offline_challenge = ''
470
+ offline_response = ''
471
+ chap_password = ''
472
+ chap_challenge = ''
473
+ valid_tag = 'VERIFY:VALID'
474
+
475
+ _dprint('Checking Credentials...')
476
+
477
+ mesg = "VERIFY:" + username + "\t" + passcode + "\t" + domaincode
478
+ mesg = <<XML
479
+ <transaction>
480
+ <type format="base">2</type>
481
+ <data>
482
+ <user-id>#{username}</user-id>
483
+ <passcode>#{passcode}</passcode>
484
+ <domaincode>#{domaincode}</domaincode>
485
+ <offline-challenge encoding="none">#{offline_challenge}</offline-challenge>
486
+ <offline-response encoding="none">#{offline_response}</offline-response>
487
+ <chap-password encoding="none">#{chap_password}</chap-password>
488
+ <chap-challenge encoding="none">#{chap_challenge}</chap-challenge>
489
+ <result>null</result>
490
+ </data>
491
+ </transaction>
492
+ XML
493
+
494
+ reconnect {
495
+
496
+ xml = _request(mesg)
497
+ response = XPath.first(xml, '//data/result')
498
+
499
+ if response =~ /VALID/
500
+ validCredentials = true
501
+ else
502
+ validCredentials = false
503
+ end
504
+ _dprint('Read response: verdict = ' + validCredentials.to_s)
505
+ }
506
+
507
+ _dprint('Returning Results...')
508
+ return validCredentials
509
+ end
510
+
511
+ =begin rdoc
512
+
513
+ Verifies the credentials via challenge-response.
514
+
515
+ @note Not currently supported by the Open Source release of WiKID.
516
+
517
+ @return [boolean] 'true' indicates credentials were valid,
518
+ 'false' if credentials were invalid or
519
+ an error occurred
520
+
521
+ =end
522
+ def chapVerify(username, domaincode, wikidChallenge = '', chapPassword = '', chapChallenge = '')
523
+
524
+ _dprint('chapVerify() called ...')
525
+ reconnect()
526
+ validCredentials = false
527
+ valid_tag = 'VERIFY:VALID'
528
+ _dprint('Checking Chap Credentials')
529
+
530
+ mesg = "CHAPOFFVERIFY:" + username + "\t" + "nil" + "\t" + domaincode + "\t" + wikidChallenge
531
+
532
+ reconnect {
533
+
534
+ $sslsocket.puts(chapPassword.length)
535
+ $sslsocket.puts(chapPassword)
536
+ $sslsocket.puts(chapChallenge.length)
537
+ $sslsocket.puts(chapChallenge.length)
538
+ $sslsocket.flush
539
+
540
+ _dprint("Reading in...")
541
+
542
+ inputLine = $sslsocket.gets.chomp
543
+ if (inputLine[0, valid_tag.length] == valid_tag)
544
+ validCredentials = true
545
+ end
546
+ }
547
+
548
+ return validCredentials
549
+ end
550
+
551
+ =begin rdoc
552
+
553
+ This method supports user pre-registration. You may upload a list of userids and
554
+ pre-registration codes into the server via the WiKIDAdmin interface. Users can then
555
+ use the pre-registration code provided to them securely by the administrator in
556
+ conjunction with the registration code provided by the WiKID token to register in
557
+ an expedited manner.
558
+
559
+ @param [string] tokenCode the registration code provided by the token
560
+ @param [string] preRegCode the code associated with the username that was uploaded to the server
561
+ @param [string] domaincode 12 digit code representing the authentication server/domain
562
+
563
+ @return [boolean] 'true' indicates that the pre-registration was successful;
564
+ 'false' if not
565
+ =end
566
+
567
+ def preRegister(tokenCode, preRegCode, domaincode = '127000000001')
568
+
569
+ _dprint('preRegister() called ...')
570
+ successful = false;
571
+
572
+ mesg = <<XML
573
+ <transaction>
574
+ <type>10</type>
575
+ <data>
576
+ <token-registration-code>#{tokenCode}</token-registration-code>
577
+ <pre-registration-code>#{preRegCode}</pre-registration-code>
578
+ <domaincode>#{domaincode}</domaincode>
579
+ <error-code>null</error-code>
580
+ <result>null</result>
581
+ </data>
582
+ </transaction>
583
+ XML
584
+
585
+ reconnect {
586
+ _dprint('Pre-registering ...')
587
+ xml = _request(mesg)
588
+
589
+ response = XPath.first(xml, '//data/result')
590
+ _dprint("response: '#{response}'")
591
+
592
+ if response.to_s =~ /SUC?CESS/
593
+ successful = true
594
+ else
595
+ successful = false
596
+ end
597
+ }
598
+
599
+ _dprint("Read response: verdict = #{successful}")
600
+ return successful
601
+
602
+ end
603
+
604
+ =begin rdoc
605
+
606
+ Find a user by username
607
+
608
+ @param [string] username the textual id of the user to search for
609
+ @param [string] domaincode the 12 digit code representating the authentication domain
610
+
611
+ @return [String] The XML representing the user object, if successful; nil if unsuccesful
612
+
613
+ =end
614
+
615
+ def findUser(username, domaincode = '127000000001')
616
+
617
+ _dprint("findUser() ...");
618
+
619
+ user = nil
620
+
621
+ mesg = <<XML
622
+ <transaction>
623
+ <type>5</type>
624
+ <data>
625
+ <domaincode>#{domaincode}</domaincode>
626
+ <user-id>#{username}</user-id>
627
+ <result>null</result>
628
+ <return-code>-2147483648</return-code>
629
+ </data>
630
+ </transaction>
631
+ XML
632
+
633
+ reconnect {
634
+ _dprint("Looking up user ...");
635
+ xml = _request(mesg)
636
+
637
+ user_xml = XPath.first(xml, '//data/user')
638
+
639
+ return user_xml
640
+ }
641
+
642
+ end
643
+
644
+ =begin rdoc
645
+
646
+ Update the previously "found" user
647
+
648
+ This method is used to update a user object on the server. The network client certificate that was
649
+ used to establish the wClient connection must be authorized to perform this action.
650
+
651
+ @param [string] user_id The userid on the server, or a user object as returned
652
+ by a call to findUser()
653
+ @param [string] domaincode 12 digit code representing the authentication domain if first argument is a userid (string), not necessary
654
+ if first argument is the user object, which will already have this
655
+
656
+ @return [boolean] 'true' indicates the update was successful
657
+ =end
658
+
659
+ def updateUser(user_id, domaincode = '127000000001', updateUserXml = '')
660
+
661
+ successful = false
662
+
663
+ _dprint("updateUser(#{user_id},#{domaincode})")
664
+
665
+ if (user_id.is_a?(String))
666
+ user_xml = findUser(user_id, domaincode)
667
+ end
668
+
669
+ if user_xml.nil?
670
+ return false
671
+ end
672
+
673
+ _dprint("user_xml: #{user_xml}, updating to #{updateUserXml}")
674
+
675
+ mesg = <<XML
676
+ <transaction>
677
+ <type>6</type>
678
+ <data>
679
+ <domaincode>#{domaincode}</domaincode>
680
+ <user-id>#{user_id}</user-id>
681
+ #{updateUserXml}
682
+ <result>null</result>
683
+ <return-code>-2147483648</return-code>
684
+ </data>
685
+ </transaction>
686
+ XML
687
+
688
+ reconnect {
689
+ _dprint('updating user ...')
690
+
691
+ xml = _request(mesg)
692
+
693
+ response = XPath.first(xml, '//data/result')
694
+ _dprint("response: '#{response}'")
695
+
696
+ if response.to_s =~ /SUCC?ESS/
697
+ successful = true
698
+ else
699
+ successful = false
700
+ end
701
+
702
+ }
703
+
704
+ return successful
705
+ end
706
+
707
+ =begin rdoc
708
+
709
+ Delete a user by userid
710
+
711
+ @param [string] user_id The userid on the server, or a user object as returned
712
+ by a call to findUser()
713
+ @param [string] domaincode 12 digit code representing the authentication domain if first argument is a userid (string), not necessary
714
+ if first argument is the user object, which will already have this
715
+
716
+ @return [boolean] ' true ' indicates deletion was successful
717
+
718
+
719
+ =end
720
+ def deleteUser(user_id, domaincode = ' 127000000001 ')
721
+
722
+ successful = false
723
+
724
+ _dprint("deleteUser(#{user_id},#{domaincode})")
725
+
726
+ if (user_id.is_a?(String))
727
+ user_xml = findUser(user_id, domaincode)
728
+ end
729
+
730
+ if user_xml.nil?
731
+ return false
732
+ end
733
+
734
+ _dprint("user: #{user_id}")
735
+
736
+ mesg = <<XML
737
+ <transaction>
738
+ <type>7</type>
739
+ <data>
740
+ <domaincode>#{domaincode}</domaincode>
741
+ <user-id>#{user_id}</user-id>
742
+ #{user_xml}
743
+ <result>null</result>
744
+ <return-code>-2147483648</return-code>
745
+ </data>
746
+ </transaction>
747
+ XML
748
+
749
+
750
+ reconnect {
751
+ _dprint('deleting user ...')
752
+
753
+ xml = _request(mesg)
754
+
755
+ unless xml.nil?
756
+ response = XPath.first(xml, '//data/result')
757
+ _dprint("response: '#{response}'")
758
+
759
+ if response.to_s =~ /SUCC?ESS/
760
+ successful = true
761
+ else
762
+ successful = false
763
+ end
764
+ end
765
+
766
+ return successful
767
+ }
768
+
769
+ end
770
+
771
+
772
+ =begin rdoc
773
+
774
+ Fetches a list of domains served by the currently connected server code.
775
+
776
+ @note Not currently supported by the Open Source release of WiKID.
777
+
778
+
779
+ @return [string] ' true ' indicates credentials were valid,
780
+ ' false ' if credentials were invalid or
781
+ an error occurred
782
+
783
+ =end
784
+ def getDomains()
785
+
786
+ _dprint("getDomains() called ...")
787
+
788
+ valid_tag = "DOMAINLIST"
789
+ _dprint("Getting Domains")
790
+
791
+ mesg = <<XML
792
+ <transaction>
793
+ <type>3</type>
794
+ <data>
795
+ <domain-list>null</domain-list>
796
+ </data>
797
+ </transaction>
798
+ XML
799
+ reconnect {
800
+ xml = _request(mesg)
801
+ domains = XPath.match(xml, '//data/domain-list')
802
+ }
803
+ _dprint("Returning Results...")
804
+ return domains
805
+ end
806
+
807
+ =begin rdoc
808
+
809
+ Modify the debug state of the current connection
810
+
811
+ @param [boolean] newStatus
812
+ =end
813
+ def setDebug(newStatus)
814
+ @@DEBUG = (newStatus == true) ? true : false
815
+ end
816
+
817
+ =begin rdoc
818
+
819
+ Prints a message if the debug flag is true, time-stamped since the epoch.
820
+
821
+ @param [string] msg Message to print out
822
+ @api private
823
+ @private
824
+ =end
825
+ def _dprint(msg)
826
+
827
+ if (@@DEBUG)
828
+ show = Time.now.to_s + ' : ' + msg
829
+ show += '<br />' if !ENV['REQUEST_URI'].nil?
830
+
831
+ puts show
832
+ #STDERR.puts show
833
+ #STDERR.flush()
834
+ end
835
+ return true
836
+ end
837
+ private :_dprint
838
+
839
+ end
840
+
841
+ end