Class: Ubiq::Decryption
- Inherits:
-
Object
- Object
- Ubiq::Decryption
- Defined in:
- lib/ubiq/decrypt.rb
Overview
Class to provide data decryption, either as a simple single function call or as a piecewise where the entire data element isn’t available at once or is too large to process in a single call.
Instance Method Summary collapse
- #begin ⇒ Object
- #close ⇒ Object
- #end ⇒ Object
- #endpoint ⇒ Object
- #endpoint_base ⇒ Object
-
#initialize(creds) ⇒ Decryption
constructor
A new instance of Decryption.
- #update(data) ⇒ Object
Constructor Details
#initialize(creds) ⇒ Decryption
Returns a new instance of Decryption.
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/ubiq/decrypt.rb', line 18 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 'Some of your credentials are missing, please check!' unless validate_creds(creds) @host = creds.host.blank? ? UBIQ_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
#begin ⇒ Object
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
# File 'lib/ubiq/decrypt.rb', line 49 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 'Decryption is not ready' unless @decryption_ready raise 'Decryption Already Started' if @decryption_started raise 'Decryption already in progress' if @key.present? && @key.key?('dec') @decryption_started = true @data = '' end |
#close ⇒ Object
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 |
# File 'lib/ubiq/decrypt.rb', line 238 def close raise 'Decryption currently running' if @decryption_started # Reset the internal state of the decryption object if @key.present? if @key['uses'].positive? 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 |
#end ⇒ Object
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 |
# File 'lib/ubiq/decrypt.rb', line 210 def end raise 'Decryption is not Started' unless @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 'Invalid Tag!' if sz.negative? if sz.zero? @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 print 'Invalid cipher data or tag!' return '' end end end |
#endpoint ⇒ Object
45 46 47 |
# File 'lib/ubiq/decrypt.rb', line 45 def endpoint '/api/v0/decryption/key' end |
#endpoint_base ⇒ Object
41 42 43 |
# File 'lib/ubiq/decrypt.rb', line 41 def endpoint_base @host + '/api/v0' end |
#update(data) ⇒ Object
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/ubiq/decrypt.rb', line 72 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 'Decryption is not Started' unless @decryption_started # Append the incoming data in the internal data buffer @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? || !@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, flags, algorithm_id, iv_length, key_length = packed_struct.unpack('CCCCn') # verify flag are correct and version is 0 raise 'invalid encryption header' if (version != 0 ) || ((flags & ~Algo::UBIQ_HEADER_V0_FLAG_AAD) != 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? close if @key['client_id'] != client_id end # IF key object not exists, request a new one from the server unless @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'] # Get the algorithm name from the internal algorithm id in the header @key['algorithm'] = Algo.new.find_alg(algorithm_id) 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 "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) # Documentation indicates the auth_data has to be set AFTER auth_tag # but we get an OpenSSL error when it is set AFTER an update call. # Checking OpenSSL documentation, there is not a requirement to set # auth_data before auth_tag so Ruby documentation seems to be # wrong. This approach works and is compatible with the encrypted # data produced by the other languages' client library if (flags & Algo::UBIQ_HEADER_V0_FLAG_AAD) != 0 @key['dec'].auth_data = packed_struct + iv + encrypted_key end @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? && @key.key?('dec') size = @data.length - @algo[:tag_length] if size.positive? plain_text = @key['dec'].update(@data[0..size - 1]) @data = @data[size..-1] end return plain_text end end |