• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
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 #include <utils/Log.h>
18 #include <assert.h>
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <limits.h>
22 #include <pthread.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <openssl/aes.h>
27 #include <openssl/hmac.h>
28 
29 #include "FwdLockFile.h"
30 #include "FwdLockGlue.h"
31 
32 #define TRUE 1
33 #define FALSE 0
34 
35 #define INVALID_OFFSET ((off64_t)-1)
36 
37 #define INVALID_BLOCK_INDEX ((uint64_t)-1)
38 
39 #define MAX_NUM_SESSIONS 128
40 
41 #define KEY_SIZE AES_BLOCK_SIZE
42 #define KEY_SIZE_IN_BITS (KEY_SIZE * 8)
43 
44 #define SHA1_HASH_SIZE 20
45 #define SHA1_BLOCK_SIZE 64
46 
47 #define FWD_LOCK_VERSION 0
48 #define FWD_LOCK_SUBFORMAT 0
49 #define USAGE_RESTRICTION_FLAGS 0
50 #define CONTENT_TYPE_LENGTH_POS 7
51 #define TOP_HEADER_SIZE 8
52 
53 #define SIG_CALC_BUFFER_SIZE (16 * SHA1_BLOCK_SIZE)
54 
55 /**
56  * Data type for the per-file state information needed by the decoder.
57  */
58 typedef struct FwdLockFile_Session {
59     int fileDesc;
60     unsigned char topHeader[TOP_HEADER_SIZE];
61     char *pContentType;
62     size_t contentTypeLength;
63     void *pEncryptedSessionKey;
64     size_t encryptedSessionKeyLength;
65     unsigned char dataSignature[SHA1_HASH_SIZE];
66     unsigned char headerSignature[SHA1_HASH_SIZE];
67     off64_t dataOffset;
68     off64_t filePos;
69     AES_KEY encryptionRoundKeys;
70     HMAC_CTX signingContext;
71     unsigned char keyStream[AES_BLOCK_SIZE];
72     uint64_t blockIndex;
73 } FwdLockFile_Session_t;
74 
75 static FwdLockFile_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL };
76 
77 static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER;
78 
79 static const unsigned char topHeaderTemplate[] =
80     { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS };
81 
82 /**
83  * Acquires an unused file session for the given file descriptor.
84  *
85  * @param[in] fileDesc A file descriptor.
86  *
87  * @return A session ID.
88  */
FwdLockFile_AcquireSession(int fileDesc)89 static int FwdLockFile_AcquireSession(int fileDesc) {
90     int sessionId = -1;
91     if (fileDesc < 0) {
92         errno = EBADF;
93     } else {
94         int i;
95         pthread_mutex_lock(&sessionAcquisitionMutex);
96         for (i = 0; i < MAX_NUM_SESSIONS; ++i) {
97             int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS;
98             if (sessionPtrs[candidateSessionId] == NULL) {
99                 sessionPtrs[candidateSessionId] = malloc(sizeof **sessionPtrs);
100                 if (sessionPtrs[candidateSessionId] != NULL) {
101                     sessionPtrs[candidateSessionId]->fileDesc = fileDesc;
102                     sessionPtrs[candidateSessionId]->pContentType = NULL;
103                     sessionPtrs[candidateSessionId]->pEncryptedSessionKey = NULL;
104                     sessionId = candidateSessionId;
105                 }
106                 break;
107             }
108         }
109         pthread_mutex_unlock(&sessionAcquisitionMutex);
110         if (i == MAX_NUM_SESSIONS) {
111             ALOGE("Too many sessions opened at the same time");
112             errno = ENFILE;
113         }
114     }
115     return sessionId;
116 }
117 
118 /**
119  * Finds the file session associated with the given file descriptor.
120  *
121  * @param[in] fileDesc A file descriptor.
122  *
123  * @return A session ID.
124  */
FwdLockFile_FindSession(int fileDesc)125 static int FwdLockFile_FindSession(int fileDesc) {
126     int sessionId = -1;
127     if (fileDesc < 0) {
128         errno = EBADF;
129     } else {
130         int i;
131         pthread_mutex_lock(&sessionAcquisitionMutex);
132         for (i = 0; i < MAX_NUM_SESSIONS; ++i) {
133             int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS;
134             if (sessionPtrs[candidateSessionId] != NULL &&
135                 sessionPtrs[candidateSessionId]->fileDesc == fileDesc) {
136                 sessionId = candidateSessionId;
137                 break;
138             }
139         }
140         pthread_mutex_unlock(&sessionAcquisitionMutex);
141         if (i == MAX_NUM_SESSIONS) {
142             errno = EBADF;
143         }
144     }
145     return sessionId;
146 }
147 
148 /**
149  * Releases a file session.
150  *
151  * @param[in] sessionID A session ID.
152  */
FwdLockFile_ReleaseSession(int sessionId)153 static void FwdLockFile_ReleaseSession(int sessionId) {
154     pthread_mutex_lock(&sessionAcquisitionMutex);
155     assert(0 <= sessionId && sessionId < MAX_NUM_SESSIONS && sessionPtrs[sessionId] != NULL);
156     free(sessionPtrs[sessionId]->pContentType);
157     free(sessionPtrs[sessionId]->pEncryptedSessionKey);
158     memset(sessionPtrs[sessionId], 0, sizeof *sessionPtrs[sessionId]); // Zero out key data.
159     free(sessionPtrs[sessionId]);
160     sessionPtrs[sessionId] = NULL;
161     pthread_mutex_unlock(&sessionAcquisitionMutex);
162 }
163 
164 /**
165  * Derives keys for encryption and signing from the encrypted session key.
166  *
167  * @param[in,out] pSession A reference to a file session.
168  *
169  * @return A Boolean value indicating whether key derivation was successful.
170  */
FwdLockFile_DeriveKeys(FwdLockFile_Session_t * pSession)171 static int FwdLockFile_DeriveKeys(FwdLockFile_Session_t * pSession) {
172     int result;
173     struct FwdLockFile_DeriveKeys_Data {
174         AES_KEY sessionRoundKeys;
175         unsigned char value[KEY_SIZE];
176         unsigned char key[KEY_SIZE];
177     };
178 
179     const size_t kSize = sizeof(struct FwdLockFile_DeriveKeys_Data);
180     struct FwdLockFile_DeriveKeys_Data *pData = malloc(kSize);
181     if (pData == NULL) {
182         result = FALSE;
183     } else {
184         result = FwdLockGlue_DecryptKey(pSession->pEncryptedSessionKey,
185                                         pSession->encryptedSessionKeyLength, pData->key, KEY_SIZE);
186         if (result) {
187             if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, &pData->sessionRoundKeys) != 0) {
188                 result = FALSE;
189             } else {
190                 // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key.
191                 memset(pData->value, 0, KEY_SIZE);
192                 AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
193                 if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS,
194                                         &pSession->encryptionRoundKeys) != 0) {
195                     result = FALSE;
196                 } else {
197                     // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key.
198                     ++pData->value[0];
199                     AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
200                     HMAC_CTX_init(&pSession->signingContext);
201                     HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL);
202                 }
203             }
204         }
205         if (!result) {
206             errno = ENOSYS;
207         }
208         memset(pData, 0, kSize); // Zero out key data.
209         free(pData);
210     }
211     return result;
212 }
213 
214 /**
215  * Calculates the counter, treated as a 16-byte little-endian number, used to generate the keystream
216  * for the given block.
217  *
218  * @param[in] pNonce A reference to the nonce.
219  * @param[in] blockIndex The index number of the block.
220  * @param[out] pCounter A reference to the counter.
221  */
FwdLockFile_CalculateCounter(const unsigned char * pNonce,uint64_t blockIndex,unsigned char * pCounter)222 static void FwdLockFile_CalculateCounter(const unsigned char *pNonce,
223                                          uint64_t blockIndex,
224                                          unsigned char *pCounter) {
225     unsigned char carry = 0;
226     size_t i = 0;
227     for (; i < sizeof blockIndex; ++i) {
228         unsigned char part = pNonce[i] + (unsigned char)(blockIndex >> (i * CHAR_BIT));
229         pCounter[i] = part + carry;
230         carry = (part < pNonce[i] || pCounter[i] < part) ? 1 : 0;
231     }
232     for (; i < AES_BLOCK_SIZE; ++i) {
233         pCounter[i] = pNonce[i] + carry;
234         carry = (pCounter[i] < pNonce[i]) ? 1 : 0;
235     }
236 }
237 
238 /**
239  * Decrypts the byte at the current file position using AES-128-CTR. In CTR (or "counter") mode,
240  * encryption and decryption are performed using the same algorithm.
241  *
242  * @param[in,out] pSession A reference to a file session.
243  * @param[in] pByte The byte to decrypt.
244  */
FwdLockFile_DecryptByte(FwdLockFile_Session_t * pSession,unsigned char * pByte)245 void FwdLockFile_DecryptByte(FwdLockFile_Session_t * pSession, unsigned char *pByte) {
246     uint64_t blockIndex = pSession->filePos / AES_BLOCK_SIZE;
247     uint64_t blockOffset = pSession->filePos % AES_BLOCK_SIZE;
248     if (blockIndex != pSession->blockIndex) {
249         // The first 16 bytes of the encrypted session key is used as the nonce.
250         unsigned char counter[AES_BLOCK_SIZE];
251         FwdLockFile_CalculateCounter(pSession->pEncryptedSessionKey, blockIndex, counter);
252         AES_encrypt(counter, pSession->keyStream, &pSession->encryptionRoundKeys);
253         pSession->blockIndex = blockIndex;
254     }
255     *pByte ^= pSession->keyStream[blockOffset];
256 }
257 
FwdLockFile_attach(int fileDesc)258 int FwdLockFile_attach(int fileDesc) {
259     int sessionId = FwdLockFile_AcquireSession(fileDesc);
260     if (sessionId >= 0) {
261         FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
262         int isSuccess = FALSE;
263         if (read(fileDesc, pSession->topHeader, TOP_HEADER_SIZE) == TOP_HEADER_SIZE &&
264                 memcmp(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate) == 0) {
265             pSession->contentTypeLength = pSession->topHeader[CONTENT_TYPE_LENGTH_POS];
266             assert(pSession->contentTypeLength <= UCHAR_MAX); // Untaint scalar for code checkers.
267             pSession->pContentType = malloc(pSession->contentTypeLength + 1);
268             if (pSession->pContentType != NULL &&
269                     read(fileDesc, pSession->pContentType, pSession->contentTypeLength) ==
270                             (ssize_t)pSession->contentTypeLength) {
271                 pSession->pContentType[pSession->contentTypeLength] = '\0';
272                 pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE);
273                 pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength);
274                 if (pSession->pEncryptedSessionKey != NULL &&
275                         read(fileDesc, pSession->pEncryptedSessionKey,
276                              pSession->encryptedSessionKeyLength) ==
277                                 (ssize_t)pSession->encryptedSessionKeyLength &&
278                         read(fileDesc, pSession->dataSignature, SHA1_HASH_SIZE) ==
279                                 SHA1_HASH_SIZE &&
280                         read(fileDesc, pSession->headerSignature, SHA1_HASH_SIZE) ==
281                                 SHA1_HASH_SIZE) {
282                     isSuccess = FwdLockFile_DeriveKeys(pSession);
283                 }
284             }
285         }
286         if (isSuccess) {
287             pSession->dataOffset = pSession->contentTypeLength +
288                     pSession->encryptedSessionKeyLength + TOP_HEADER_SIZE + 2 * SHA1_HASH_SIZE;
289             pSession->filePos = 0;
290             pSession->blockIndex = INVALID_BLOCK_INDEX;
291         } else {
292             FwdLockFile_ReleaseSession(sessionId);
293             sessionId = -1;
294         }
295     }
296     return (sessionId >= 0) ? 0 : -1;
297 }
298 
FwdLockFile_read(int fileDesc,void * pBuffer,size_t numBytes)299 ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes) {
300     ssize_t numBytesRead;
301     int sessionId = FwdLockFile_FindSession(fileDesc);
302     if (sessionId < 0) {
303         numBytesRead = -1;
304     } else {
305         FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
306         ssize_t i;
307         numBytesRead = read(pSession->fileDesc, pBuffer, numBytes);
308         for (i = 0; i < numBytesRead; ++i) {
309             FwdLockFile_DecryptByte(pSession, &((unsigned char *)pBuffer)[i]);
310             ++pSession->filePos;
311         }
312     }
313     return numBytesRead;
314 }
315 
FwdLockFile_lseek(int fileDesc,off64_t offset,int whence)316 off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence) {
317     off64_t newFilePos;
318     int sessionId = FwdLockFile_FindSession(fileDesc);
319     if (sessionId < 0) {
320         newFilePos = INVALID_OFFSET;
321     } else {
322         FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
323         switch (whence) {
324         case SEEK_SET:
325             newFilePos = lseek64(pSession->fileDesc, pSession->dataOffset + offset, whence);
326             break;
327         case SEEK_CUR:
328         case SEEK_END:
329             newFilePos = lseek64(pSession->fileDesc, offset, whence);
330             break;
331         default:
332             errno = EINVAL;
333             newFilePos = INVALID_OFFSET;
334             break;
335         }
336         if (newFilePos != INVALID_OFFSET) {
337             if (newFilePos < pSession->dataOffset) {
338                 // The new file position is illegal for an internal Forward Lock file. Restore the
339                 // original file position.
340                 (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
341                               SEEK_SET);
342                 errno = EINVAL;
343                 newFilePos = INVALID_OFFSET;
344             } else {
345                 // The return value should be the file position that lseek64() would have returned
346                 // for the embedded content file.
347                 pSession->filePos = newFilePos - pSession->dataOffset;
348                 newFilePos = pSession->filePos;
349             }
350         }
351     }
352     return newFilePos;
353 }
354 
FwdLockFile_detach(int fileDesc)355 int FwdLockFile_detach(int fileDesc) {
356     int sessionId = FwdLockFile_FindSession(fileDesc);
357     if (sessionId < 0) {
358         return -1;
359     }
360     HMAC_CTX_cleanup(&sessionPtrs[sessionId]->signingContext);
361     FwdLockFile_ReleaseSession(sessionId);
362     return 0;
363 }
364 
FwdLockFile_close(int fileDesc)365 int FwdLockFile_close(int fileDesc) {
366     return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
367 }
368 
FwdLockFile_CheckDataIntegrity(int fileDesc)369 int FwdLockFile_CheckDataIntegrity(int fileDesc) {
370     int result;
371     int sessionId = FwdLockFile_FindSession(fileDesc);
372     if (sessionId < 0) {
373         result = FALSE;
374     } else {
375         struct FwdLockFile_CheckDataIntegrity_Data {
376             unsigned char signature[SHA1_HASH_SIZE];
377             unsigned char buffer[SIG_CALC_BUFFER_SIZE];
378         } *pData = malloc(sizeof *pData);
379         if (pData == NULL) {
380             result = FALSE;
381         } else {
382             FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
383             if (lseek64(pSession->fileDesc, pSession->dataOffset, SEEK_SET) !=
384                     pSession->dataOffset) {
385                 result = FALSE;
386             } else {
387                 ssize_t numBytesRead;
388                 unsigned int signatureSize = SHA1_HASH_SIZE;
389                 while ((numBytesRead =
390                         read(pSession->fileDesc, pData->buffer, SIG_CALC_BUFFER_SIZE)) > 0) {
391                     HMAC_Update(&pSession->signingContext, pData->buffer, (size_t)numBytesRead);
392                 }
393                 if (numBytesRead < 0) {
394                     result = FALSE;
395                 } else {
396                     HMAC_Final(&pSession->signingContext, pData->signature, &signatureSize);
397                     assert(signatureSize == SHA1_HASH_SIZE);
398                     result = memcmp(pData->signature, pSession->dataSignature, SHA1_HASH_SIZE) == 0;
399                 }
400                 HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
401                 (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
402                               SEEK_SET);
403             }
404             free(pData);
405         }
406     }
407     return result;
408 }
409 
FwdLockFile_CheckHeaderIntegrity(int fileDesc)410 int FwdLockFile_CheckHeaderIntegrity(int fileDesc) {
411     int result;
412     int sessionId = FwdLockFile_FindSession(fileDesc);
413     if (sessionId < 0) {
414         result = FALSE;
415     } else {
416         FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
417         unsigned char signature[SHA1_HASH_SIZE];
418         unsigned int signatureSize = SHA1_HASH_SIZE;
419         HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE);
420         HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->pContentType,
421                     pSession->contentTypeLength);
422         HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey,
423                     pSession->encryptedSessionKeyLength);
424         HMAC_Update(&pSession->signingContext, pSession->dataSignature, SHA1_HASH_SIZE);
425         HMAC_Final(&pSession->signingContext, signature, &signatureSize);
426         assert(signatureSize == SHA1_HASH_SIZE);
427         result = memcmp(signature, pSession->headerSignature, SHA1_HASH_SIZE) == 0;
428         HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
429     }
430     return result;
431 }
432 
FwdLockFile_CheckIntegrity(int fileDesc)433 int FwdLockFile_CheckIntegrity(int fileDesc) {
434     return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
435 }
436 
FwdLockFile_GetContentType(int fileDesc)437 const char *FwdLockFile_GetContentType(int fileDesc) {
438     int sessionId = FwdLockFile_FindSession(fileDesc);
439     if (sessionId < 0) {
440         return NULL;
441     }
442     return sessionPtrs[sessionId]->pContentType;
443 }
444