ec2_amitools 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +54 -0
  3. data/bin/console +14 -0
  4. data/bin/ec2-ami-tools-version +6 -0
  5. data/bin/ec2-bundle-image +6 -0
  6. data/bin/ec2-bundle-vol +6 -0
  7. data/bin/ec2-delete-bundle +6 -0
  8. data/bin/ec2-download-bundle +6 -0
  9. data/bin/ec2-migrate-bundle +6 -0
  10. data/bin/ec2-migrate-manifest +6 -0
  11. data/bin/ec2-unbundle +6 -0
  12. data/bin/ec2-upload-bundle +6 -0
  13. data/bin/setup +8 -0
  14. data/etc/ec2/amitools/cert-ec2-cn-north-1.pem +28 -0
  15. data/etc/ec2/amitools/cert-ec2-gov.pem +17 -0
  16. data/etc/ec2/amitools/cert-ec2.pem +23 -0
  17. data/etc/ec2/amitools/mappings.csv +9 -0
  18. data/lib/ec2/amitools/bundle.rb +251 -0
  19. data/lib/ec2/amitools/bundle_base.rb +58 -0
  20. data/lib/ec2/amitools/bundleimage.rb +94 -0
  21. data/lib/ec2/amitools/bundleimageparameters.rb +42 -0
  22. data/lib/ec2/amitools/bundlemachineparameters.rb +60 -0
  23. data/lib/ec2/amitools/bundleparameters.rb +120 -0
  24. data/lib/ec2/amitools/bundlevol.rb +240 -0
  25. data/lib/ec2/amitools/bundlevolparameters.rb +164 -0
  26. data/lib/ec2/amitools/crypto.rb +379 -0
  27. data/lib/ec2/amitools/decryptmanifest.rb +20 -0
  28. data/lib/ec2/amitools/defaults.rb +12 -0
  29. data/lib/ec2/amitools/deletebundle.rb +212 -0
  30. data/lib/ec2/amitools/deletebundleparameters.rb +78 -0
  31. data/lib/ec2/amitools/downloadbundle.rb +161 -0
  32. data/lib/ec2/amitools/downloadbundleparameters.rb +84 -0
  33. data/lib/ec2/amitools/exception.rb +86 -0
  34. data/lib/ec2/amitools/fileutil.rb +219 -0
  35. data/lib/ec2/amitools/format.rb +127 -0
  36. data/lib/ec2/amitools/instance-data.rb +97 -0
  37. data/lib/ec2/amitools/manifest_wrapper.rb +132 -0
  38. data/lib/ec2/amitools/manifestv20070829.rb +361 -0
  39. data/lib/ec2/amitools/manifestv20071010.rb +403 -0
  40. data/lib/ec2/amitools/manifestv3.rb +331 -0
  41. data/lib/ec2/amitools/mapids.rb +148 -0
  42. data/lib/ec2/amitools/migratebundle.rb +222 -0
  43. data/lib/ec2/amitools/migratebundleparameters.rb +173 -0
  44. data/lib/ec2/amitools/migratemanifest.rb +225 -0
  45. data/lib/ec2/amitools/migratemanifestparameters.rb +118 -0
  46. data/lib/ec2/amitools/minimalec2.rb +116 -0
  47. data/lib/ec2/amitools/parameter_exceptions.rb +34 -0
  48. data/lib/ec2/amitools/parameters_base.rb +168 -0
  49. data/lib/ec2/amitools/region.rb +93 -0
  50. data/lib/ec2/amitools/s3toolparameters.rb +183 -0
  51. data/lib/ec2/amitools/showversion.rb +12 -0
  52. data/lib/ec2/amitools/syschecks.rb +27 -0
  53. data/lib/ec2/amitools/tool_base.rb +224 -0
  54. data/lib/ec2/amitools/unbundle.rb +107 -0
  55. data/lib/ec2/amitools/unbundleparameters.rb +65 -0
  56. data/lib/ec2/amitools/uploadbundle.rb +361 -0
  57. data/lib/ec2/amitools/uploadbundleparameters.rb +108 -0
  58. data/lib/ec2/amitools/util.rb +532 -0
  59. data/lib/ec2/amitools/version.rb +33 -0
  60. data/lib/ec2/amitools/xmlbuilder.rb +237 -0
  61. data/lib/ec2/amitools/xmlutil.rb +55 -0
  62. data/lib/ec2/common/constants.rb +16 -0
  63. data/lib/ec2/common/curl.rb +110 -0
  64. data/lib/ec2/common/headers.rb +95 -0
  65. data/lib/ec2/common/headersv4.rb +173 -0
  66. data/lib/ec2/common/http.rb +333 -0
  67. data/lib/ec2/common/s3support.rb +231 -0
  68. data/lib/ec2/common/signature.rb +68 -0
  69. data/lib/ec2/oem/LICENSE.txt +58 -0
  70. data/lib/ec2/oem/open4.rb +399 -0
  71. data/lib/ec2/platform/base/architecture.rb +26 -0
  72. data/lib/ec2/platform/base/constants.rb +54 -0
  73. data/lib/ec2/platform/base/pipeline.rb +181 -0
  74. data/lib/ec2/platform/base.rb +57 -0
  75. data/lib/ec2/platform/current.rb +55 -0
  76. data/lib/ec2/platform/linux/architecture.rb +35 -0
  77. data/lib/ec2/platform/linux/constants.rb +23 -0
  78. data/lib/ec2/platform/linux/fstab.rb +99 -0
  79. data/lib/ec2/platform/linux/identity.rb +16 -0
  80. data/lib/ec2/platform/linux/image.rb +811 -0
  81. data/lib/ec2/platform/linux/mtab.rb +74 -0
  82. data/lib/ec2/platform/linux/pipeline.rb +40 -0
  83. data/lib/ec2/platform/linux/rsync.rb +114 -0
  84. data/lib/ec2/platform/linux/tar.rb +124 -0
  85. data/lib/ec2/platform/linux/uname.rb +50 -0
  86. data/lib/ec2/platform/linux.rb +83 -0
  87. data/lib/ec2/platform/solaris/architecture.rb +28 -0
  88. data/lib/ec2/platform/solaris/constants.rb +30 -0
  89. data/lib/ec2/platform/solaris/fstab.rb +43 -0
  90. data/lib/ec2/platform/solaris/identity.rb +16 -0
  91. data/lib/ec2/platform/solaris/image.rb +327 -0
  92. data/lib/ec2/platform/solaris/mtab.rb +29 -0
  93. data/lib/ec2/platform/solaris/pipeline.rb +40 -0
  94. data/lib/ec2/platform/solaris/rsync.rb +24 -0
  95. data/lib/ec2/platform/solaris/tar.rb +36 -0
  96. data/lib/ec2/platform/solaris/uname.rb +21 -0
  97. data/lib/ec2/platform/solaris.rb +38 -0
  98. data/lib/ec2/platform.rb +69 -0
  99. data/lib/ec2/version.rb +8 -0
  100. data/lib/ec2_amitools +1 -0
  101. data/lib/ec2_amitools.rb +7 -0
  102. metadata +184 -0
