Class: SecurityClient::Decryption

Inherits:
Object
  • Object
show all
Defined in:
lib/security_client.rb

Instance Method Summary collapse

Constructor Details

#initialize(creds) ⇒ Decryption

Returns a new instance of Decryption.



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'lib/security_client.rb', line 332

def initialize(creds)
# Initialize the decryption module object
# Set the credentials in instance varibales to be used among methods
# the server to which to make the request
# raise RuntimeError, 'Some of your credentials are missing, please check!' if !validate_creds(creds)
@host = creds.host.blank? ? VOLTRON_HOST : creds.host

# The client's public API key (used to identify the client to the server
@papi = creds.access_key_id

# The client's secret API key (used to authenticate HTTP requests)
@sapi = creds.secret_signing_key

# The client's secret RSA encryption key/password (used to decrypt the client's RSA key from the server). This key is not retained by this object.
@srsa = creds.secret_crypto_access_key

@decryption_ready = true
@decryption_started = false

end

Instance Method Details

#beginObject

Raises:

  • (RuntimeError)


361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/security_client.rb', line 361

def begin
  # Begin the decryption process

  # This interface does not take any cipher text in its arguments
  # in an attempt to maintain an API that corresponds to the
  # encryption object. In doing so, the work that can take place
  # in this function is limited. without any data, there is no
  # way to determine which key is in use or decrypt any data.
  #
  # this function simply throws an error if starting an decryption
  # while one is already in progress, and initializes the internal
  # buffer

  raise RuntimeError, 'Decryption is not ready' if !@decryption_ready

  raise RuntimeError, 'Decryption Already Started' if @decryption_started

  raise RuntimeError, 'Decryption already in progress' if @key.present? and @key.key?("dec")
  @decryption_started = true
  @data = ''
end

#closeObject

Raises:

  • (RuntimeError)


538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
# File 'lib/security_client.rb', line 538

def close
  raise RuntimeError, 'Decryption currently running' if @decryption_started
  # Reset the internal state of the decryption object
  if @key.present?
    if @key['uses'] > 0
      query_url = "#{endpoint}/#{@key['finger_print']}/#{@key['session']}"
      url = "#{endpoint_base}/decryption/key/#{@key['finger_print']}/#{@key['session']}"
      query = {uses: @key['uses']}
      headers = Auth.build_headers(@papi, @sapi, query_url, query, @host, 'patch')
      response = HTTParty.patch(
        url,
        body: query.to_json,
        headers: headers
      )
      remove_instance_variable(:@data)
      remove_instance_variable(:@key)
    end
  end
end

#endObject

Raises:

  • (RuntimeError)


512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/security_client.rb', line 512

def end
  raise RuntimeError, 'Decryption is not Started' if !@decryption_started
  # The update function always maintains tag-size bytes in
  # the buffer because this function provides no data parameter.
  # by the time the caller calls this function, all data must
  # have already been input to the decryption object.

  sz = @data.length - @algo[:tag_length]

  raise RuntimeError, 'Invalid Tag!' if sz < 0
  if sz == 0
    @key['dec'].auth_tag = @data
    begin
      pt = @key['dec'].final
      # Delete the decryptor context
      @key.delete('dec')
      # Return the decrypted plain data
      @decryption_started = false
      return pt
    rescue Exception => e
      print 'Invalid cipher data or tag!'
      return ''
    end
  end
end

#endpointObject



357
358
359
# File 'lib/security_client.rb', line 357

def endpoint
  '/api/v0/decryption/key'
end

#endpoint_baseObject



353
354
355
# File 'lib/security_client.rb', line 353

def endpoint_base
  @host + '/api/v0'
end

#update(data) ⇒ Object



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
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
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
# File 'lib/security_client.rb', line 383

