aes_encryptor.cpp
Go to the documentation of this file.
1 /*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2017, Open Source Robotics Foundation
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of Willow Garage, Inc. nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34 
35 #include "rosbag/bag.h"
36 #include "rosbag/aes_encryptor.h"
37 #include "rosbag/gpgme_utils.h"
38 
39 #include <openssl/rand.h>
40 
42 
44 
45 namespace rosbag
46 {
47 
48 const std::string AesCbcEncryptor::GPG_USER_FIELD_NAME = "gpg_user";
49 const std::string AesCbcEncryptor::ENCRYPTED_KEY_FIELD_NAME = "encrypted_key";
50 
52 
60 static std::string encryptStringGpg(std::string& user, std::basic_string<unsigned char> const& input) {
61  gpgme_ctx_t ctx;
62  gpgme_error_t err = gpgme_new(&ctx);
63  if (err) {
64  throw BagException((boost::format("Failed to create a GPG context: %1%") % gpgme_strerror(err)).str());
65  }
66 
67  gpgme_key_t keys[2] = {NULL, NULL};
68  getGpgKey(ctx, user, keys[0]);
69  if (user == std::string("*")) {
70  user = std::string(keys[0]->uids->name);
71  }
72 
73  gpgme_data_t input_data;
74  err = gpgme_data_new_from_mem(&input_data, reinterpret_cast<const char*>(input.c_str()), input.length(), 1);
75  if (err) {
76  gpgme_release(ctx);
77  throw BagException(
78  (boost::format("Failed to encrypt string: gpgme_data_new_from_mem returned %1%") % gpgme_strerror(err)).str());
79  }
80  gpgme_data_t output_data;
81  err = gpgme_data_new(&output_data);
82  if (err) {
83  gpgme_data_release(input_data);
84  gpgme_release(ctx);
85  throw BagException(
86  (boost::format("Failed to encrypt string: gpgme_data_new returned %1%") % gpgme_strerror(err)).str());
87  }
88  err = gpgme_op_encrypt(ctx, keys, static_cast<gpgme_encrypt_flags_t>(GPGME_ENCRYPT_ALWAYS_TRUST), input_data, output_data);
89  if (err) {
90  gpgme_data_release(output_data);
91  gpgme_data_release(input_data);
92  gpgme_release(ctx);
93  throw BagException((boost::format("Failed to encrypt: %1%. Have you installed a public key %2%?") % gpgme_strerror(err) % user).str());
94  }
95  gpgme_key_release(keys[0]);
96  std::size_t output_length = gpgme_data_seek(output_data, 0, SEEK_END);
97  std::string output(output_length, 0);
98  gpgme_data_seek(output_data, 0, SEEK_SET);
99  ssize_t bytes_read = gpgme_data_read(output_data, &output[0], output_length);
100  // Release resources and return
101  gpgme_data_release(output_data);
102  gpgme_data_release(input_data);
103  gpgme_release(ctx);
104  if (-1 == bytes_read) {
105  throw BagException("Failed to read encrypted string");
106  }
107  return output;
108 }
109 
111 
118 static std::basic_string<unsigned char> decryptStringGpg(std::string const& user, std::string const& input) {
119  gpgme_ctx_t ctx;
120  gpgme_error_t err = gpgme_new(&ctx);
121  if (err) {
122  throw BagException((boost::format("Failed to create a GPG context: %1%") % gpgme_strerror(err)).str());
123  }
124 
125  gpgme_data_t input_data;
126  err = gpgme_data_new_from_mem(&input_data, input.c_str(), input.length(), 1);
127  if (err) {
128  gpgme_release(ctx);
129  throw BagException(
130  (boost::format("Failed to decrypt bag: gpgme_data_new_from_mem returned %1%") % gpgme_strerror(err)).str());
131  }
132  gpgme_data_t output_data;
133  err = gpgme_data_new(&output_data);
134  if (err) {
135  gpgme_data_release(input_data);
136  gpgme_release(ctx);
137  throw BagException(
138  (boost::format("Failed to decrypt bag: gpgme_data_new returned %1%") % gpgme_strerror(err)).str());
139  }
140  err = gpgme_op_decrypt(ctx, input_data, output_data);
141  if (err) {
142  gpgme_data_release(output_data);
143  gpgme_data_release(input_data);
144  gpgme_release(ctx);
145  throw BagException((boost::format("Failed to decrypt bag: %1%. Have you installed a private key %2%?") % gpgme_strerror(err) % user).str());
146  }
147  std::size_t output_length = gpgme_data_seek(output_data, 0, SEEK_END);
148  if (output_length != AES_BLOCK_SIZE) {
149  gpgme_data_release(output_data);
150  gpgme_data_release(input_data);
151  gpgme_release(ctx);
152  throw BagException("Decrypted string length mismatches");
153  }
154  std::basic_string<unsigned char> output(output_length, 0);
155  gpgme_data_seek(output_data, 0, SEEK_SET);
156  ssize_t bytes_read = gpgme_data_read(output_data, reinterpret_cast<char*>(&output[0]), output_length);
157  // Release resources and return
158  gpgme_data_release(output_data);
159  gpgme_data_release(input_data);
160  gpgme_release(ctx);
161  if (-1 == bytes_read) {
162  throw BagException("Failed to read decrypted symmetric key");
163  }
164  return output;
165 }
166 
167 static std::string readHeaderField(ros::M_string const& header_fields, std::string const& field_name) {
168  ros::M_string::const_iterator it = header_fields.find(field_name);
169  if (it == header_fields.end()) {
170  return std::string();
171  }
172  return it->second;
173 }
174 
175 void AesCbcEncryptor::initialize(Bag const& bag, std::string const& gpg_key_user) {
176  // GPGME must be initialized even when reading
177  initGpgme();
178  // Encryption user can be set only when writing a bag file
179  if (bag.getMode() != bagmode::Write) {
180  return;
181  }
182  if (gpg_key_user_ == gpg_key_user) {
183  return;
184  }
185  if (gpg_key_user_.empty()) {
186  gpg_key_user_ = gpg_key_user;
188  AES_set_encrypt_key(&symmetric_key_[0], AES_BLOCK_SIZE*8, &aes_encrypt_key_);
189  } else {
190  // Encryption user cannot change once set
191  throw BagException(
192  (boost::format("Encryption user has already been set to %s") % gpg_key_user_.c_str()).str());
193  }
194 }
195 
196 uint32_t AesCbcEncryptor::encryptChunk(const uint32_t chunk_size, const uint64_t chunk_data_pos, ChunkedFile& file) {
197  // Read existing (compressed) chunk
198  std::basic_string<unsigned char> compressed_chunk(chunk_size, 0);
199  file.seek(chunk_data_pos);
200  file.read((char*) &compressed_chunk[0], chunk_size);
201  // Apply PKCS#7 padding to the chunk
202  std::size_t pad_size = AES_BLOCK_SIZE - chunk_size % AES_BLOCK_SIZE;
203  compressed_chunk.resize(compressed_chunk.length() + pad_size, pad_size);
204  // Encrypt chunk
205  std::basic_string<unsigned char> encrypted_chunk(compressed_chunk.length(), 0);
206  std::basic_string<unsigned char> iv(AES_BLOCK_SIZE, 0);
207  if (!RAND_bytes(&iv[0], AES_BLOCK_SIZE)) {
208  throw BagException("Failed to build initialization vector");
209  }
210  file.seek(chunk_data_pos);
211  file.write((char*) &iv[0], AES_BLOCK_SIZE);
212  AES_cbc_encrypt(&compressed_chunk[0], &encrypted_chunk[0], encrypted_chunk.length(), &aes_encrypt_key_, &iv[0], AES_ENCRYPT);
213  // Write encrypted chunk
214  file.write((char*) &encrypted_chunk[0], encrypted_chunk.length());
215  file.truncate(chunk_data_pos + AES_BLOCK_SIZE + encrypted_chunk.length());
216  return AES_BLOCK_SIZE + encrypted_chunk.length();
217 }
218 
219 void AesCbcEncryptor::decryptChunk(ChunkHeader const& chunk_header, Buffer& decrypted_chunk, ChunkedFile& file) const {
220  // Test encrypted chunk size
221  if (chunk_header.compressed_size % AES_BLOCK_SIZE != 0) {
222  throw BagFormatException((boost::format("Error in encrypted chunk size: %d") % chunk_header.compressed_size).str());
223  }
224  // Read encrypted chunk
225  if (chunk_header.compressed_size < AES_BLOCK_SIZE) {
226  throw BagFormatException((boost::format("No initialization vector in encrypted chunk: %d") % chunk_header.compressed_size).str());
227  }
228  std::basic_string<unsigned char> iv(AES_BLOCK_SIZE, 0);
229  file.read((char*) &iv[0], AES_BLOCK_SIZE);
230  std::basic_string<unsigned char> encrypted_chunk(chunk_header.compressed_size - AES_BLOCK_SIZE, 0);
231  file.read((char*) &encrypted_chunk[0], chunk_header.compressed_size - AES_BLOCK_SIZE);
232  // Decrypt chunk
233  decrypted_chunk.setSize(chunk_header.compressed_size - AES_BLOCK_SIZE);
234  AES_cbc_encrypt(&encrypted_chunk[0], (unsigned char*) decrypted_chunk.getData(), chunk_header.compressed_size - AES_BLOCK_SIZE,
235  &aes_decrypt_key_, &iv[0], AES_DECRYPT);
236  if (decrypted_chunk.getSize() == 0) {
237  throw BagFormatException("Decrypted chunk is empty");
238  }
239  decrypted_chunk.setSize(decrypted_chunk.getSize() - *(decrypted_chunk.getData()+decrypted_chunk.getSize()-1));
240 }
241 
243  header_fields[ENCRYPTOR_FIELD_NAME] = "rosbag/AesCbcEncryptor";
244  header_fields[GPG_USER_FIELD_NAME] = gpg_key_user_;
246 }
247 
250  if (encrypted_symmetric_key_.empty()) {
251  throw BagFormatException("Encrypted symmetric key is not found in header");
252  }
254  if (gpg_key_user_.empty()) {
255  throw BagFormatException("GPG key user is not found in header");
256  }
258  AES_set_decrypt_key(&symmetric_key_[0], AES_BLOCK_SIZE*8, &aes_decrypt_key_);
259 }
260 
261 void AesCbcEncryptor::writeEncryptedHeader(boost::function<void(ros::M_string const&)>, ros::M_string const& header_fields, ChunkedFile& file) {
262  boost::shared_array<uint8_t> header_buffer;
263  uint32_t header_len;
264  ros::Header::write(header_fields, header_buffer, header_len);
265  // Apply PKCS#7 padding to the header
266  std::size_t pad_size = AES_BLOCK_SIZE - header_len % AES_BLOCK_SIZE;
267  uint32_t encrypted_buffer_size = header_len + pad_size;
268  std::basic_string<unsigned char> header_buffer_with_pad(encrypted_buffer_size, pad_size);
269  memcpy(&header_buffer_with_pad[0], header_buffer.get(), header_len);
270  // Encrypt chunk
271  std::basic_string<unsigned char> encrypted_buffer(encrypted_buffer_size, 0);
272  std::basic_string<unsigned char> iv(AES_BLOCK_SIZE, 0);
273  if (!RAND_bytes(&iv[0], AES_BLOCK_SIZE)) {
274  throw BagException("Failed to build initialization vector");
275  }
276  encrypted_buffer_size += AES_BLOCK_SIZE;
277  file.write((char*) &encrypted_buffer_size, 4);
278  encrypted_buffer_size -= AES_BLOCK_SIZE;
279  file.write((char*) &iv[0], AES_BLOCK_SIZE);
280  AES_cbc_encrypt(&header_buffer_with_pad[0], &encrypted_buffer[0], encrypted_buffer_size, &aes_encrypt_key_, &iv[0], AES_ENCRYPT);
281  // Write
282  file.write((char*) &encrypted_buffer[0], encrypted_buffer_size);
283 }
284 
285 bool AesCbcEncryptor::readEncryptedHeader(boost::function<bool(ros::Header&)>, ros::Header& header, Buffer& header_buffer, ChunkedFile& file) {
286  // Read the encrypted header length
287  uint32_t encrypted_header_len;
288  file.read((char*) &encrypted_header_len, 4);
289  if (encrypted_header_len % AES_BLOCK_SIZE != 0) {
290  throw BagFormatException((boost::format("Error in encrypted header length: %d") % encrypted_header_len).str());
291  }
292  if (encrypted_header_len < AES_BLOCK_SIZE) {
293  throw BagFormatException((boost::format("No initialization vector in encrypted header: %d") % encrypted_header_len).str());
294  }
295  // Read encrypted header
296  std::basic_string<unsigned char> iv(AES_BLOCK_SIZE, 0);
297  file.read((char*) &iv[0], AES_BLOCK_SIZE);
298  encrypted_header_len -= AES_BLOCK_SIZE;
299  std::basic_string<unsigned char> encrypted_header(encrypted_header_len, 0);
300  file.read((char*) &encrypted_header[0], encrypted_header_len);
301  // Decrypt header
302  header_buffer.setSize(encrypted_header_len);
303  AES_cbc_encrypt(&encrypted_header[0], (unsigned char*) header_buffer.getData(), encrypted_header_len, &aes_decrypt_key_, &iv[0], AES_DECRYPT);
304  if (header_buffer.getSize() == 0) {
305  throw BagFormatException("Decrypted header is empty");
306  }
307  header_buffer.setSize(header_buffer.getSize() - *(header_buffer.getData()+header_buffer.getSize()-1));
308  // Parse the header
309  std::string error_msg;
310  return header.parse(header_buffer.getData(), header_buffer.getSize(), error_msg);
311 }
312 
314  // Compose a new symmetric key for a bag file to be written
315  if (gpg_key_user_.empty()) {
316  return;
317  }
318  symmetric_key_.resize(AES_BLOCK_SIZE);
319  if (!RAND_bytes(&symmetric_key_[0], AES_BLOCK_SIZE)) {
320  throw BagException("Failed to build symmetric key");
321  }
322  // Encrypted session key is written in bag file header
324 }
325 
326 } // namespace rosbag
void read(void *ptr, size_t size)
read size bytes from the file into ptr
BagMode getMode() const
Get the mode the bag is in.
Definition: bag.cpp:189
ChunkedFile reads and writes files which contain interleaved chunks of compressed and uncompressed da...
Definition: chunked_file.h:51
void write(std::string const &s)
uint8_t * getData()
Definition: buffer.cpp:52
void initialize(Bag const &bag, std::string const &gpg_key_user)
Initialize encryptor.
Base class for rosbag exceptions.
Definition: exceptions.h:43
bool truncate(uint64_t length)
void seek(uint64_t offset, int origin=std::ios_base::beg)
seek to given offset from origin
static const std::string ENCRYPTOR_FIELD_NAME
Definition: constants.h:64
Exception thrown on problems reading the bag format.
Definition: exceptions.h:57
static std::string readHeaderField(ros::M_string const &header_fields, std::string const &field_name)
static const std::string GPG_USER_FIELD_NAME
Definition: aes_encryptor.h:48
uint32_t compressed_size
compressed size of the chunk in bytes
Definition: structures.h:72
std::basic_string< unsigned char > symmetric_key_
Definition: aes_encryptor.h:70
std::map< std::string, std::string > M_string
static std::basic_string< unsigned char > decryptStringGpg(std::string const &user, std::string const &input)
Decrypt string using GPGME.
void initGpgme()
Initialize GPGME library.
Definition: gpgme_utils.cpp:42
bool readEncryptedHeader(boost::function< bool(ros::Header &)>, ros::Header &header, Buffer &header_buffer, ChunkedFile &)
Read encrypted header from bag file.
bool parse(const boost::shared_array< uint8_t > &buffer, uint32_t size, std::string &error_msg)
static void write(const M_string &key_vals, boost::shared_array< uint8_t > &buffer, uint32_t &size)
void decryptChunk(ChunkHeader const &chunk_header, Buffer &decrypted_chunk, ChunkedFile &file) const
Decrypt chunk.
uint32_t encryptChunk(const uint32_t chunk_size, const uint64_t chunk_data_pos, ChunkedFile &file)
Encrypt chunk.
std::string encrypted_symmetric_key_
Definition: aes_encryptor.h:72
uint32_t getSize() const
Definition: buffer.cpp:54
static const std::string ENCRYPTED_KEY_FIELD_NAME
Definition: aes_encryptor.h:49
static std::string encryptStringGpg(std::string &user, std::basic_string< unsigned char > const &input)
Encrypt string using GPGME.
#define PLUGINLIB_EXPORT_CLASS(class_type, base_class_type)
void writeEncryptedHeader(boost::function< void(ros::M_string const &)>, ros::M_string const &header_fields, ChunkedFile &)
Write encrypted header to bag file.
void addFieldsToFileHeader(ros::M_string &header_fields) const
Add encryptor information to bag file header.
void setSize(uint32_t size)
Definition: buffer.cpp:56
void getGpgKey(gpgme_ctx_t &ctx, std::string const &user, gpgme_key_t &key)
Get GPG key.
Definition: gpgme_utils.cpp:53
void readFieldsFromFileHeader(ros::M_string const &header_fields)
Read encryptor information from bag file header.


rosbag_storage
Author(s): Dirk Thomas
autogenerated on Mon Feb 28 2022 23:33:55