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