FwdLockFile.c revision fdd65a0fc7df2c878cc601e4c0f4021cb264f051
13551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)/*
23551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * Copyright (C) 2010 The Android Open Source Project
33551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) *
43551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * Licensed under the Apache License, Version 2.0 (the "License");
5c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch * you may not use this file except in compliance with the License.
6effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch * You may obtain a copy of the License at
74e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) *
8424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) *      http://www.apache.org/licenses/LICENSE-2.0
91320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci *
104e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles) * Unless required by applicable law or agreed to in writing, software
116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) * distributed under the License is distributed on an "AS IS" BASIS,
1258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133551c9c881056c480085172ff9840cab31610854Torne (Richard Coles) * See the License for the specific language governing permissions and
1423730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) * limitations under the License.
1523730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles) */
165d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
170f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)#include <assert.h>
183551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)#include <errno.h>
19424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)#include <fcntl.h>
20c5cede9ae108bb15f6b7a8aea21c7e1fefa2834cBen Murdoch#include <limits.h>
21effb81e5f8246d0db0270817048dc992db66e9fbBen Murdoch#include <pthread.h>
2223730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include <stdlib.h>
23f2477e01787aa58f445919b809d89e252beef54fTorne (Richard Coles)#include <string.h>
24424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)#include <unistd.h>
251320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include <openssl/aes.h>
2623730a6e56a168d1879203e4b3819bb36e3d8f1fTorne (Richard Coles)#include <openssl/hmac.h>
274e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
286e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)#include "FwdLockFile.h"
2958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#include "FwdLockGlue.h"
300f1bc08d4cfcc34181b0b5cbf065c40f687bf740Torne (Richard Coles)
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#define TRUE 1
32424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)#define FALSE 0
333551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)
341320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define INVALID_OFFSET ((off64_t)-1)
354e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
364e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)#define INVALID_BLOCK_INDEX ((uint64_t)-1)
374e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)
381320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define MAX_NUM_SESSIONS 128
391320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
401320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define KEY_SIZE AES_BLOCK_SIZE
411320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define KEY_SIZE_IN_BITS (KEY_SIZE * 8)
421320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
431320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define SHA1_HASH_SIZE 20
441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define SHA1_BLOCK_SIZE 64
451320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
461320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define FWD_LOCK_VERSION 0
471320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define FWD_LOCK_SUBFORMAT 0
481320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define USAGE_RESTRICTION_FLAGS 0
4958537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#define CONTENT_TYPE_LENGTH_POS 7
5058537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)#define TOP_HEADER_SIZE 8
5158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)
521320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#define SIG_CALC_BUFFER_SIZE (16 * SHA1_BLOCK_SIZE)
5358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)
5458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)/**
5558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) * Data type for the per-file state information needed by the decoder.
56424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles) */
574e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)typedef struct FwdLockFile_Session {
584e180b6a0b4720a9b8e9e959a882386f690f08ffTorne (Richard Coles)    int fileDesc;
59    unsigned char topHeader[TOP_HEADER_SIZE];
60    char *pContentType;
61    size_t contentTypeLength;
62    void *pEncryptedSessionKey;
63    size_t encryptedSessionKeyLength;
64    unsigned char dataSignature[SHA1_HASH_SIZE];
65    unsigned char headerSignature[SHA1_HASH_SIZE];
66    off64_t dataOffset;
67    off64_t filePos;
68    AES_KEY encryptionRoundKeys;
69    HMAC_CTX signingContext;
70    unsigned char keyStream[AES_BLOCK_SIZE];
71    uint64_t blockIndex;
72} FwdLockFile_Session_t;
73
74static FwdLockFile_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL };
75
76static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER;
77
78static const unsigned char topHeaderTemplate[] =
79    { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS };
80
81/**
82 * Acquires an unused file session for the given file descriptor.
83 *
84 * @param[in] fileDesc A file descriptor.
85 *
86 * @return A session ID.
87 */
88static int FwdLockFile_AcquireSession(int fileDesc) {
89    int sessionId = -1;
90    if (fileDesc < 0) {
91        errno = EBADF;
92    } else {
93        int i;
94        pthread_mutex_lock(&sessionAcquisitionMutex);
95        for (i = 0; i < MAX_NUM_SESSIONS; ++i) {
96            int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS;
97            if (sessionPtrs[candidateSessionId] == NULL) {
98                sessionPtrs[candidateSessionId] = malloc(sizeof **sessionPtrs);
99                if (sessionPtrs[candidateSessionId] != NULL) {
100                    sessionPtrs[candidateSessionId]->fileDesc = fileDesc;
101                    sessionPtrs[candidateSessionId]->pContentType = NULL;
102                    sessionPtrs[candidateSessionId]->pEncryptedSessionKey = NULL;
103                    sessionId = candidateSessionId;
104                }
105                break;
106            }
107        }
108        pthread_mutex_unlock(&sessionAcquisitionMutex);
109        if (i == MAX_NUM_SESSIONS) {
110            errno = ENFILE;
111        }
112    }
113    return sessionId;
114}
115
116/**
117 * Finds the file session associated to the given file descriptor.
118 *
119 * @param[in] fileDesc A file descriptor.
120 *
121 * @return A session ID.
122 */
123static int FwdLockFile_FindSession(int fileDesc) {
124    int sessionId = -1;
125    if (fileDesc < 0) {
126        errno = EBADF;
127    } else {
128        int i;
129        pthread_mutex_lock(&sessionAcquisitionMutex);
130        for (i = 0; i < MAX_NUM_SESSIONS; ++i) {
131            int candidateSessionId = (fileDesc + i) % MAX_NUM_SESSIONS;
132            if (sessionPtrs[candidateSessionId] != NULL &&
133                sessionPtrs[candidateSessionId]->fileDesc == fileDesc) {
134                sessionId = candidateSessionId;
135                break;
136            }
137        }
138        pthread_mutex_unlock(&sessionAcquisitionMutex);
139        if (i == MAX_NUM_SESSIONS) {
140            errno = EBADF;
141        }
142    }
143    return sessionId;
144}
145
146/**
147 * Releases a file session.
148 *
149 * @param[in] sessionID A session ID.
150 */
151static void FwdLockFile_ReleaseSession(int sessionId) {
152    pthread_mutex_lock(&sessionAcquisitionMutex);
153    assert(0 <= sessionId && sessionId < MAX_NUM_SESSIONS && sessionPtrs[sessionId] != NULL);
154    free(sessionPtrs[sessionId]->pContentType);
155    free(sessionPtrs[sessionId]->pEncryptedSessionKey);
156    memset(sessionPtrs[sessionId], 0, sizeof *sessionPtrs[sessionId]); // Zero out key data.
157    free(sessionPtrs[sessionId]);
158    sessionPtrs[sessionId] = NULL;
159    pthread_mutex_unlock(&sessionAcquisitionMutex);
160}
161
162/**
163 * Derives keys for encryption and signing from the encrypted session key.
164 *
165 * @param[in,out] pSession A reference to a file session.
166 *
167 * @return A Boolean value indicating whether key derivation was successful.
168 */
169static int FwdLockFile_DeriveKeys(FwdLockFile_Session_t * pSession) {
170    int result;
171    struct FwdLockFile_DeriveKeys_Data {
172        AES_KEY sessionRoundKeys;
173        unsigned char value[KEY_SIZE];
174        unsigned char key[KEY_SIZE];
175    } *pData = malloc(sizeof *pData);
176    if (pData == NULL) {
177        result = FALSE;
178    } else {
179        result = FwdLockGlue_DecryptKey(pSession->pEncryptedSessionKey,
180                                        pSession->encryptedSessionKeyLength, pData->key, KEY_SIZE);
181        if (result) {
182            if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS, &pData->sessionRoundKeys) != 0) {
183                result = FALSE;
184            } else {
185                // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key.
186                memset(pData->value, 0, KEY_SIZE);
187                AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
188                if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS,
189                                        &pSession->encryptionRoundKeys) != 0) {
190                    result = FALSE;
191                } else {
192                    // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key.
193                    ++pData->value[0];
194                    AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
195                    HMAC_CTX_init(&pSession->signingContext);
196                    HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL);
197                }
198            }
199        }
200        if (!result) {
201            errno = ENOSYS;
202        }
203        memset(pData, 0, sizeof pData); // Zero out key data.
204        free(pData);
205    }
206    return result;
207}
208
209/**
210 * Calculates the counter, treated as a 16-byte little-endian number, used to generate the keystream
211 * for the given block.
212 *
213 * @param[in] pNonce A reference to the nonce.
214 * @param[in] blockIndex The index number of the block.
215 * @param[out] pCounter A reference to the counter.
216 */
217static void FwdLockFile_CalculateCounter(const unsigned char *pNonce,
218                                         uint64_t blockIndex,
219                                         unsigned char *pCounter) {
220    unsigned char carry = 0;
221    size_t i = 0;
222    for (; i < sizeof blockIndex; ++i) {
223        unsigned char part = pNonce[i] + (unsigned char)(blockIndex >> (i * CHAR_BIT));
224        pCounter[i] = part + carry;
225        carry = (part < pNonce[i] || pCounter[i] < part) ? 1 : 0;
226    }
227    for (; i < AES_BLOCK_SIZE; ++i) {
228        pCounter[i] = pNonce[i] + carry;
229        carry = (pCounter[i] < pNonce[i]) ? 1 : 0;
230    }
231}
232
233/**
234 * Decrypts the byte at the current file position using AES-128-CTR. In CTR (or "counter") mode,
235 * encryption and decryption are performed using the same algorithm.
236 *
237 * @param[in,out] pSession A reference to a file session.
238 * @param[in] pByte The byte to decrypt.
239 */
240void FwdLockFile_DecryptByte(FwdLockFile_Session_t * pSession, unsigned char *pByte) {
241    uint64_t blockIndex = pSession->filePos / AES_BLOCK_SIZE;
242    uint64_t blockOffset = pSession->filePos % AES_BLOCK_SIZE;
243    if (blockIndex != pSession->blockIndex) {
244        // The first 16 bytes of the encrypted session key is used as the nonce.
245        unsigned char counter[AES_BLOCK_SIZE];
246        FwdLockFile_CalculateCounter(pSession->pEncryptedSessionKey, blockIndex, counter);
247        AES_encrypt(counter, pSession->keyStream, &pSession->encryptionRoundKeys);
248        pSession->blockIndex = blockIndex;
249    }
250    *pByte ^= pSession->keyStream[blockOffset];
251}
252
253int FwdLockFile_attach(int fileDesc) {
254    int sessionId = FwdLockFile_AcquireSession(fileDesc);
255    if (sessionId >= 0) {
256        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
257        int isSuccess = FALSE;
258        if (read(fileDesc, pSession->topHeader, TOP_HEADER_SIZE) == TOP_HEADER_SIZE &&
259                memcmp(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate) == 0) {
260            pSession->contentTypeLength = pSession->topHeader[CONTENT_TYPE_LENGTH_POS];
261            assert(pSession->contentTypeLength <= UCHAR_MAX); // Untaint scalar for code checkers.
262            pSession->pContentType = malloc(pSession->contentTypeLength + 1);
263            if (pSession->pContentType != NULL &&
264                    read(fileDesc, pSession->pContentType, pSession->contentTypeLength) ==
265                            (ssize_t)pSession->contentTypeLength) {
266                pSession->pContentType[pSession->contentTypeLength] = '\0';
267                pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE);
268                pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength);
269                if (pSession->pEncryptedSessionKey != NULL &&
270                        read(fileDesc, pSession->pEncryptedSessionKey,
271                             pSession->encryptedSessionKeyLength) ==
272                                (ssize_t)pSession->encryptedSessionKeyLength &&
273                        read(fileDesc, pSession->dataSignature, SHA1_HASH_SIZE) ==
274                                SHA1_HASH_SIZE &&
275                        read(fileDesc, pSession->headerSignature, SHA1_HASH_SIZE) ==
276                                SHA1_HASH_SIZE) {
277                    isSuccess = FwdLockFile_DeriveKeys(pSession);
278                }
279            }
280        }
281        if (isSuccess) {
282            pSession->dataOffset = pSession->contentTypeLength +
283                    pSession->encryptedSessionKeyLength + TOP_HEADER_SIZE + 2 * SHA1_HASH_SIZE;
284            pSession->filePos = 0;
285            pSession->blockIndex = INVALID_BLOCK_INDEX;
286        } else {
287            FwdLockFile_ReleaseSession(sessionId);
288            sessionId = -1;
289        }
290    }
291    return (sessionId >= 0) ? 0 : -1;
292}
293
294int FwdLockFile_open(const char *pFilename) {
295    int fileDesc = open(pFilename, O_RDONLY);
296    if (fileDesc >= 0 && FwdLockFile_attach(fileDesc) < 0) {
297        (void)close(fileDesc);
298        fileDesc = -1;
299    }
300    return fileDesc;
301}
302
303ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes) {
304    ssize_t numBytesRead;
305    int sessionId = FwdLockFile_FindSession(fileDesc);
306    if (sessionId < 0) {
307        numBytesRead = -1;
308    } else {
309        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
310        ssize_t i;
311        numBytesRead = read(pSession->fileDesc, pBuffer, numBytes);
312        for (i = 0; i < numBytesRead; ++i) {
313            FwdLockFile_DecryptByte(pSession, &((unsigned char *)pBuffer)[i]);
314            ++pSession->filePos;
315        }
316    }
317    return numBytesRead;
318}
319
320off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence) {
321    off64_t newFilePos;
322    int sessionId = FwdLockFile_FindSession(fileDesc);
323    if (sessionId < 0) {
324        newFilePos = INVALID_OFFSET;
325    } else {
326        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
327        switch (whence) {
328        case SEEK_SET:
329            newFilePos = lseek64(pSession->fileDesc, pSession->dataOffset + offset, whence);
330            break;
331        case SEEK_CUR:
332        case SEEK_END:
333            newFilePos = lseek64(pSession->fileDesc, offset, whence);
334            break;
335        default:
336            errno = EINVAL;
337            newFilePos = INVALID_OFFSET;
338            break;
339        }
340        if (newFilePos != INVALID_OFFSET) {
341            if (newFilePos < pSession->dataOffset) {
342                // The new file position is illegal for an internal Forward Lock file. Restore the
343                // original file position.
344                (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
345                              SEEK_SET);
346                errno = EINVAL;
347                newFilePos = INVALID_OFFSET;
348            } else {
349                // The return value should be the file position that lseek64() would have returned
350                // for the embedded content file.
351                pSession->filePos = newFilePos - pSession->dataOffset;
352                newFilePos = pSession->filePos;
353            }
354        }
355    }
356    return newFilePos;
357}
358
359int FwdLockFile_detach(int fileDesc) {
360    int sessionId = FwdLockFile_FindSession(fileDesc);
361    if (sessionId < 0) {
362        return -1;
363    }
364    HMAC_CTX_cleanup(&sessionPtrs[sessionId]->signingContext);
365    FwdLockFile_ReleaseSession(sessionId);
366    return 0;
367}
368
369int FwdLockFile_close(int fileDesc) {
370    return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
371}
372
373int FwdLockFile_CheckDataIntegrity(int fileDesc) {
374    int result;
375    int sessionId = FwdLockFile_FindSession(fileDesc);
376    if (sessionId < 0) {
377        result = FALSE;
378    } else {
379        struct FwdLockFile_CheckDataIntegrity_Data {
380            unsigned char signature[SHA1_HASH_SIZE];
381            unsigned char buffer[SIG_CALC_BUFFER_SIZE];
382        } *pData = malloc(sizeof *pData);
383        if (pData == NULL) {
384            result = FALSE;
385        } else {
386            FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
387            if (lseek64(pSession->fileDesc, pSession->dataOffset, SEEK_SET) !=
388                    pSession->dataOffset) {
389                result = FALSE;
390            } else {
391                ssize_t numBytesRead;
392                size_t signatureSize = SHA1_HASH_SIZE;
393                while ((numBytesRead =
394                        read(pSession->fileDesc, pData->buffer, SIG_CALC_BUFFER_SIZE)) > 0) {
395                    HMAC_Update(&pSession->signingContext, pData->buffer, (size_t)numBytesRead);
396                }
397                if (numBytesRead < 0) {
398                    result = FALSE;
399                } else {
400                    HMAC_Final(&pSession->signingContext, pData->signature, &signatureSize);
401                    assert(signatureSize == SHA1_HASH_SIZE);
402                    result = memcmp(pData->signature, pSession->dataSignature, signatureSize) == 0;
403                }
404                HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
405                (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
406                              SEEK_SET);
407            }
408            free(pData);
409        }
410    }
411    return result;
412}
413
414int FwdLockFile_CheckHeaderIntegrity(int fileDesc) {
415    int result;
416    int sessionId = FwdLockFile_FindSession(fileDesc);
417    if (sessionId < 0) {
418        result = FALSE;
419    } else {
420        FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
421        unsigned char signature[SHA1_HASH_SIZE];
422        size_t signatureSize = SHA1_HASH_SIZE;
423        HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE);
424        HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->pContentType,
425                    pSession->contentTypeLength);
426        HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey,
427                    pSession->encryptedSessionKeyLength);
428        HMAC_Update(&pSession->signingContext, pSession->dataSignature, signatureSize);
429        HMAC_Final(&pSession->signingContext, signature, &signatureSize);
430        assert(signatureSize == SHA1_HASH_SIZE);
431        result = memcmp(signature, pSession->headerSignature, signatureSize) == 0;
432        HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
433    }
434    return result;
435}
436
437int FwdLockFile_CheckIntegrity(int fileDesc) {
438    return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
439}
440
441const char *FwdLockFile_GetContentType(int fileDesc) {
442    int sessionId = FwdLockFile_FindSession(fileDesc);
443    if (sessionId < 0) {
444        return NULL;
445    }
446    return sessionPtrs[sessionId]->pContentType;
447}
448