def update(data)
  # Decryption of cipher text is performed here
  # Cipher text must be passed to this function in the order in which it was output from the encryption.update function.

  # Each encryption has a header on it that identifies the algorithm
  # used  and an encryption of the data key that was used to encrypt
  # the original plain text. there is no guarantee how much of that
  # data will be passed to this function or how many times this
  # function will be called to process all of the data. to that end,
  # this function buffers data internally, when it is unable to
  # process it.
  #
  # The function buffers data internally until the entire header is
  # received. once the header has been received, the encrypted data
  # key is sent to the server for decryption. after the header has
  # been successfully handled, this function always decrypts all of
  # the data in its internal buffer *except* for however many bytes
  # are specified by the algorithm's tag size. see the end() function
  # for details.

  raise RuntimeError, 'Decryption is not Started' if !@decryption_started

  # Append the incoming data in the internal data buffer
  @data  = @data + data

  # if there is no key or 'dec' member of key, then the code is still trying to build a complete header
  if !@key.present? or !@key.key?("dec")
    struct_length = [1,1,1,1,1].pack('CCCCn').length
    packed_struct = @data[0...struct_length]

    # Does the buffer contain enough of the header to
    # determine the lengths of the initialization vector
    # and the key?
    if @data.length > struct_length
      # Unpack the values packed in encryption
      version, flag_for_later, algorithm_id, iv_length, key_length = packed_struct.unpack('CCCCn')

      # verify flag and version are 0
      raise RuntimeError, 'invalid encryption header' if version != 0 or flag_for_later != 0

      # Does the buffer contain the entire header?
      if @data.length > struct_length + iv_length + key_length
        # Extract the initialization vector
        iv = @data[struct_length...iv_length + struct_length]
        # Extract the encryped key
        encrypted_key = @data[struct_length + iv_length...key_length + struct_length + iv_length]
        # Remove the header from the buffer
        @data = @data[struct_length + iv_length + key_length..-1]

        # generate a local identifier for the key
        hash_sha512 = OpenSSL::Digest::SHA512.new
        hash_sha512 << encrypted_key
        client_id = hash_sha512.digest

        if @key.present?
          if @key['client_id'] != client_id
            close()
          end
        end

        # IF key object not exists, request a new one from the server
        if !@key.present?
          url = endpoint_base + "/decryption/key"
          query = {encrypted_data_key: Base64.strict_encode64(encrypted_key)}
          headers = Auth.build_headers(@papi, @sapi, endpoint, query, @host, 'post')

          response = HTTParty.post(
            url,
            body: query.to_json,
            headers: headers
          )

          # Response status is 200 OK
          if response.code == WEBrick::HTTPStatus::RC_OK
            @key = {}
            @key['finger_print'] = response['key_fingerprint']
            @key['client_id'] = client_id
            @key['session'] = response['encryption_session']

            @key['algorithm'] = 'aes-256-gcm'

            encrypted_private_key = response['encrypted_private_key']
            # Decrypt the encryped private key using SRSA
            private_key = OpenSSL::PKey::RSA.new(encrypted_private_key,@srsa)

            wrapped_data_key = response['wrapped_data_key']
            # Decode WDK from base64 format
            wdk = Base64.strict_decode64(wrapped_data_key)
            # Use private key to decrypt the wrapped data key
            dk = private_key.private_decrypt(wdk,OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)

            @key['raw'] = dk
            @key['uses'] = 0
          else
            # Raise the error if response is not 200
            raise RuntimeError, "HTTPError Response: Expected 201, got #{response.code}"
          end
        end

        # If the key object exists, create a new decryptor
        # with the initialization vector from the header and
        # the decrypted key (which is either new from the
        # server or cached from the previous decryption). in
        # either case, increment the key usage

        if @key.present?
          @algo = Algo.new.get_algo(@key['algorithm'])
          @key['dec'] = Algo.new.decryptor(@algo, @key['raw'], iv)
          @key['uses'] += 1
        end
      end
    end
  end

  # if the object has a key and a decryptor, then decrypt whatever
  # data is in the buffer, less any data that needs to be saved to
  # serve as the tag.
  plain_text = ''
  if @key.present? and @key.key?("dec")
    size = @data.length - @algo[:tag_length]
    if size > 0
      plain_text = @key['dec'].update(@data[0..size-1])
      @data = @data[size..-1]
    end
    return plain_text
  end

end