Access.lua 18.9 KB
Newer Older
1
2
local _G = require "_G"
local ipairs = _G.ipairs
3
local pairs = _G.pairs
4
local pcall = _G.pcall
5
local rawget = _G.rawget
6
local select = _G.select
7
local setmetatable = _G.setmetatable
8
local type = _G.type
9
10
11

local array = require "table"
local unpack = array.unpack or _G.unpack
12

13
14
local coroutine = require "coroutine"
local running = coroutine.running
15
16
17
18
19
20
21
22
23
24
25
26
27

local string = require "string"
local char = string.char

local math = require "math"
local random = math.random
local randomseed = math.randomseed

local struct = require "struct"
local encode = struct.pack

local socket = require "socket.core"
local gettime = socket.gettime
28
29
30
31

local table = require "loop.table"
local clear = table.clear
local copy = table.copy
32
local memoize = table.memoize
33
34
35

local oil = require "oil"
local neworb = oil.init
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
36
local CORBAException = require "oil.corba.giop.Exception"
37
38
39
40
41
local idl = require "oil.corba.idl"
local OctetSeq = idl.OctetSeq

local hash = require "lce.hash"
local sha256 = hash.sha256
42

43
44
local LRUCache = require "loop.collection.LRUCache"

45
46
47
local log = require "openbus.util.logger"
local oo = require "openbus.util.oo"
local class = oo.class
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
48
49
local sysex = require "openbus.util.sysex"
local is_NO_PERMISSION = sysex.is_NO_PERMISSION
50
local tickets = require "openbus.util.tickets"
51
52
53
54

local msg = require "openbus.core.messages"
local idl = require "openbus.core.idl"
local loadidl = idl.loadto
55
local InvalidLoginsException = idl.types.services.access_control.InvalidLogins
56
local EncryptedBlockSize = idl.const.EncryptedBlockSize
57
local CredentialContextId = idl.const.credential.CredentialContextId
58
local loginconst = idl.const.services.access_control
59
60
61
62
63
64
local InvalidChainCode = loginconst.InvalidChainCode
local InvalidCredentialCode = loginconst.InvalidCredentialCode
local InvalidLoginCode = loginconst.InvalidLoginCode
local InvalidPublicKeyCode = loginconst.InvalidPublicKeyCode
local InvalidRemoteCode = loginconst.InvalidRemoteCode
local InvalidTargetCode = loginconst.InvalidTargetCode
65
local UnknownBusCode = loginconst.UnknownBusCode
66
67
local NoLoginCode = loginconst.NoLoginCode
local UnavailableBusCode = loginconst.UnavailableBusCode
68
local UnknownBusCode = loginconst.UnknownBusCode
69
70
local oldidl = require "openbus.core.legacy.idl"
local loadoldidl = oldidl.loadto
71

72
local repids = {
73
74
75
  CallChain = idl.types.services.access_control.CallChain,
  CredentialData = idl.types.credential.CredentialData,
  CredentialReset = idl.types.credential.CredentialReset,
76
77
  LegacyCredential = oldidl.types.access_control_service.Credential,
  LegacyACS = oldidl.types.access_control_service.IAccessControlService,
78
}
79
80
local VersionHeader = char(idl.const.MajorVersion,
                           idl.const.MinorVersion)
81
local LegacyCredentialContextId = 1234
82
83
local SecretSize = 16

84
local NullChar = "\0"
85
86
local NullSecret = NullChar:rep(SecretSize)
local NullHash = NullChar:rep(idl.const.HashValueSize)
87
local NullChain = {
88
  encoded = "",
89
  signature = NullChar:rep(EncryptedBlockSize),
90
91
  originators = {},
  caller = nil,
92
}
93
94

local WeakKeys = {__mode = "k"}
95
96


97
98
99


local function calculateHash(secret, ticket, request)
100
  return sha256(encode(
101
    "<c2c0I4c0",             -- '<' flag to set to little endian
102
    VersionHeader,           -- 'c2' sequence of exactly 2 chars of a string
103
    secret,                  -- 'c0' sequence of all chars of a string
104
105
    ticket,                  -- 'I4' unsigned integer with 4 bytes
    request.operation_name)) -- 'c0' sequence of all chars of a string
