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