class MCollective::SSL
A class that assists in encrypting and decrypting data using a combination of RSA and AES
Data
will be AES encrypted for speed, the Key used in # the AES stage will be encrypted using RSA
ssl = SSL.new(public_key, private_key, passphrase) data = File.read("largefile.dat") crypted_data = ssl.encrypt_with_private(data) pp crypted_data
This will result in a hash of data like:
crypted = {:key => "crd4NHvG....=", :data => "XWXlqN+i...=="}
The key and data will all be base 64 encoded already by default you can pass a 2nd parameter as false to encrypt_with_private
and counterparts that will prevent the base 64 encoding
You can pass the data hash into ssl.decrypt_with_public which should return your original data
There are matching methods for using a public key to encrypt data to be decrypted using a private key
Attributes
Public Class Methods
# File lib/mcollective/ssl.rb 195 def self.base64_decode(string) 196 # The Base 64 character set is A-Z a-z 0-9 + / = 197 # Also allow for whitespace, but raise if we get anything else 198 if string !~ /^[A-Za-z0-9+\/=\s]+$/ 199 raise ArgumentError, 'invalid base64' 200 end 201 Base64.decode64(string) 202 end
# File lib/mcollective/ssl.rb 186 def self.base64_encode(string) 187 Base64.encode64(string) 188 end
# File lib/mcollective/ssl.rb 208 def self.md5(string) 209 Digest::MD5.hexdigest(string) 210 end
# File lib/mcollective/ssl.rb 37 def initialize(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil) 38 @public_key_file = pubkey 39 @private_key_file = privkey 40 41 @public_key = read_key(:public, pubkey) 42 @private_key = read_key(:private, privkey, passphrase) 43 44 @ssl_cipher = "aes-256-cbc" 45 @ssl_cipher = Config.instance.ssl_cipher if Config.instance.ssl_cipher 46 @ssl_cipher = cipher if cipher 47 48 raise "The supplied cipher '#{@ssl_cipher}' is not supported" unless OpenSSL::Cipher.ciphers.include?(@ssl_cipher) 49 end
Creates a RFC 4122 version 5 UUID. If string is supplied it will produce repeatable UUIDs for that string else a random 128bit string will be used from OpenSSL::BN
Code used with permission from:
https://github.com/kwilczynski/puppet-functions/blob/master/lib/puppet/parser/functions/uuid.rb
# File lib/mcollective/ssl.rb 218 def self.uuid(string=nil) 219 string ||= OpenSSL::Random.random_bytes(16).unpack('H*').shift 220 221 uuid_name_space_dns = [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8].map {|b| b.chr}.join 222 223 sha1 = Digest::SHA1.new 224 sha1.update(uuid_name_space_dns) 225 sha1.update(string) 226 227 # first 16 bytes.. 228 bytes = sha1.digest[0, 16].bytes.to_a 229 230 # version 5 adjustments 231 bytes[6] &= 0x0f 232 bytes[6] |= 0x50 233 234 # variant is DCE 1.1 235 bytes[8] &= 0x3f 236 bytes[8] |= 0x80 237 238 bytes = [4, 2, 2, 2, 6].collect do |i| 239 bytes.slice!(0, i).pack('C*').unpack('H*') 240 end 241 242 bytes.join('-') 243 end
Public Instance Methods
decrypts a string given key, iv and data
# File lib/mcollective/ssl.rb 158 def aes_decrypt(key, crypt_string) 159 cipher = OpenSSL::Cipher.new(ssl_cipher) 160 161 cipher.decrypt 162 cipher.key = key 163 cipher.pkcs5_keyivgen(key) 164 decrypted_data = cipher.update(crypt_string) + cipher.final 165 end
encrypts a string, returns a hash of key, iv and data
# File lib/mcollective/ssl.rb 144 def aes_encrypt(plain_string) 145 cipher = OpenSSL::Cipher.new(ssl_cipher) 146 cipher.encrypt 147 148 key = cipher.random_key 149 150 cipher.key = key 151 cipher.pkcs5_keyivgen(key) 152 encrypted_data = cipher.update(plain_string) + cipher.final 153 154 {:key => key, :data => encrypted_data} 155 end
base 64 decode a string
# File lib/mcollective/ssl.rb 191 def base64_decode(string) 192 SSL.base64_decode(string) 193 end
base 64 encode a string
# File lib/mcollective/ssl.rb 182 def base64_encode(string) 183 SSL.base64_encode(string) 184 end
Decrypts data, expects a hash as create with crypt_with_public
# File lib/mcollective/ssl.rb 88 def decrypt_with_private(crypted, base64=true) 89 raise "Crypted data should include a key" unless crypted.include?(:key) 90 raise "Crypted data should include data" unless crypted.include?(:data) 91 92 if base64 93 key = rsa_decrypt_with_private(base64_decode(crypted[:key])) 94 aes_decrypt(key, base64_decode(crypted[:data])) 95 else 96 key = rsa_decrypt_with_private(crypted[:key]) 97 aes_decrypt(key, crypted[:data]) 98 end 99 end
Decrypts data, expects a hash as create with crypt_with_private
# File lib/mcollective/ssl.rb 102 def decrypt_with_public(crypted, base64=true) 103 raise "Crypted data should include a key" unless crypted.include?(:key) 104 raise "Crypted data should include data" unless crypted.include?(:data) 105 106 if base64 107 key = rsa_decrypt_with_public(base64_decode(crypted[:key])) 108 aes_decrypt(key, base64_decode(crypted[:data])) 109 else 110 key = rsa_decrypt_with_public(crypted[:key]) 111 aes_decrypt(key, crypted[:data]) 112 end 113 end
Encrypts supplied data using AES and then encrypts using RSA the key and IV
Return a hash with everything optionally base 64 encoded
# File lib/mcollective/ssl.rb 73 def encrypt_with_private(plain_text, base64=true) 74 crypted = aes_encrypt(plain_text) 75 76 if base64 77 key = base64_encode(rsa_encrypt_with_private(crypted[:key])) 78 data = base64_encode(crypted[:data]) 79 else 80 key = rsa_encrypt_with_private(crypted[:key]) 81 data = crypted[:data] 82 end 83 84 {:key => key, :data => data} 85 end
Encrypts supplied data using AES and then encrypts using RSA the key and IV
Return a hash with everything optionally base 64 encoded
# File lib/mcollective/ssl.rb 55 def encrypt_with_public(plain_text, base64=true) 56 crypted = aes_encrypt(plain_text) 57 58 if base64 59 key = base64_encode(rsa_encrypt_with_public(crypted[:key])) 60 data = base64_encode(crypted[:data]) 61 else 62 key = rsa_encrypt_with_public(crypted[:key]) 63 data = crypted[:data] 64 end 65 66 {:key => key, :data => data} 67 end
# File lib/mcollective/ssl.rb 204 def md5(string) 205 SSL.md5(string) 206 end
Reads either a :public or :private key from disk, uses an optional passphrase to read the private key
# File lib/mcollective/ssl.rb 247 def read_key(type, key=nil, passphrase=nil) 248 return key if key.nil? 249 250 raise "Could not find key #{key}" unless File.exist?(key) 251 raise "#{type} key file '#{key}' is empty" if File.zero?(key) 252 253 if type == :public 254 begin 255 key = OpenSSL::PKey::RSA.new(File.read(key)) 256 rescue OpenSSL::PKey::RSAError 257 key = OpenSSL::X509::Certificate.new(File.read(key)).public_key 258 end 259 260 # Ruby < 1.9.3 had a bug where it does not correctly clear the 261 # queue of errors while reading a key. It tries various ways 262 # to read the key and each failing attempt pushes an error onto 263 # the queue. With pubkeys only the 3rd attempt pass leaving 2 264 # stale errors on the error queue. 265 # 266 # In 1.9.3 they fixed this by simply discarding the errors after 267 # every attempt. So we simulate this fix here for older rubies 268 # as without it we get SSL_read errors from the Stomp+TLS sessions 269 # 270 # We do this only on 1.8 relying on 1.9.3 to do the right thing 271 # and we do not support 1.9 less than 1.9.3 272 # 273 # See http://bugs.ruby-lang.org/issues/4550 274 OpenSSL.errors if Util.ruby_version =~ /^1.8/ 275 276 return key 277 elsif type == :private 278 return OpenSSL::PKey::RSA.new(File.read(key), passphrase) 279 else 280 raise "Can only load :public or :private keys" 281 end 282 end
Use the private key to RSA decrypt data
# File lib/mcollective/ssl.rb 123 def rsa_decrypt_with_private(crypt_string) 124 raise "No private key set" unless @private_key 125 126 @private_key.private_decrypt(crypt_string) 127 end
Use the public key to RSA decrypt data
# File lib/mcollective/ssl.rb 137 def rsa_decrypt_with_public(crypt_string) 138 raise "No public key set" unless @public_key 139 140 @public_key.public_decrypt(crypt_string) 141 end
Use the private key to RSA encrypt data
# File lib/mcollective/ssl.rb 130 def rsa_encrypt_with_private(plain_string) 131 raise "No private key set" unless @private_key 132 133 @private_key.private_encrypt(plain_string) 134 end
Use the public key to RSA encrypt data
# File lib/mcollective/ssl.rb 116 def rsa_encrypt_with_public(plain_string) 117 raise "No public key set" unless @public_key 118 119 @public_key.public_encrypt(plain_string) 120 end
Signs a string using the private key
# File lib/mcollective/ssl.rb 168 def sign(string, base64=false) 169 sig = @private_key.sign(OpenSSL::Digest::SHA1.new, string) 170 171 base64 ? base64_encode(sig) : sig 172 end
Using the public key verifies that a string was signed using the private key
# File lib/mcollective/ssl.rb 175 def verify_signature(signature, string, base64=false) 176 signature = base64_decode(signature) if base64 177 178 @public_key.verify(OpenSSL::Digest::SHA1.new, signature, string) 179 end