106
107
108
109
end

randomseed(gettime())
local function newSecret()
110
  local bytes = {}
111
  for i=1, SecretSize do bytes[i] = random(0, 255) end
112
  return char(unpack(bytes))
113
114
115
end

local function setNoPermSysEx(request, minor)
116
  request.islocal = true
117
  request.success = false
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
118
  request.results = {CORBAException{"NO_PERMISSION",
119
120
121
    completed = "COMPLETED_NO",
    minor = minor,
  }}
122
123
end

124
125
126
127
local function validateCredential(self, credential, login, request)
  local hash = credential.hash
  if hash ~= nil then
    local chain = credential.chain
128
    if chain == nil or chain.target == self.login.entity then
129
130
131
132
      -- get current credential session
      local session = self.incomingSessions:rawget(credential.session)
      if session ~= nil then
        local ticket = credential.ticket
133
134
135
        -- validate credential data with session data
        if login.id == session.login
        and hash == calculateHash(session.secret, ticket, request)
136
137
138
139
140
141
        and session.tickets:check(ticket) then
          return true
        end
      end
    end
  elseif credential.owner == login.entity then -- got a OpenBus 1.5 credential
142
    credential.chain.target = self.login.entity
143
144
145
146
147
148
149
150
151
152
153
154
    if credential.delegate == "" then
      return true
    end
    local allowLegacyDelegate = login.allowLegacyDelegate
    if allowLegacyDelegate == nil then
      allowLegacyDelegate = self.legacy:isValid(credential)
      login.allowLegacyDelegate = allowLegacyDelegate
    end
    return allowLegacyDelegate
  end
  -- create a new session for the invalid credential
  return false
155
156
end

157
local function validateChain(self, chain, caller)
158
  if chain ~= nil then
159
    local signature = chain.signature
160
161
162
    if signature then -- no legacy chain (OpenBus 1.5)
      return self.buskey:verify(sha256(chain.encoded), signature)
         and chain.caller.id == caller.id
163
    end
164
    return true
165
  end
166
167
end

168
169


170
171
local Context = class()

172
173
174
175
176
177
-- Fields that must be provided before using the context:
-- orb: OiL ORB to be used to access the bus

-- Optional field that may be provided to configure the interceptor:
-- prvkey: default private key to be used by all connections using this context

178
function Context:__init()
179
180
181
182
183
184
185
  local orb = self.orb
  local types = orb.types
  local idltypes = {}
  for name, repid in pairs(repids) do
    idltypes[name] = types:lookup_id(repid)
  end
  self.types = idltypes
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
  self.callerChainOf = setmetatable({}, WeakKeys) -- [thread] = chain
  self.joinedChainOf = setmetatable({}, WeakKeys) -- [thread] = chain
end

function Context:getCallerChain()
  return self.callerChainOf[running()]
end

function Context:joinChain(chain)
  local thread = running()
  self.joinedChainOf[thread] = chain or self.callerChainOf[thread] -- or error?
end

function Context:exitChain()
  self.joinedChainOf[running()] = nil
end

function Context:getJoinedChain()
  return self.joinedChainOf[running()]
end



209
local Interceptor = class()
210

211
-- Fields that must be provided before using the interceptor:
212
-- context      : context object to be used to retrieve credential info from
213
214
215
-- busid        : UUID of the bus being accessed
-- buskey       : public key of the bus being accessed
-- AccessControl: AccessControl facet of the bus being accessed
216
-- LoginRegistry: LoginRegistry facet of the bus being accessed
217
-- login        : information about the login used to access the bus
218

219
-- Optional field that may be provided to configure the interceptor:
220
221
222
-- prvkey      : private key associated to the key registered to the login
-- legacy      : ACS facet of OpenBus 1.5 to validate legacy invocations
-- maxcachesize: max size for LRUCache objects
223

224
function Interceptor:__init()
225
  self.maxcachesize = self.maxcachesize or LRUCache.maxsize
226
  self:resetCaches()
227
228
end

