File encryption method reading whole files into a dynamic stack buffer
[platform/core/appfw/wgt-backend.git] / src / wgt / step / encryption / step_encrypt_resources.cc
1 // Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
2 // Use of this source code is governed by a apache 2.0 license that can be
3 // found in the LICENSE file.
4
5 #include "wgt/step/encryption/step_encrypt_resources.h"
6
7 #include <web_app_enc.h>
8
9 #include <boost/filesystem/operations.hpp>
10 #include <boost/system/error_code.hpp>
11
12 #include <common/utils/file_util.h>
13 #include <common/utils/byte_size_literals.h>
14
15 #include <manifest_parser/utils/logging.h>
16 #include <sys/stat.h>
17
18 #include <algorithm>
19 #include <cstdio>
20 #include <cstdlib>
21 #include <set>
22 #include <string>
23
24 namespace {
25
26 const std::size_t kEncryptionChunkMaxSize = 8_kB; // bytes
27 const std::set<std::string> encryptSet { ".html", ".htm", ".css", ".js"};
28
29 FILE* OpenFile(const std::string& path, const std::string& mode) {
30   FILE* result = nullptr;
31
32   do {
33     result = fopen(path.c_str(), mode.c_str());
34   } while ((nullptr == result));
35
36   return result;
37 }
38
39 std::size_t ReadBytes(unsigned char* buffer, std::size_t count, FILE* stream) {
40   std::size_t result = std::fread(buffer,
41                                   1,
42                                   count,
43                                   stream);
44   if (result != count) {
45     if (0 != std::ferror(stream)) {
46       LOG(ERROR) << "Error while reading data";
47     }
48   }
49
50   return result;
51 }
52
53 void WriteBytes(unsigned char* buffer, std::size_t count, FILE* stream) {
54   // original file is treated as destination!
55   std::size_t bytes_written = 0;
56   std::size_t bytes_to_write = 0;
57   do {
58     bytes_to_write = count - bytes_written;
59     bytes_written = std::fwrite(buffer + bytes_written,
60                                sizeof(unsigned char),
61                                count - bytes_written,
62                                stream);
63     if ((bytes_written != bytes_to_write)) {
64       LOG(ERROR) << "Error while writing data";
65       free(buffer);
66       fclose(stream);
67     }
68   } while ((bytes_written != bytes_to_write));
69 }
70
71 }  // namespace
72
73
74 namespace wgt {
75 namespace encryption {
76
77 namespace bf = boost::filesystem;
78 namespace bs = boost::system;
79
80 common_installer::Step::Status StepEncryptResources::precheck() {
81   backend_data_ = static_cast<WgtBackendData*>(context_->backend_data.get());
82   if (!backend_data_) {
83     LOG(ERROR) << "no backend data";
84     return common_installer::Step::Status::ERROR;
85   }
86
87   SetEncryptionRoot();
88
89   if (input_.empty()) {
90     LOG(ERROR) << "unpacked_dir_path attribute is empty";
91     return Step::Status::INVALID_VALUE;
92   }
93   if (!bf::exists(input_)) {
94     LOG(ERROR) << "unpacked_dir_path (" << input_ << ") path does not exist";
95     return Step::Status::INVALID_VALUE;
96   }
97
98   return common_installer::Step::Status::OK;
99 }
100
101 common_installer::Step::Status StepEncryptResources::process() {
102   if (!backend_data_->settings.get().encryption_enabled()) {
103     LOG(DEBUG) << "no encryption";
104     return common_installer::Step::Status::OK;
105   }
106   LOG(DEBUG) << "Encrypting";
107
108   if (!Encrypt(input_)) {
109     LOG(ERROR) << "Error during encryption";
110     return common_installer::Step::Status::ERROR;
111   }
112
113   return common_installer::Step::Status::OK;
114 }
115
116 bool StepEncryptResources::Encrypt(const bf::path &src) {
117   // traversing through src dir (recurrence if subdir found)
118   // for every file found, check if it should be encrypted (ToBeEncrypted)
119   // if yes, encrypt it (and replace original one)
120   // if not, leave it
121   for (bf::directory_iterator file(src);
122        file != bf::directory_iterator();
123        ++file) {
124     bs::error_code error_code;
125     bf::path current(file->path());
126
127     bool is_dir = bf::is_directory(current, error_code);
128     if (error_code)
129       return false;
130     if (is_dir) {
131       if (!Encrypt(current))
132         return false;
133       continue;
134     }
135
136     bool is_sym = bf::is_symlink(symlink_status(current, error_code));
137     if (error_code)
138       return false;
139     if (is_sym)
140       continue;
141
142     // it is regular file (not dir, not symlink)
143     if (ToBeEncrypted(current)) {
144       LOG(INFO) << "File for encryption: " << current;
145       if (!EncryptFile(current))
146         return false;
147     }
148   }
149   return true;
150 }
151
152 bool StepEncryptResources::EncryptFile(const bf::path &src) {
153   bf::path encFile(src.string() + ".enc");
154   struct stat info;
155   memset(&info, 0, sizeof(info));
156   if (stat(src.string().c_str(), &info) != 0) {
157     LOG(ERROR) << "Could not access file " << src.string();
158     return false;
159   }
160   const std::size_t fileSize = info.st_size;
161   if (0 == fileSize) {
162     LOG(ERROR) << src.string().c_str() << " size is 0, so encryption is skiped";
163     return true;
164   }
165
166   FILE *input = OpenFile(src.string().c_str(), "rb");
167   if (input == nullptr) {
168     LOG(ERROR) << "Cannot open file for encryption: " << src.string();
169     return false;
170   }
171
172   FILE *output = OpenFile(encFile.string().c_str(), "wb");
173   if (output == nullptr) {
174     LOG(ERROR) << "Cannot create encrypted file: " << encFile.string();
175     return false;
176   }
177
178   std::size_t chunkSize = (fileSize > kEncryptionChunkMaxSize
179                                  ? kEncryptionChunkMaxSize : fileSize);
180
181   std::unique_ptr<unsigned char[]> inChunk(new unsigned char[chunkSize]);
182   std::size_t bytesRead = 0;
183
184   do {
185     bytesRead = ReadBytes(inChunk.get(), chunkSize, input);
186     if (0 != bytesRead) {
187       unsigned char* encrypted_data = nullptr;
188       size_t encrypted_size = 0;
189       // TODO(p.sikorski) check if it is Preloaded
190       int ret;
191       if (context_->request_mode.get() == common_installer::RequestMode::GLOBAL) {
192         ret = wae_encrypt_global_web_application(
193                 context_->pkgid.get().c_str(),
194                 context_->is_preload_request.get() ?
195                 true : false,
196                 inChunk.get(),
197                 (size_t)bytesRead,
198                 &encrypted_data,
199                 &encrypted_size);
200       } else {
201           ret = wae_encrypt_web_application(
202                   context_->uid.get(),
203                   context_->pkgid.get().c_str(),
204                   inChunk.get(),
205                   (size_t)bytesRead,
206                   &encrypted_data,
207                   &encrypted_size);
208       }
209
210       if (WAE_ERROR_NONE != ret) {
211         LOG(ERROR) << "Error during encrypting:";
212         switch (ret) {
213           case WAE_ERROR_INVALID_PARAMETER:
214             LOG(ERROR) << "WAE_ERROR_INVALID_PARAMETER";
215             break;
216           case WAE_ERROR_PERMISSION_DENIED:
217             LOG(ERROR) << "WAE_ERROR_PERMISSION_DENIED";
218             break;
219           case WAE_ERROR_NO_KEY:
220             LOG(ERROR) << "WAE_ERROR_NO_KEY";
221             break;
222           case WAE_ERROR_KEY_MANAGER:
223             LOG(ERROR) << "WAE_ERROR_KEY_MANAGER";
224             break;
225           case WAE_ERROR_CRYPTO:
226             LOG(ERROR) << "WAE_ERROR_CRYPTO";
227             break;
228           case WAE_ERROR_UNKNOWN:
229             LOG(ERROR) << "WAE_ERROR_UNKNOWN";
230             break;
231           default:
232             LOG(ERROR) << "UNKNOWN";
233             break;
234         }
235         fclose(output);
236         fclose(input);
237         return false;
238       }
239
240       if (encrypted_size <= 0) {
241         LOG(ERROR) << "Encryption Failed using TrustZone";
242         fclose(output);
243         fclose(input);
244         return false;
245       }
246
247       std::stringstream toString;
248       toString << encrypted_size;
249
250       WriteBytes((unsigned char*)toString.str().c_str(), sizeof(int), output);
251       WriteBytes((unsigned char*)encrypted_data, encrypted_size, output);
252       free(encrypted_data);
253     }
254     inChunk.reset(new unsigned char[chunkSize]);
255
256   } while (!std::feof(input));
257
258   fclose(output);
259   fclose(input);
260
261   LOG(DEBUG) << "File encrypted successfully";
262   if (0 != unlink(src.string().c_str())) {
263     return false;
264   }
265
266   LOG(DEBUG) << "Rename encrypted file";
267   if (0 != std::rename(encFile.c_str(), src.string().c_str())) {
268     return false;
269   }
270
271   return true;
272 }
273
274 void StepEncryptResources::SetEncryptionRoot() {
275   input_ = context_->unpacked_dir_path.get();
276 }
277
278 bool StepEncryptResources::ToBeEncrypted(const bf::path &file) {
279   size_t found_key = file.string().rfind(".");
280   if (std::string::npos != found_key) {
281     std::string mimeType = file.string().substr(found_key);
282     std::transform(mimeType.begin(), mimeType.end(), mimeType.begin(),
283                    ::tolower);
284     return encryptSet.count(mimeType) > 0;
285   }
286   return false;
287 }
288
289 }  // namespace encryption
290 }  // namespace wgt