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    } *pData = malloc(sizeof *pData);
178    if (pData == NULL) {
179        result = FALSE;
180    } else {
181        result = FwdLockGlue_DecryptKey(pSession->pEncryptedSessionKey,
182                                        pSession->encryptedSessionKeyLength, pData->key, KEY_SIZE);
183        if (result) {
184            if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, &pData->sessionRoundKeys) != 0) {
185                result = FALSE;
186            } else {
187                // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key.
188                memset(pData->value, 0, KEY_SIZE);
189                AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
190                if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS,
191                                        &pSession->encryptionRoundKeys) != 0) {
192                    result = FALSE;
193                } else {
194                    // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key.
195                    ++pData->value[0];
196                    AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
197                    HMAC_CTX_init(&pSession->signingContext);
198                    HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL);
199                }
200            }
201        }
202        if (!result) {
203            errno = ENOSYS;
204        }
205        memset(pData, 0, sizeof pData); // Zero out key data.
206        free(pData);
207    }
208    return result;
209}
210
211/**
212 * Calculates the counter, treated as a 16-byte little-endian number, used to generate the keystream
213 * for the given block.
214 *
215 * @param[in] pNonce A reference to the nonce.
216 * @param[in] blockIndex The index number of the block.
217 * @param[out] pCounter A reference to the counter.
218 */
219static void FwdLockFile_CalculateCounter(const unsigned char *pNonce,
220                                         uint64_t blockIndex,
221                                         unsigned char *pCounter) {
222    unsigned char carry = 0;
223    size_t i = 0;
224    for (; i < sizeof blockIndex; ++i) {
225        unsigned char part = pNonce[i] + (unsigned char)(blockIndex >> (i * CHAR_BIT));
226        pCounter[i] = part + carry;
227        carry = (part < pNonce[i] || pCounter[i] < part) ? 1 : 0;
228    }
229    for (; i < AES_BLOCK_SIZE; ++i) {
230        pCounter[i] = pNonce[i] + carry;
231        carry = (pCounter[i] < pNonce[i]) ? 1 : 0;
232    }
233}
234
235/**
236 * Decrypts the byte at the current file position using AES-128-CTR. In CTR (or "counter") mode,
237 * encryption and decryption are performed using the same algorithm.
238 *
239 * @param[in,out] pSession A reference to a file session.
240 * @param[in] pByte The byte to decrypt.
241 */
242void FwdLockFile_DecryptByte(FwdLockFile_Session_t * pSession, unsigned char *pByte) {
243    uint64_t blockIndex = pSession->filePos / AES_BLOCK_SIZE;
244    uint64_t blockOffset = pSession->filePos % AES_BLOCK_SIZE;
245    if (blockIndex != pSession->blockIndex) {
246        // The first 16 bytes of the encrypted session key is used as the nonce.
247        unsigned char counter[AES_BLOCK_SIZE];
248        FwdLockFile_CalculateCounter(pSession->pEncryptedSessionKey, blockIndex, counter);
249        AES_encrypt(counter, pSession->keyStream, &pSession->encryptionRoundKeys);
250        pSession->blockIndex = blockIndex;
251    }
252    *pByte ^= pSession->keyStream[blockOffset];
253}
254
255int FwdLockFile_attach(int fileDesc) {
256    int sessionId = FwdLockFile_AcquireSession(fileDesc);
257    if (sessionId >= 0) {
258        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
259        int isSuccess = FALSE;
260        if (read(fileDesc, pSession->topHeader, TOP_HEADER_SIZE) == TOP_HEADER_SIZE &&
261                memcmp(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate) == 0) {
262            pSession->contentTypeLength = pSession->topHeader[CONTENT_TYPE_LENGTH_POS];
263            assert(pSession->contentTypeLength <= UCHAR_MAX); // Untaint scalar for code checkers.
264            pSession->pContentType = malloc(pSession->contentTypeLength + 1);
265            if (pSession->pContentType != NULL &&
266                    read(fileDesc, pSession->pContentType, pSession->contentTypeLength) ==
267                            (ssize_t)pSession->contentTypeLength) {
268                pSession->pContentType[pSession->contentTypeLength] = '\0';
269                pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE);
270                pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength);
271                if (pSession->pEncryptedSessionKey != NULL &&
272                        read(fileDesc, pSession->pEncryptedSessionKey,
273                             pSession->encryptedSessionKeyLength) ==
274                                (ssize_t)pSession->encryptedSessionKeyLength &&
275                        read(fileDesc, pSession->dataSignature, SHA1_HASH_SIZE) ==
276                                SHA1_HASH_SIZE &&
277                        read(fileDesc, pSession->headerSignature, SHA1_HASH_SIZE) ==
278                                SHA1_HASH_SIZE) {
279                    isSuccess = FwdLockFile_DeriveKeys(pSession);
280                }
281            }
282        }
283        if (isSuccess) {
284            pSession->dataOffset = pSession->contentTypeLength +
285                    pSession->encryptedSessionKeyLength + TOP_HEADER_SIZE + 2 * SHA1_HASH_SIZE;
286            pSession->filePos = 0;
287            pSession->blockIndex = INVALID_BLOCK_INDEX;
288        } else {
289            FwdLockFile_ReleaseSession(sessionId);
290            sessionId = -1;
291        }
292    }
293    return (sessionId >= 0) ? 0 : -1;
294}
295
296ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes) {
297    ssize_t numBytesRead;
298    int sessionId = FwdLockFile_FindSession(fileDesc);
299    if (sessionId < 0) {
300        numBytesRead = -1;
301    } else {
302        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
303        ssize_t i;
304        numBytesRead = read(pSession->fileDesc, pBuffer, numBytes);
305        for (i = 0; i < numBytesRead; ++i) {
306            FwdLockFile_DecryptByte(pSession, &((unsigned char *)pBuffer)[i]);
307            ++pSession->filePos;
308        }
309    }
310    return numBytesRead;
311}
312
313off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence) {
314    off64_t newFilePos;
315    int sessionId = FwdLockFile_FindSession(fileDesc);
316    if (sessionId < 0) {
317        newFilePos = INVALID_OFFSET;
318    } else {
319        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
320        switch (whence) {
321        case SEEK_SET:
322            newFilePos = lseek64(pSession->fileDesc, pSession->dataOffset + offset, whence);
323            break;
324        case SEEK_CUR:
325        case SEEK_END:
326            newFilePos = lseek64(pSession->fileDesc, offset, whence);
327            break;
328        default:
329            errno = EINVAL;
330            newFilePos = INVALID_OFFSET;
331            break;
332        }
333        if (newFilePos != INVALID_OFFSET) {
334            if (newFilePos < pSession->dataOffset) {
335                // The new file position is illegal for an internal Forward Lock file. Restore the
336                // original file position.
337                (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
338                              SEEK_SET);
339                errno = EINVAL;
340                newFilePos = INVALID_OFFSET;
341            } else {
342                // The return value should be the file position that lseek64() would have returned
343                // for the embedded content file.
344                pSession->filePos = newFilePos - pSession->dataOffset;
345                newFilePos = pSession->filePos;
346            }
347        }
348    }
349    return newFilePos;
350}
351
352int FwdLockFile_detach(int fileDesc) {
353    int sessionId = FwdLockFile_FindSession(fileDesc);
354    if (sessionId < 0) {
355        return -1;
356    }
357    HMAC_CTX_cleanup(&sessionPtrs[sessionId]->signingContext);
358    FwdLockFile_ReleaseSession(sessionId);
359    return 0;
360}
361
362int FwdLockFile_close(int fileDesc) {
363    return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
364}
365
366int FwdLockFile_CheckDataIntegrity(int fileDesc) {
367    int result;
368    int sessionId = FwdLockFile_FindSession(fileDesc);
369    if (sessionId < 0) {
370        result = FALSE;
371    } else {
372        struct FwdLockFile_CheckDataIntegrity_Data {
373            unsigned char signature[SHA1_HASH_SIZE];
374            unsigned char buffer[SIG_CALC_BUFFER_SIZE];
375        } *pData = malloc(sizeof *pData);
376        if (pData == NULL) {
377            result = FALSE;
378        } else {
379            FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
380            if (lseek64(pSession->fileDesc, pSession->dataOffset, SEEK_SET) !=
381                    pSession->dataOffset) {
382                result = FALSE;
383            } else {
384                ssize_t numBytesRead;
385                unsigned int signatureSize = SHA1_HASH_SIZE;
386                while ((numBytesRead =
387                        read(pSession->fileDesc, pData->buffer, SIG_CALC_BUFFER_SIZE)) > 0) {
388                    HMAC_Update(&pSession->signingContext, pData->buffer, (size_t)numBytesRead);
389                }
390                if (numBytesRead < 0) {
391                    result = FALSE;
392                } else {
393                    HMAC_Final(&pSession->signingContext, pData->signature, &signatureSize);
394                    assert(signatureSize == SHA1_HASH_SIZE);
395                    result = memcmp(pData->signature, pSession->dataSignature, SHA1_HASH_SIZE) == 0;
396                }
397                HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
398                (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
399                              SEEK_SET);
400            }
401            free(pData);
402        }
403    }
404    return result;
405}
406
407int FwdLockFile_CheckHeaderIntegrity(int fileDesc) {
408    int result;
409    int sessionId = FwdLockFile_FindSession(fileDesc);
410    if (sessionId < 0) {
411        result = FALSE;
412    } else {
413        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
414        unsigned char signature[SHA1_HASH_SIZE];
415        unsigned int signatureSize = SHA1_HASH_SIZE;
416        HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE);
417        HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->pContentType,
418                    pSession->contentTypeLength);
419        HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey,
420                    pSession->encryptedSessionKeyLength);
421        HMAC_Update(&pSession->signingContext, pSession->dataSignature, SHA1_HASH_SIZE);
422        HMAC_Final(&pSession->signingContext, signature, &signatureSize);
423        assert(signatureSize == SHA1_HASH_SIZE);
424        result = memcmp(signature, pSession->headerSignature, SHA1_HASH_SIZE) == 0;
425        HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
426    }
427    return result;
428}
429
430int FwdLockFile_CheckIntegrity(int fileDesc) {
431    return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
432}
433
434const char *FwdLockFile_GetContentType(int fileDesc) {
435    int sessionId = FwdLockFile_FindSession(fileDesc);
436    if (sessionId < 0) {
437        return NULL;
438    }
439    return sessionPtrs[sessionId]->pContentType;
440}
441