229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
function Interceptor:maxCacheSize(newsize)
  if not newsize then
    return self.maxcachesize
  else
    self.maxcachesize = newsize
    self.profile2login.maxsize = newsize
    self.outgoingSessions.maxsize = newsize
    self.incomingSessions.maxsize = newsize
  end
end

local function newLRU(self, data)
  local data = data or {}
  data.maxsize = self.maxcachesize
  return LRUCache(data)
end

246
function Interceptor:resetCaches()
247
248
249
  self.profile2login = newLRU(self) -- [iop_profile] = loginid
  self.outgoingSessions = newLRU(self)
  self.incomingSessions = newLRU(self,{
250
251
252
253
254
255
256
    retrieve = function(id)
      return {
        id = id,
        secret = newSecret(),
        tickets = tickets(),
      }
    end,
257
  })
258
259
end

260
function Interceptor:unmarshalSignedChain(chain, busid)
261
262
263
264
265
266
267
268
269
270
271
272
  local encoded = chain.encoded
  if encoded ~= "" then
    local context = self.context
    local types = context.types
    local orb = context.orb
    local decoder = orb:newdecoder(chain.encoded)
    local decoded = decoder:get(types.CallChain)
    local originators = decoded.originators
    originators.n = nil -- remove field 'n' created by OiL unmarshal
    chain.originators = originators
    chain.caller = decoded.caller
    chain.target = decoded.target
273
    chain.busid = busid
274
275
276
277
    return chain
  end
end

278
local unmarshalSignedChain = Interceptor.unmarshalSignedChain
279
function Interceptor:unmarshalCredential(contexts)
280
281
282
  local context = self.context
  local types = context.types
  local orb = context.orb
283
284
285
  local data = contexts[CredentialContextId]
  if data ~= nil then
    local credential = orb:newdecoder(data):get(types.CredentialData)
286
    credential.chain = unmarshalSignedChain(self, credential.chain, credential.bus)
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
    return credential
  end
  if self.legacy ~= nil then
    local data = contexts[LegacyCredentialContextId]
    if data ~= nil then
      local credential = orb:newdecoder(data):get(types.LegacyCredential)
      local loginId = credential.identifier
      local entity = credential.owner
      local delegate = credential.delegate
      local caller = {id=loginId, entity=entity}
      local originators = {}
      if delegate ~= "" then
        originators[1] = {id="<unknown>",entity=delegate}
      end
      credential.login = loginId
      credential.chain = {
        originators = originators,
        caller = caller,
      }
      return credential
    end
  end
end

311
312
313


function Interceptor:sendrequest(request)
314
315
  local contexts = {}
  local legacy = self.legacy
316
317
318
  local context = self.context
  local orb = context.orb
  local chain = context.joinedChainOf[running()]
319
320
  if chain==nil or chain.signature~=nil then -- no legacy chain (OpenBus 1.5)
    local sessionid, ticket, hash = 0, 0, NullHash
321
322
    local profile2login = self.profile2login
    local target = profile2login:get(request.profile_data)
323
    if target ~= nil then -- known IOR profile, so it supports OpenBus 2.0
324
      legacy = nil -- do not send legacy credential (OpenBus 1.5)
325
326
327
328
329
330
331
      local ok, result = pcall(self.signChainFor, self, target, chain or NullChain)
      if not ok then
        log:exception(msg.UnableToSignChainForTarget:tag{
          error = result,
          target = target,
          chain = chain,
        })
332
        local minor = UnavailableBusCode
333
        if result._repid == InvalidLoginsException then
334
335
336
337
338
          for profile_data, profile_target in pairs(profile2login.map) do
            if target == profile_target then
              profile2login:remove(profile_data)
            end
          end
339
          minor = InvalidTargetCode
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
        end
        setNoPermSysEx(request, minor)
        return
      end
      chain = result
      local session = self.outgoingSessions:get(target)
      if session ~= nil then -- credential session is established
        sessionid = session.id
        ticket = session.ticket+1
        session.ticket = ticket
        hash = calculateHash(session.secret, ticket, request)
        log:access(self, msg.PerformBusCall:tag{
          operation = request.operation_name,
          remote = target,
        })
      else
        log:access(self, msg.ReinitiateCredentialSession:tag{
          operation = request.operation_name,
        })
      end
