File encryption method reading whole files into a dynamic stack buffer.
[framework/web/wrt-installer.git] / src / jobs / widget_install / task_encrypt_resource.cpp
1 /*
2  * Copyright (c) 2011 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16 /**
17  * @file    task_ecnrypt_resource.cpp
18  * @author  Soyoung Kim (sy037.kim@samsung.com)
19  * @version 1.0
20  * @brief   Implementation file for installer task encrypt resource
21  */
22 #include "task_encrypt_resource.h"
23
24 #undef __USE_FILE_OFFSET64
25
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <fts.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <cstdio>
32
33 #include <memory>
34
35 #include <dpl/log/log.h>
36 #include <dpl/errno_string.h>
37 #include <dpl/foreach.h>
38 #include <dpl/scoped_fclose.h>
39 #include <dpl/wrt-dao-ro/global_config.h>
40 #include <dpl/string.h>
41
42 #include <widget_install/job_widget_install.h>
43 #include <widget_install/widget_install_context.h>
44 #include <widget_install/widget_install_errors.h>
45
46 using namespace WrtDB;
47 using namespace WRTEncryptor;
48
49 namespace {
50 const std::size_t ENCRYPTION_CHUNK_MAX_SIZE = 1024; // bytes
51
52 std::set<std::string>& getSupportedForEncryption()
53 {
54     static std::set<std::string> encryptSet;
55     if (encryptSet.empty()) {
56         encryptSet.insert(".html");
57         encryptSet.insert(".css");
58         encryptSet.insert(".js");
59     }
60     return encryptSet;
61 }
62
63 bool isSupportedForEncryption(const std::string &file)
64 {
65     size_t foundKey = file.rfind(".");
66     if (std::string::npos != foundKey) {
67         std::string mimeType = file.substr(foundKey);
68         return getSupportedForEncryption().count(mimeType) > 0;
69     }
70     return false;
71 }
72
73 /**
74  * Opens a file.
75  *
76  * @param path Path to a file.
77  * @param mode Mode.
78  * @return Stream handle.
79  * @throw ExtractFileFailed If error (other than EINTR) occurs.
80  */
81 FILE* openFile(const std::string& path, const std::string& mode)
82 {
83     FILE* result = NULL;
84
85     do
86     {
87         result = fopen(path.c_str(), mode.c_str());
88     } while ((NULL == result) && (EINTR == errno));
89
90     if (NULL == result)
91     {
92         ThrowMsg(Jobs::WidgetInstall::Exceptions::InternalError,
93                  "Could not open file " << path);
94     }
95
96     return result;
97 }
98
99 /**
100  * Reads bytes from a stream.
101  *
102  * @param buffer Buffer to read the bytes into.
103  * @param count Number of bytes to read.
104  * @param stream Stream to read from.
105  * @return Number of bytes read
106  * @throw ExtractFileFailed If error (other than EINTR) occurs.
107  */
108 std::size_t readBytes(unsigned char* buffer, std::size_t count, FILE* stream)
109 {
110     std::size_t result = std::fread(buffer,
111                                     sizeof(unsigned char),
112                                     count,
113                                     stream);
114
115     if (result != count)
116     {
117         int error = errno;
118         if (0 != std::ferror(stream))
119         {
120             if (EINTR != error)
121             {
122                 ThrowMsg(Jobs::WidgetInstall::Exceptions::InternalError,
123                          "Error while reading data" <<
124                          " [" << DPL::GetErrnoString(error) << "]");
125             }
126         }
127     }
128
129     return result;
130 }
131
132 /**
133  * Writes bytes to a stream.
134  *
135  * @param buffer Data to write.
136  * @param count Number of bytes.
137  * @param stream Stream to write to.
138  * @throw ExtractFileFailed If error (other than EINTR) occurs.
139  */
140 void writeBytes(unsigned char* buffer, std::size_t count, FILE* stream)
141 {
142     std::size_t bytesWritten = 0;
143     std::size_t bytesToWrite = 0;
144     do
145     {
146         bytesToWrite = count - bytesWritten;
147         bytesWritten = std::fwrite(buffer + bytesWritten,
148                                    sizeof(unsigned char),
149                                    count - bytesWritten,
150                                    stream);
151         if ((bytesWritten != bytesToWrite) && (EINTR != errno))
152         {
153             int error = errno;
154             ThrowMsg(Jobs::WidgetInstall::Exceptions::InternalError,
155                      "Error while writing data" <<
156                      " [" << DPL::GetErrnoString(error) << "]");
157         }
158     } while ((bytesWritten != bytesToWrite) && (EINTR == errno));
159 }
160 }
161
162 namespace Jobs {
163 namespace WidgetInstall {
164 TaskEncryptResource::TaskEncryptResource(InstallerContext& context) :
165     DPL::TaskDecl<TaskEncryptResource>(this),
166     m_context(context)
167 {
168     AddStep(&TaskEncryptResource::StepEncryptResource);
169 }
170
171 void TaskEncryptResource::StepEncryptResource()
172 {
173     LogDebug("Step Encrypt resource");
174     m_resEnc = new ResourceEncryptor;
175     m_resEnc->CreateEncryptionKey(DPL::ToUTF8String(m_context.
176                                                         widgetConfig.tzAppid));
177
178     EncryptDirectory(m_context.locations->getTemporaryRootDir());
179 }
180
181 void TaskEncryptResource::EncryptDirectory(std::string path)
182 {
183     FTS *fts;
184     FTSENT *ftsent;
185     char * const paths[] = { const_cast<char * const>(path.c_str()), NULL };
186
187     if ((fts = fts_open(paths, FTS_PHYSICAL | FTS_NOCHDIR, NULL)) == NULL) {
188         //ERROR
189         int error = errno;
190         LogWarning(__PRETTY_FUNCTION__ << ": fts_open failed with error: "
191                                        << strerror(error));
192         ThrowMsg(Exceptions::InternalError, "Error reading directory: "
193                  << path);
194     }
195
196     while ((ftsent = fts_read(fts)) != NULL) {
197         switch (ftsent->fts_info) {
198         case FTS_DP:
199         case FTS_DC:
200         case FTS_D:
201         case FTS_DEFAULT:
202         case FTS_SLNONE:
203             //directories, non-regular files, dangling symbolic links
204             break;
205         case FTS_F:
206         case FTS_NSOK:
207         case FTS_SL:
208             //regular files and other objects that can be counted
209             if (isSupportedForEncryption(ftsent->fts_path)) {
210                 EncryptFile(ftsent->fts_path);
211             }
212             break;
213         case FTS_NS:
214         case FTS_DOT:
215         case FTS_DNR:
216         case FTS_ERR:
217         default:
218             LogWarning(__PRETTY_FUNCTION__
219                        << ": traversal failed on file: "
220                        << ftsent->fts_path
221                        << " with error: "
222                        << strerror(ftsent->fts_errno));
223             ThrowMsg(Exceptions::InternalError, "Error reading file");
224         }
225     }
226
227     if (fts_close(fts) == -1) {
228         int error = errno;
229         LogWarning(__PRETTY_FUNCTION__ << ": fts_close failed with error: "
230                                        << strerror(error));
231     }
232 }
233
234 void TaskEncryptResource::EncryptFile(const std::string &fileName)
235 {
236     try
237     {
238         LogDebug("Encrypt file: " << fileName);
239         std::string encFile = fileName + ".enc";
240
241         struct stat info = {0};
242         if (stat(fileName.c_str(), &info) != 0)
243         {
244             int error = errno;
245             ThrowMsg(Exceptions::InternalError,
246                      "Could not access file " << fileName <<
247                      "[" << DPL::GetErrnoString(error) << "]");
248         }
249         const std::size_t fileSize = info.st_size;
250
251         DPL::ScopedFClose inFile(openFile(fileName, "r"));
252         DPL::ScopedFClose outFile(openFile(encFile, "w"));
253
254         const std::size_t chunkSize = (fileSize > ENCRYPTION_CHUNK_MAX_SIZE
255                 ? ENCRYPTION_CHUNK_MAX_SIZE : fileSize);
256         const int maxBlockSize = m_resEnc->GetBlockSize(chunkSize);
257
258         std::unique_ptr<unsigned char[]> inChunk(new unsigned char[chunkSize]);
259         std::unique_ptr<unsigned char[]> outChunk;
260
261         std::size_t bytesRead = 0;
262         int curBlockSize = 0;
263         do
264         {
265             bytesRead = readBytes(inChunk.get(), chunkSize, inFile.Get());
266             if (chunkSize != bytesRead)
267             {
268                 curBlockSize = m_resEnc->GetBlockSize(bytesRead);
269                 outChunk.reset(new unsigned char[curBlockSize]);
270             }
271             else
272             {
273                 if (maxBlockSize != curBlockSize)
274                 {
275                     curBlockSize = maxBlockSize;
276                     outChunk.reset(new unsigned char[curBlockSize]);
277                 }
278             }
279
280             m_resEnc->EncryptChunk(inChunk.get(), outChunk.get(), bytesRead);
281
282             writeBytes(outChunk.get(), curBlockSize, outFile.Get());
283
284         } while (0 == std::feof(inFile.Get()));
285
286         LogDebug("File encrypted successfully");
287
288         outFile.Reset();
289         inFile.Reset();
290
291         LogDebug("Remove plain-text file: " << fileName);
292         if (0 != unlink(fileName.c_str()))
293         {
294             Throw(Exceptions::InternalError);
295         }
296
297         LogDebug("Rename encrypted file");
298         if (0 != std::rename(encFile.c_str(), fileName.c_str()))
299         {
300             Throw(Exceptions::InternalError);
301         }
302
303         std::string realPath = fileName;
304         realPath.replace(0,
305                          m_context.locations->getTemporaryRootDir().length(),
306                          m_context.locations->getSourceDir());
307
308         WrtDB::EncryptedFileInfo fileInfo;
309         fileInfo.fileName = DPL::FromUTF8String(realPath);
310         fileInfo.fileSize = fileSize;
311
312         m_context.widgetConfig.encryptedFiles.insert(fileInfo);
313     }
314     Catch (Exceptions::InternalError)
315     {
316         ReThrowMsg(Exceptions::ExtractFileFailed, fileName);
317     }
318     Catch (ResourceEncryptor::Exception::Base)
319     {
320         ReThrowMsg(Exceptions::ExtractFileFailed, fileName);
321     }
322 }
323
324 } //namespace WidgetInstall
325 } //namespace Jobs