ec2_amitools 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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