360
    else
361
      log:access(self, msg.InitiateCredentialSession:tag{
362
363
364
365
366
367
368
369
370
371
372
373
374
        operation = request.operation_name,
      })
    end
    -- marshal credential information
    local credential = {
      bus = self.busid,
      login = self.login.id,
      session = sessionid,
      ticket = ticket,
      hash = hash,
      chain = chain or NullChain,
    }
    local encoder = orb:newencoder()
375
    encoder:put(credential, context.types.CredentialData)
376
    contexts[CredentialContextId] = encoder:getdata()
377
378
  elseif not legacy then
    setNoPermSysEx(request, InvalidChainCode)
379
380
381
382
383
384
385
386
387
  end
  -- marshal legacy credential (OpenBus 1.5)
  if legacy ~= nil then
    local encoder = orb:newencoder()
    local login = self.login
    encoder:string(login.id)
    encoder:string(login.entity)
    if chain == nil then
      encoder:string("")
388
    elseif #chain.originators > 0 and self.legacyDelegOrig then
389
390
391
392
393
394
395
      encoder:string(chain.originators[1].entity)
    else
      encoder:string(chain.caller.entity)
    end
    contexts[LegacyCredentialContextId] = encoder:getdata()
  end
  request.service_context = contexts
396
397
end

398
local ExclusivelyLocal = {
399
400
401
402
  [NoLoginCode] = true,
  [InvalidRemoteCode] = true,
  [UnavailableBusCode] = true,
  [InvalidTargetCode] = true,
403
404
}

405
function Interceptor:receivereply(request)
406
407
  if not request.success then
    local except = request.results[1]
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
408
    if is_NO_PERMISSION(except, nil, "COMPLETED_NO") then
409
      if except.minor == InvalidCredentialCode then
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
        -- got invalid credential exception
        local data = request.reply_service_context[CredentialContextId]
        if data ~= nil then
          local context = self.context
          local decoder = context.orb:newdecoder(data)
          local reset = decoder:get(context.types.CredentialReset)
          local secret, errmsg = self.prvkey:decrypt(reset.challenge)
          if secret ~= nil then
            local target = reset.target
            log:access(self, msg.GotCredentialReset:tag{
              operation = request.operation_name,
              remote = target,
            })
            reset.secret = secret
            -- initialize session and set credential session information
            self.profile2login:put(request.profile_data, target)
            self.outgoingSessions:put(target, {
              id = reset.session,
              secret = reset.secret,
              remote = target,
              ticket = -1,
            })
            request.success = nil -- reissue request to the same reference
          else
            log:exception(msg.GotCredentialResetWithBadChallenge:tag{
              operation = request.operation_name,
              remote = reset.target,
              error = errmsg,
            })
439
            except.minor = InvalidRemoteCode
440
          end
441
        else
442
          log:exception(msg.CredentialResetMissing:tag{
443
444
            operation = request.operation_name,
          })
445
          except.minor = InvalidRemoteCode
446
        end
447
448
      elseif not request.islocal and ExclusivelyLocal[except.minor] ~= nil then
        log:exception(msg.IllegalUseOfLocalMinorCodeByRemoteSite:tag{
449
          operation = request.operation_name,
450
          codeused = except.minor,
451
        })
452
        except.minor = InvalidRemoteCode
453
454
455
      end
    end
  end
456
457
end

458
459
460
function Interceptor:receiverequest(request, credential)
  local credential = credential
                  or self:unmarshalCredential(request.service_context)
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
461
  if credential ~= nil then
462
463
    local busid = credential.bus
    if busid == nil or busid == self.busid then
464
      local caller = self.LoginRegistry:getLoginEntry(credential.login)
465
      if caller ~= nil then
466
        local context = self.context
467
        if validateCredential(self, credential, caller, request) then
468
469
          local chain = credential.chain
          if validateChain(self, chain, caller) then
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
470
            log:access(self, msg.GotBusCall:tag{
471
              operation = request.operation_name,
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
472
473
              remote = caller.id,
              entity = caller.entity,
474
            })
475
476
            chain.busid = busid
            context.callerChainOf[running()] = chain
