WiKID 3.0.2 → 3.2.3

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.
@@ -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