Module: NdrPseudonymise::NdrEncrypt::EncryptedObject

Defined in:
lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb

Overview

Defines utility methods for encrypting / decrypting objects

Class Method Summary collapse

Class Method Details

.blob(data) ⇒ Object

rubocop:disable Style/SlicingWithRange



11
12
13
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 11

def self.blob(data)
  "blob #{data.size}\0#{data}"
end

.compress(blob) ⇒ Object

Create zlib-compressed version of the content



30
31
32
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 30

def self.compress(blob)
  Zlib::Deflate.deflate(blob)
end

.decompress(contents) ⇒ Object

Unpack zlib-compressed content



35
36
37
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 35

def self.decompress(contents)
  Zlib::Inflate.inflate(contents)
end

.decrypt(rawdata, private_key: nil, passin: nil) ⇒ Object

Decrypt sensitive secret data, given a private key and its password Returns the decrypted output data TODO: write equivalent command-line method using only openssl and shell scripts TODO: Refactor with code from era UnifiedSources::ApiRetrieval::Extractor

Raises:

  • (ArgumentError)


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 71

def self.decrypt(rawdata, private_key: nil, passin: nil)
  # We need to support ruby 2.0 so cannot use required keyword arguments syntax
  raise(ArgumentError, 'missing keyword: :private_key') unless private_key
  return nil unless rawdata

  password = get_passphrase(private_key: private_key, passin: passin)
  private_key_data = File.read(private_key)
  cipher = OpenSSL::Cipher.new('aes-256-cbc')
  cipher.decrypt
  private_key = OpenSSL::PKey::RSA.new(private_key_data, password)
  key_size = private_key.n.num_bytes
  cipher.key = private_key.private_decrypt(rawdata[0..key_size - 1])
  cipher.iv = rawdata[key_size..key_size + 15]
  decrypted_data = cipher.update(rawdata[key_size + 16..-1])
  decrypted_data << cipher.final
end

.digest(blob) ⇒ Object



25
26
27
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 25

def self.digest(blob)
  Digest::SHA256.hexdigest(blob)
end

.encrypt(secret_data, pub_key: nil) ⇒ Object

Encrypt sensitive secret data, given a public key file as a String Returns the encrypted output data Result can either be decrypted using the decrypt method on this class. TODO: write equivalent command-line method using only openssl and shell scripts

Raises:

  • (ArgumentError)


51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 51

def self.encrypt(secret_data, pub_key: nil)
  # We need to support ruby 2.0 so cannot use required keyword arguments syntax
  raise(ArgumentError, 'missing keyword: :pub_key') unless pub_key
  return nil unless secret_data

  public_key_data = File.read(pub_key)
  cipher = OpenSSL::Cipher.new('aes-256-cbc')
  cipher.encrypt
  cipher.key = random_key = cipher.random_key
  cipher.iv = random_iv = cipher.random_iv
  rawdata = cipher.update(secret_data)
  rawdata << cipher.final
  public_key = OpenSSL::PKey::RSA.new(public_key_data)
  public_key.public_encrypt(random_key) + random_iv + rawdata
end

.encrypted_id(git_blobid, key_name: nil) ⇒ Object

Raises:

  • (ArgumentError)


39
40
41
42
43
44
45
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 39

def self.encrypted_id(git_blobid, key_name: nil)
  # We need to support ruby 2.0 so cannot use required keyword arguments syntax
  raise(ArgumentError, 'missing keyword: :key_name') unless key_name

  temp_id = "ndr_encrypt #{git_blobid} #{key_name}"
  digest(blob(temp_id))
end

.get_passphrase(private_key: nil, passin: nil) ⇒ Object

Raises:

  • (ArgumentError)


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
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 88

def self.get_passphrase(private_key: nil, passin: nil)
  # We need to support ruby 2.0 so cannot use required keyword arguments syntax
  raise(ArgumentError, 'missing keyword: :private_key') unless private_key

  @passphrase_cache ||= {}
  return @passphrase_cache[private_key] if @passphrase_cache.key?(private_key)

  raise(ArgumentError, 'Missing private key file') unless File.exist?(private_key)

  # Implement a subset of the openssl -passin options in
  # https://www.openssl.org/docs/man3.0/man1/openssl-passphrase-options.html
  result = case passin
           when nil, ''
             msg = "Enter passphrase for #{private_key}: "
             if IO.console.respond_to?(:getpass)
               IO.console.getpass msg
             else
               $stdout.print msg
               password = $stdin.noecho(&:gets).chomp
               puts
               password
             end
           when /\Apass:/
             passin[5..-1]
           when /\Aenv:/
             ENV[passin[4..-1]]
           when 'stdin'
             $stdin.readline.chomp
           else
             raise(ArgumentError, 'Unsupported passin option')
           end
  @passphrase_cache[private_key] = result
end

.unpack_blob(blob) ⇒ Object

Raises:

  • (ArgumentError)


15
16
17
18
19
20
21
22
23
# File 'lib/ndr_pseudonymise/ndr_encrypt/encrypted_object.rb', line 15

def self.unpack_blob(blob)
  prefix, data = blob.split("\x00", 2)
  raise(ArgumentError, 'Invalid blob format') unless /\Ablob [0-9]+\z/ =~ prefix

  size = prefix[5..-1].to_i
  raise(ArgumentError, 'Incorrect blob size') unless size == data.size

  data
end