477
478
479
          else
            -- invalid call chain
            log:exception(msg.GotCallWithInvalidChain:tag{
480
              operation = request.operation_name,
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
481
482
              remote = caller.id,
              entity = caller.entity,
483
            })
484
            setNoPermSysEx(request, InvalidChainCode)
485
          end
486
        elseif busid == nil then
487
488
          -- invalid legacy credential (OpenBus 1.5)
          log:exception(msg.GotLegacyCallWithInvalidCredential:tag{
489
            operation = request.operation_name,
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
490
491
            remote = caller.id,
            entity = caller.entity,
492
            delegate = credential.delegate,
493
          })
494
          setNoPermSysEx(request, 0)
495
496
497
498
        else
          -- invalid credential, try to reset credetial session
          local sessions = self.incomingSessions
          local newsession = sessions:get(#sessions.map+1)
499
          newsession.login = caller.id
500
501
502
503
504
505
506
507
          local challenge, errmsg = caller.pubkey:encrypt(newsession.secret)
          if challenge ~= nil then
            -- marshall credential reset
            log:access(self, msg.SendCredentialReset:tag{
              operation = request.operation_name,
              remote = caller.id,
              entity = caller.entity,
            })
508
            local encoder = context.orb:newencoder()
509
            encoder:put({
510
              target = self.login.id,
511
512
              session = newsession.id,
              challenge = challenge,
513
            }, context.types.CredentialReset)
514
515
516
            request.reply_service_context = {
              [CredentialContextId] = encoder:getdata(),
            }
517
            setNoPermSysEx(request, InvalidCredentialCode)
518
519
520
521
522
523
524
          else
            log:exception(msg.UnableToEncryptSecretWithCallerKey:tag{
              operation = request.operation_name,
              remote = caller.id,
              entity = caller.entity,
              error = errmsg,
            })
525
            setNoPermSysEx(request, InvalidPublicKeyCode)
526
          end
527
        end
528
      else
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
529
        -- credential with invalid login
530
531
532
        local logmessage = (busid == nil) and msg.GotLegacyCallWithInvalidLogin
                                           or msg.GotCallWithInvalidLogin
        log:exception(logmessage:tag{
533
          operation = request.operation_name,
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
534
          remote = credential.login,
535
        })
536
        setNoPermSysEx(request, InvalidLoginCode)
537
538
      end
    else
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
539
540
      -- credential for another bus
      log:exception(msg.GotCallFromAnotherBus:tag{
541
        operation = request.operation_name,
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
542
        remote = credential.login,
543
        bus = busid,
544
      })
545
      setNoPermSysEx(request, UnknownBusCode)
546
547
    end
  else
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
548
    log:access(self, msg.GotOrdinaryCall:tag{
549
      operation = request.operation_name,
550
551
    })
  end
552
553
end

554
function Interceptor:sendreply()
555
  self.context.callerChainOf[running()] = nil
556
557
558
559
end



Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
function log.custom:access(conn, ...)
  local viewer = self.viewer
  local output = viewer.output
  if self.flags.multiplex ~= nil then
    local login, busid = conn.login, conn.busid
    if login ~= nil then
      output:write(msg.CurrentLogin:tag{
        bus = busid,
        login = login.id,
        entity = login.entity,
      },"  ")
    end
  end
  for i = 1, select("#", ...) do
    local value = select(i, ...)
    if type(value) == "string"
      then output:write(value)
      else viewer:write(value)
    end
  end
end



584
local module = {
585
  Context = Context,
586
  Interceptor = Interceptor,
Renato Figueiro Maia's avatar
Renato Figueiro Maia committed
587
  setNoPermSysEx = setNoPermSysEx,
588
589
}

590
function module.initORB(configs)
591
592
593
594
595
596
597
598
  if configs == nil then
    configs = {}
  end
  if configs.options == nil then
    configs.options = {}
  end
  if configs.options.tcp == nil then
    configs.options.tcp = {reuseaddr=true}
599
600
601
602
  end
  configs.flavor = "cooperative;corba.intercepted"
  local orb = neworb(configs)
  loadidl(orb)
603
  loadoldidl(orb)
604
  return orb
605
606
607
end

return module