FwdLockFile.c revision 2d45dbd1910a7e51d383e8583017e6f26cd3498a
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 */
58typedef 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
75static FwdLockFile_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL };
76
77static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER;
78
79static 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 */
89static 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 */
125static 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 */
153static 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 */
171static 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 */
222static 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 */
245void 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
258int 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
299ssize_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
316off64_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
355int 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
365int FwdLockFile_close(int fileDesc) {
366    return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
367}
368
369int 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
410int 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
433int FwdLockFile_CheckIntegrity(int fileDesc) {
434    return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
435}
436
437const 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