Class MCollective::SSL
In: lib/mcollective/ssl.rb
Parent: Object

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

Methods

Attributes

private_key_file  [R] 
public_key_file  [R] 
ssl_cipher  [R] 

Public Class methods

[Source]

     # File lib/mcollective/ssl.rb, line 195
195:     def self.base64_decode(string)
196:       Base64.decode64(string)
197:     end

[Source]

     # File lib/mcollective/ssl.rb, line 186
186:     def self.base64_encode(string)
187:       Base64.encode64(string)
188:     end

[Source]

     # File lib/mcollective/ssl.rb, line 203
203:     def self.md5(string)
204:       Digest::MD5.hexdigest(string)
205:     end

[Source]

    # File lib/mcollective/ssl.rb, line 37
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

[Source]

     # File lib/mcollective/ssl.rb, line 213
213:     def self.uuid(string=nil)
214:       string ||= OpenSSL::Random.random_bytes(16).unpack('H*').shift
215: 
216:       uuid_name_space_dns = "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"
217: 
218:       sha1 = Digest::SHA1.new
219:       sha1.update(uuid_name_space_dns)
220:       sha1.update(string)
221: 
222:       # first 16 bytes..
223:       bytes = sha1.digest[0, 16].bytes.to_a
224: 
225:       # version 5 adjustments
226:       bytes[6] &= 0x0f
227:       bytes[6] |= 0x50
228: 
229:       # variant is DCE 1.1
230:       bytes[8] &= 0x3f
231:       bytes[8] |= 0x80
232: 
233:       bytes = [4, 2, 2, 2, 6].collect do |i|
234:         bytes.slice!(0, i).pack('C*').unpack('H*')
235:       end
236: 
237:       bytes.join('-')
238:     end

Public Instance methods

decrypts a string given key, iv and data

[Source]

     # File lib/mcollective/ssl.rb, line 158
158:     def aes_decrypt(key, crypt_string)
159:       cipher = OpenSSL::Cipher::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

[Source]

     # File lib/mcollective/ssl.rb, line 144
144:     def aes_encrypt(plain_string)
145:       cipher = OpenSSL::Cipher::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

[Source]

     # File lib/mcollective/ssl.rb, line 191
191:     def base64_decode(string)
192:       SSL.base64_decode(string)
193:     end

base 64 encode a string

[Source]

     # File lib/mcollective/ssl.rb, line 182
182:     def base64_encode(string)
183:       SSL.base64_encode(string)
184:     end

Decrypts data, expects a hash as create with crypt_with_public

[Source]

    # File lib/mcollective/ssl.rb, line 88
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

[Source]

     # File lib/mcollective/ssl.rb, line 102
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

[Source]

    # File lib/mcollective/ssl.rb, line 73
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

[Source]

    # File lib/mcollective/ssl.rb, line 55
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

[Source]

     # File lib/mcollective/ssl.rb, line 199
199:     def md5(string)
200:       SSL.md5(string)
201:     end

Reads either a :public or :private key from disk, uses an optional passphrase to read the private key

[Source]

     # File lib/mcollective/ssl.rb, line 242
242:     def read_key(type, key=nil, passphrase=nil)
243:       return key if key.nil?
244: 
245:       raise "Could not find key #{key}" unless File.exist?(key)
246:       raise "#{type} key file '#{key}' is empty" if File.zero?(key)
247: 
248:       if type == :public
249:         begin
250:           key = OpenSSL::PKey::RSA.new(File.read(key))
251:         rescue OpenSSL::PKey::RSAError
252:           key = OpenSSL::X509::Certificate.new(File.read(key)).public_key
253:         end
254: 
255:         # Ruby < 1.9.3 had a bug where it does not correctly clear the
256:         # queue of errors while reading a key.  It tries various ways
257:         # to read the key and each failing attempt pushes an error onto
258:         # the queue.  With pubkeys only the 3rd attempt pass leaving 2
259:         # stale errors on the error queue.
260:         #
261:         # In 1.9.3 they fixed this by simply discarding the errors after
262:         # every attempt.  So we simulate this fix here for older rubies
263:         # as without it we get SSL_read errors from the Stomp+TLS sessions
264:         #
265:         # We do this only on 1.8 relying on 1.9.3 to do the right thing
266:         # and we do not support 1.9 less than 1.9.3
267:         #
268:         # See  http://bugs.ruby-lang.org/issues/4550
269:         OpenSSL.errors if Util.ruby_version =~ /^1.8/
270: 
271:         return key
272:       elsif type == :private
273:         return OpenSSL::PKey::RSA.new(File.read(key), passphrase)
274:       else
275:         raise "Can only load :public or :private keys"
276:       end
277:     end

Use the private key to RSA decrypt data

[Source]

     # File lib/mcollective/ssl.rb, line 123
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

[Source]

     # File lib/mcollective/ssl.rb, line 137
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

[Source]

     # File lib/mcollective/ssl.rb, line 130
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

[Source]

     # File lib/mcollective/ssl.rb, line 116
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

[Source]

     # File lib/mcollective/ssl.rb, line 168
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

[Source]

     # File lib/mcollective/ssl.rb, line 175
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

[Validate]