@@ -0,0 +1,164 @@
1
+ # Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
2
+ # Reserved. Licensed under the Amazon Software License (the
3
+ # "License"). You may not use this file except in compliance with the
4
+ # License. A copy of the License is located at
5
+ # http://aws.amazon.com/asl or in the "license" file accompanying this
6
+ # file. This file is distributed on an "AS IS" BASIS, WITHOUT
7
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
8
+ # the License for the specific language governing permissions and
9
+ # limitations under the License.
10
+
11
+ require 'ec2/amitools/bundlemachineparameters'
12
+
13
+ # The Bundle Volume command line parameters.
14
+ class BundleVolParameters < BundleMachineParameters
15
+
16
+ PREFIX_DESCRIPTION = "The filename prefix for bundled AMI files. Defaults to \"image\"."
17
+ EXCLUDE_DESCRIPTION = ["A comma-separated list of absolute directory paths, relative to",
18
+ "the volume being bundled, to exclude. This option overrides the",
19
+ "\"--all\" option."]
20
+ INCLUDE_DESCRIPTION = ["Linux Only. A comma-separated list of absolute file paths, relative",
21
+ "to the volume being bundled, to include. This option overrides the",
22
+ "default filtered files list."]
23
+ FILTER_DESCRIPTION = "Do not use the default filtered files list."
24
+ CLONE_VOLUME_DESCRIPTION = 'Clone volume into an image but do not generate a bundle.'
25
+ ALL_DESCRIPTION = ["Include all directories in the volume being bundled, including those",
26
+ "on remotely mounted filesystems."]
27
+ SIZE_DESCRIPTION = ["The size, in MB (1024 * 1024 bytes), of the image file to create.",
28
+ "The maximum size is 10240 MB."]
29
+ VOLUME_DESCRIPTION = "The absolute path to the mounted volume to be bundled. Defaults to \"/\"."
30
+ FSTAB_DESCRIPTION = "The absolute path to the fstab to be bundled into the image."
31
+ GENERATE_FSTAB_DESCRIPTION= ["Inject a generated EC2 fstab. (Only use this if you are not rebundling",
32
+ "an existing instance.)"]
33
+ GRUB_CONFIG_DESCRIPTION = "The absolute path to the grub config to be bundled into the image."
34
+ PARTITION_TYPE_DESCRIPTION= ["Create a disk image using a partition table. Optionally, specify the",
35
+ "partition table type to use. Viable options are 'mbr' or 'gpt'. If no",
36
+ "partition table type is specified, the type used on the parent block",
37
+ "device of the specified volume is used, if applicable, otherwise, we",
38
+ "default to using 'gpt'. You may also specify 'none' to disable the use",
39
+ "of partition tables, altogether." ]
40
+ SCRIPT_DESCRIPTION = ['Executable customization script to call after cloning volume. The file',
41
+ 'must expect a single argument: the mount point of the cloned volume.']
42
+ INHERIT_DESCRIPTION = ['Inherit instance metadata. Enabled by default.',
43
+ 'Bundling will fail if inherit is enabled but instance data',
44
+ 'is not accessible, for example not bundling an EC2 instance.']
45
+
46
+ attr_accessor :all,
47
+ :exclude,
48
+ :includes,
49
+ :filter,
50
+ :prefix,
51
+ :size,
52
+ :volume,
53
+ :fstab,
54
+ :part_type,
55
+ :inherit,
56
+ :clone_only,
57
+ :script,
58
+ :generate_fstab,
59
+ :grub_config
60
+
61
+ def optional_params()
62
+ super()
63
+ on('-a', '--all', *ALL_DESCRIPTION) do
64
+ @all = true
65
+ end
66
+
67
+ on('-e', '--exclude DIR1,DIR2,...', Array, *EXCLUDE_DESCRIPTION) do |p|
68
+ @exclude = p
69
+ end
70
+
71
+ on('-i', '--include FILE1,FILE2,...', Array, *INCLUDE_DESCRIPTION) do |p|
72
+ @includes = p
73
+ end
74
+
75
+ on('--no-filter', FILTER_DESCRIPTION) do
76
+ @filter = false
77
+ end
78
+
79
+ on('-p', '--prefix PREFIX', String, PREFIX_DESCRIPTION) do |prefix|
80
+ assert_good_key(prefix, '--prefix')
81
+ @prefix = prefix
82
+ end
83
+
84
+ on('--clone-only', CLONE_VOLUME_DESCRIPTION) do
85
+ @clone_only = true
86
+ end
87
+
88
+ on('-s', '--size MB', Integer, *SIZE_DESCRIPTION) do |p|
89
+ @size = p
90
+ end
91
+
92
+ on('--[no-]inherit', *INHERIT_DESCRIPTION) do |p|
93
+ @inherit = p
94
+ end
95
+
96
+ on('-v', '--volume PATH', String, VOLUME_DESCRIPTION) do |volume|
97
+ assert_directory_exists(volume, '--volume')
98
+ @volume = volume
99
+ end
100
+
101
+ on('--fstab PATH', String, FSTAB_DESCRIPTION) do |fstab|
102
+ assert_file_exists(fstab, '--fstab')
103
+ @fstab = fstab
104
+ end
105
+
106
+ on('--grub-config PATH', String, GRUB_CONFIG_DESCRIPTION) do |grub_config|
107
+ assert_file_exists(grub_config, '--grub-config')
108
+ @grub_config = grub_config
109
+ end
110
+
111
+ on('-P', '--partition TYPE', EC2::Platform::PartitionType.list,
112
+ *PARTITION_TYPE_DESCRIPTION) do |ptype|
113
+ @part_type = ptype
114
+ end
115
+
116
+ on('-S', '--script SCRIPT', String, *SCRIPT_DESCRIPTION) do |scriptfile|
117
+ assert_file_executable(scriptfile, '--script')
118
+ @script = scriptfile
119
+ end
120
+
121
+ on('--generate-fstab', *GENERATE_FSTAB_DESCRIPTION) do
122
+ @generate_fstab = true
123
+ end
124
+ end
125
+
126
+ def validate_params()
127
+ raise InvalidCombination.new("--fstab", "--generate-fstab") if @fstab and @generate_fstab
128
+
129
+ if @exclude
130
+ volume = @volume || '/'
131
+ @exclude.each do |dir|
132
+ path = File::join(volume, dir)
133
+ assert_glob_expands(path, '--exclude')
134
+ end
135
+ end
136
+
137
+ if @includes
138
+ volume = @volume || '/'
139
+ @includes.each do |file|
140
+ path = File::join(volume, file)
141
+ assert_glob_expands(path, '--include')
142
+ end
143
+ end
144
+
145
+ super()
146
+ end
147
+
148
+ def set_defaults()
149
+ super()
150
+ @inherit = true if @inherit.nil? # false means a parameter was provided.
151
+ @exclude ||= []
152
+ @includes ||= []
153
+ @filter = true if @filter.nil?
154
+ @prefix ||= 'image'
155
+ @size ||= MAX_SIZE_MB
156
+ @volume ||= '/'
157
+ @clone_only ||= false
158
+ @part_type ||= :auto
159
+ if @generate_fstab
160
+ @fstab = :default
161
+ @fstab = :legacy if @arch == "i386"
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,379 @@
1
+ # Copyright 2008-2014 Amazon.com, Inc. or its affiliates. All Rights
2
+ # Reserved. Licensed under the Amazon Software License (the
3
+ # "License"). You may not use this file except in compliance with the
4
+ # License. A copy of the License is located at
5
+ # http://aws.amazon.com/asl or in the "license" file accompanying this
6
+ # file. This file is distributed on an "AS IS" BASIS, WITHOUT
7
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
8
+ # the License for the specific language governing permissions and
9
+ # limitations under the License.
10
+
11
+ require 'ec2/amitools/format'
12
+ require 'digest/sha1'
13
+ require 'openssl'
14
+ require 'stringio'
15
+
16
+ ###
17
+ # Cryptographic utilities module.
18
+ #
19
+ module Crypto
20
+ BUFFER_SIZE = 1024 * 1024
21
+ ASYM_ALG = 'RSA'
22
+ SYM_ALG = 'AES-128-CBC'
23
+ DIGEST_ALG = 'SHA1'
24
+ PADDING = OpenSSL::PKey::RSA::PKCS1_PADDING
25
+ VERSION1 = 1
26
+ VERSION2 = 2
27
+ SHA1_FINGERPRINT_REGEX = /([a-f0-9]{2}(:[a-f0-9]{2}){15})/i
28
+
29
+ #----------------------------------------------------------------------------#
30
+
31
+ ##
32
+ # Decrypt the specified cipher text according to the AMI Manifest Encryption
33
+ # Scheme Version 1 or 2.
34
+ #
35
+ # ((|cipher_text|)) The cipher text to decrypt.
36
+ # ((|keyio_or_keyfilename|)) The key data IO stream or the name of the private
37
+ # key file.
38
+ #
39
+ def Crypto.decryptasym(cipher_text, keyfilename)
40
+ raise ArgumentError.new('cipher_text') unless cipher_text
41
+ raise ArgumentError.new('keyfilename') unless keyfilename and FileTest.exists? keyfilename
42
+
43
+ # Load key.
44
+ privkey = File.open(keyfilename, 'r') { |f| OpenSSL::PKey::RSA.new(f) }
45
+
46
+ # Get version.
47
+ version = cipher_text[0]
48
+ if version == VERSION2
49
+ return Crypto.decryptasym_v2( cipher_text, keyfilename )
50
+ end
51
+ raise ArgumentError.new("invalid encryption scheme versionb: #{version}") unless version == 1
52
+
53
+ # Decrypt and extract encrypted symmetric key and initialization vector.
54
+ symkey_cryptogram_len = cipher_text.slice(1, 2).unpack('C')[0]
55
+ symkey_cryptogram = privkey.private_decrypt(
56
+ cipher_text.slice(2, symkey_cryptogram_len),
57
+ PADDING)
58
+ symkey = symkey_cryptogram.slice(0, 16)
59
+ iv = symkey_cryptogram.slice(16, 16)
60
+
61
+ # Decrypt data with the symmetric key.
62
+ cryptogram = cipher_text.slice(2 + symkey_cryptogram_len..cipher_text.size)
63
+ decryptsym(cryptogram, symkey, iv)
64
+ end
65
+
66
+ #----------------------------------------------------------------------------#
67
+
68
+ ##
69
+ # Decrypt the specified cipher text according to the AMI Manifest Encryption
70
+ # Scheme Version 2.
71
+ #
72
+ # ((|cipher_text|)) The cipher text to decrypt.
73
+ # ((|keyio_or_keyfilename|)) The key data IO stream or the name of the private
74
+ # key file.
75
+ #
76
+ def Crypto.decryptasym_v2(cipher_text, keyfilename)
77
+ raise ArgumentError.new('cipher_text') unless cipher_text
78
+ raise ArgumentError.new('keyfilename') unless keyfilename and FileTest.exists? keyfilename
79
+
80
+ # Load key.
81
+ privkey = File.open(keyfilename, 'r') { |f| OpenSSL::PKey::RSA.new(f) }
82
+
83
+ # Get version.
84
+ version = cipher_text[0]
85
+ raise ArgumentError.new("invalid encryption scheme versionb: #{version}") unless version == VERSION2
86
+
87
+ # Decrypt and extract encrypted symmetric key and initialization vector.
88
+ hi_byte, lo_byte = cipher_text.slice(1, 3).unpack('CC')
89
+ symkey_cryptogram_len = ( hi_byte << 8 ) | lo_byte
90
+ symkey_cryptogram = privkey.private_decrypt(
91
+ cipher_text.slice(3, symkey_cryptogram_len),
92
+ PADDING)
93
+
94
+ symkey = symkey_cryptogram.slice(0, 16)
95
+ iv = symkey_cryptogram.slice(16, 16)
96
+
97
+ # Decrypt data with the symmetric key.
98
+ cryptogram = cipher_text.slice( ( 3 + symkey_cryptogram_len )..cipher_text.size)
99
+ decryptsym(cryptogram, symkey, iv)
100
+ end
101
+
102
+ #----------------------------------------------------------------------------#
103
+
104
+ ##
105
+ # Asymmetrically encrypt the specified data using the AMI Manifest Encryption
106
+ # Scheme Version 2.
107
+ #
108
+ # The data is encrypted with an ephemeral symmetric key and initialization
109
+ # vector. The symmetric key and initialization vector are encrypted with the
110
+ # specified public key and preprended to the data.
111
+ #
112
+ # ((|data|)) The data to encrypt.
113
+ # ((|pubkey|)) The public key.
114
+ #
115
+ def Crypto.encryptasym(data, pubkey)
116
+ raise ArgumentError.new('data') unless data
117
+ raise ArgumentError.new('pubkey') unless pubkey
118
+
119
+ symkey = gensymkey
120
+ iv = geniv
121
+ symkey_cryptogram = pubkey.public_encrypt( symkey + iv, PADDING )
122
+
123
+ data_cryptogram = encryptsym(data, symkey, iv)
124
+
125
+ hi_byte, lo_byte = Format.int2int16(symkey_cryptogram.size)
126
+
127
+ Format::int2byte(VERSION2) + hi_byte + lo_byte + symkey_cryptogram + data_cryptogram
128
+ end
129
+
130
+ #----------------------------------------------------------------------------#
131
+
132
+ ##
133
+ # Verify the authenticity of the data from the IO stream or string ((|data|))
134
+ # using the signature ((|sig|)) and the public key ((|pubkey|)).
135
+ #
136
+ # Return true iff the signature is valid.
137
+ #
138
+ def Crypto.authenticate(data, sig, pubkey)
139
+ raise ArgumentError.new("Invalid parameter data") if data.nil?
140
+ raise ArgumentError.new("Invalid parameter sig") if sig.nil? or sig.length==0
141
+ raise ArgumentError.new("Invalid parameter pubkey") if pubkey.nil?
142
+
143
+ # Create IO stream if necessary.
144
+ io = (data.instance_of?(StringIO) ? data : StringIO.new(data))
145
+
146
+ sha = OpenSSL::Digest::SHA1.new
147
+ res = false
148
+ while not (io.eof?)
149
+ res = pubkey.verify(sha, sig, io.read(BUFFER_SIZE))
150
+ end
151
+ res
152
+ end
153
+
154
+ #----------------------------------------------------------------------------#
155
+
156
+ ##
157
+ # Decrypt the specified cipher text file to create the specified plain text
158
+ # file.
159
+ #
160
+ # The symmetric cipher is AES in CBC mode. 128 bit keys are used. If the plain
161
+ # text file already exists it will be overwritten.
162
+ #
163
+ # ((|src|)) The name of the cipher text file to decrypt.
164
+ # ((|dst|)) The name of the plain text file to create.
165
+ # ((|key|)) The 128 bit (16 byte) symmetric key.
166
+ # ((|iv|)) The 128 bit (16 byte) initialization vector.
167
+ #
168
+ def Crypto.decryptfile(src, dst, key, iv)
169
+ raise ArgumentError.new("invalid file name: #{src}") unless FileTest.exists?(src)
170
+ raise ArgumentError.new("invalid key") unless key and key.size == 16
171
+ raise ArgumentError.new("invalid iv") unless iv and iv.size == 16
172
+ pio = IO.popen( "openssl enc -d -aes-128-cbc -in #{src} -out #{dst} -K #{Format::bin2hex(key)} -iv #{Format::bin2hex(iv)} 2>&1" )
173
+ result = pio.read
174
+ pio.close
175
+ raise "error decrypting file #{src}: #{result}" if result.strip != ''
176
+ end
177
+
178
+ #----------------------------------------------------------------------------#
179
+
180
+ ##
181
+ # Decrypt _ciphertext_ using _key_ and _iv_ using AES-128-CBC.
182
+ #
183
+ def Crypto.decryptsym(plaintext, key, iv)
184
+ raise ArgumentError.new("plaintext must be a String") unless plaintext.is_a? String
185
+ raise ArgumentError.new("invalid key") unless key.is_a? String and key.size == 16
186
+ raise ArgumentError.new("invalid iv") unless iv.is_a? String and iv.size == 16
187
+
188
+ cipher = OpenSSL::Cipher::Cipher.new( 'AES-128-CBC' )
189
+ cipher.decrypt( key, iv )
190
+ # NOTE: If the key and iv aren't set this doesn't work correctly.
191
+ cipher.key = key
192
+ cipher.iv = iv
193
+ plaintext = cipher.update( plaintext )
194
+ plaintext + cipher.final
195
+ end
196
+
197
+ #----------------------------------------------------------------------------#
198
+
199
+ ##
200
+ # Generate and return a message digest for the data from the IO stream
201
+ # ((|io|)), using the algorithm alg
202
+ #
203
+ def Crypto.digest(io, alg = OpenSSL::Digest::SHA1.new)
204
+ raise ArgumentError.new('io') unless io.kind_of?(IO) or io.kind_of?(StringIO)
205
+ while not io.eof?
206
+ alg.update(io.read(BUFFER_SIZE))
207
+ end
208
+ alg.digest
209
+ end
210
+
211
+ #----------------------------------------------------------------------------#
212
+
213
+ # Return the HMAC SHA1 of _data_ using _key_.
214
+ def Crypto.hmac_sha1( key, data )
215
+ raise ParameterError.new( "key must be a String" ) unless key.is_a? String
216
+ raise ParameterError.new( "data must be a String" ) unless data.is_a? String
217
+
218
+ md = OpenSSL::Digest::SHA1.new
219
+ hmac = OpenSSL::HMAC.new( key, md)
220
+ hmac.update( data )
221
+ return hmac.digest
222
+ end
223
+
224
+ #----------------------------------------------------------------------------#
225
+
226
+ ##
227
+ # Decrypt the specified cipher text file to create the specified plain text
228
+ # file.
229
+ #
230
+ # The symmetric cipher is AES in CBC mode. 128 bit keys are used. If the plain
231
+ # text file already exists it will be overwritten.
232
+ #
233
+ # ((|key|)) The 128 bit (16 byte) symmetric key.
234
+ # ((|src|)) The name of the cipher text file to encrypt.
235
+ # ((|dst|)) The name of the plain text file to create.
236
+ # ((|iv|)) The 128 bit (16 byte) initialization vector.
237
+ #
238
+ def Crypto.encryptfile(src, dst, key, iv)
239
+ raise ArgumentError.new("invalid file name: #{src}") unless FileTest.exists?(src)
240
+ raise ArgumentError.new("invalid key") unless key and key.size == 16
241
+ raise ArgumentError.new("invalid iv") unless iv and iv.size == 16
242
+ cmd = "openssl enc -e -aes-128-cbc -in #{src} -out #{dst} -K #{Format::bin2hex(key)} -iv #{Format::bin2hex(iv)}"
243
+ result = Kernel::system(cmd)
244
+ raise "error encrypting file #{src}" unless result
245
+ end
246
+
247
+ #----------------------------------------------------------------------------#
248
+
249
+ ##
250
+ # Encrypt _plaintext_ with _key_ and _iv_ using AES-128-CBC.
251
+ #
252
+ def Crypto.encryptsym(plaintext, key, iv)
253
+ raise ArgumentError.new("plaintext must be a String") unless plaintext.kind_of? String
254
+ raise ArgumentError.new("invalid key") unless ( key.is_a? String and key.size == 16 )
255
+ raise ArgumentError.new("invalid iv") unless ( iv.is_a? String and iv.size == 16 )
256
+
257
+ cipher = OpenSSL::Cipher::Cipher.new( 'AES-128-CBC' )
258
+ cipher.encrypt( key, iv )
259
+ # NOTE: If the key and iv aren't set this doesn't work correctly.
260
+ cipher.key = key
261
+ cipher.iv = iv
262
+ ciphertext = cipher.update( plaintext )
263
+ ciphertext + cipher.final
264
+ end
265
+
266
+ #----------------------------------------------------------------------------#
267
+
268
+ ##
269
+ # Generate an initialization vector suitable use with symmetric cipher.
270
+ #
271
+ def Crypto.geniv
272
+ OpenSSL::Cipher::Cipher.new(SYM_ALG).random_iv
273
+ end
274
+
275
+ #----------------------------------------------------------------------------#
276
+
277
+ ##
278
+ # Generate a key suitable for use with a symmetric cipher.
279
+ #
280
+ def Crypto.gensymkey
281
+ OpenSSL::Cipher::Cipher.new(SYM_ALG).random_key
282
+ end
283
+
284
+ #----------------------------------------------------------------------------#
285
+
286
+ ##
287
+ # Return the public key from the X509 certificate file ((|filename|)).
288
+ #
289
+ def Crypto.certfile2pubkey(filename)
290
+ begin
291
+ File.open(filename) do |f|
292
+ return cert2pubkey(f)
293
+ end
294
+ rescue Exception => e
295
+ raise "error reading certificate file #{filename}: #{e.message}"
296
+ end
297
+ end
298
+
299
+ #----------------------------------------------------------------------------#
300
+
301
+ def Crypto.cert2pubkey(data)
302
+ begin
303
+ return OpenSSL::X509::Certificate.new(data).public_key
304
+ rescue Exception => e
305
+ raise "error reading certificate: #{e.message}"
306
+ end
307
+ end
308
+
309
+ #----------------------------------------------------------------------------#
310
+
311
+ ##
312
+ # Sign the data from IO stream or string ((|data|)) using the key in
313
+ # ((|keyfilename|)).
314
+ #
315
+ # Return the signature.
316
+ #
317
+ def Crypto.sign(data, keyfilename)
318
+ raise ArgumentError.new('data') unless data
319
+ raise ArgumentError.new("invalid file name: #{keyfilename}") unless FileTest.exists?(keyfilename)
320
+
321
+ # Create an IO stream from the data if necessary.
322
+ io = (data.instance_of?(StringIO) ? data : StringIO.new(data))
323
+
324
+ sha = OpenSSL::Digest::SHA1.new
325
+ pk = loadprivkey( keyfilename )
326
+ return pk.sign(sha, io.read )
327
+ end
328
+
329
+ #------------------------------------------------------------------------------#
330
+
331
+ ##
332
+ # Generate the SHA1 fingerprint for a PEM-encoded certificate (NOT private key)
333
+ # Returns the fingerprint in aa:bb:... form
334
+ # Raises ArgumentError if the fingerprint cannot be obtained
335
+ #
336
+ def Crypto.cert_sha1_fingerprint(cert_filename)
337
+ raise ArgumentError.new('cert_filename is nil') if cert_filename.nil?
338
+ raise ArgumentError.new("invalid cert file name: #{cert_filename}") unless FileTest.exists?(cert_filename)
339
+ fingerprint = nil
340
+
341
+ IO.popen("openssl x509 -in #{cert_filename} -noout -sha1 -fingerprint") do |io|
342
+ out = io.read
343
+ md = SHA1_FINGERPRINT_REGEX.match(out)
344
+ if md
345
+ fingerprint = md[1]
346
+ end
347
+ end
348
+
349
+ raise ArgumentError.new("could not generate fingerprint for #{cert_filename}") if fingerprint.nil?
350
+
351
+ return fingerprint
352
+ end
353
+
354
+ #------------------------------------------------------------------------------#
355
+
356
+ def Crypto.loadprivkey filename
357
+ begin
358
+ OpenSSL::PKey::RSA.new( File.open( filename,'r' ) )
359
+ rescue Exception => e
360
+ raise "error reading private key from file #{filename}: #{e.message}"
361
+ end
362
+ end
363
+
364
+ #----------------------------------------------------------------------------#
365
+
366
+ ##
367
+ # XOR the byte string ((|a|)) with the byte string ((|b|)). The operans must
368
+ # be of the same length.
369
+ #
370
+ def Crypto.xor(a, b)
371
+ raise ArgumentError.new('data lengths differ') unless a.size == b.size
372
+ xored = String.new
373
+ a.size.times do |i|
374
+ xored << (a[i] ^ b[i])
375
+ end
376
+
377
+ xored
378
+ end
379
+ end