Home | History | Annotate | Download | only in decoder
      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  */
     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  */
    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  */
    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  */
    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     } *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  */
    219 static 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  */
    242 void 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 
    255 int 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 
    296 int FwdLockFile_open(const char *pFilename) {
    297     int fileDesc = open(pFilename, O_RDONLY);
    298     if (fileDesc < 0) {
    299         ALOGE("failed to open file '%s': %s", pFilename, strerror(errno));
    300         return fileDesc;
    301     }
    302 
    303     if (FwdLockFile_attach(fileDesc) < 0) {
    304         (void)close(fileDesc);
    305         fileDesc = -1;
    306     }
    307     return fileDesc;
    308 }
    309 
    310 ssize_t FwdLockFile_read(int fileDesc, void *pBuffer, size_t numBytes) {
    311     ssize_t numBytesRead;
    312     int sessionId = FwdLockFile_FindSession(fileDesc);
    313     if (sessionId < 0) {
    314         numBytesRead = -1;
    315     } else {
    316         FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
    317         ssize_t i;
    318         numBytesRead = read(pSession->fileDesc, pBuffer, numBytes);
    319         for (i = 0; i < numBytesRead; ++i) {
    320             FwdLockFile_DecryptByte(pSession, &((unsigned char *)pBuffer)[i]);
    321             ++pSession->filePos;
    322         }
    323     }
    324     return numBytesRead;
    325 }
    326 
    327 off64_t FwdLockFile_lseek(int fileDesc, off64_t offset, int whence) {
    328     off64_t newFilePos;
    329     int sessionId = FwdLockFile_FindSession(fileDesc);
    330     if (sessionId < 0) {
    331         newFilePos = INVALID_OFFSET;
    332     } else {
    333         FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
    334         switch (whence) {
    335         case SEEK_SET:
    336             newFilePos = lseek64(pSession->fileDesc, pSession->dataOffset + offset, whence);
    337             break;
    338         case SEEK_CUR:
    339         case SEEK_END:
    340             newFilePos = lseek64(pSession->fileDesc, offset, whence);
    341             break;
    342         default:
    343             errno = EINVAL;
    344             newFilePos = INVALID_OFFSET;
    345             break;
    346         }
    347         if (newFilePos != INVALID_OFFSET) {
    348             if (newFilePos < pSession->dataOffset) {
    349                 // The new file position is illegal for an internal Forward Lock file. Restore the
    350                 // original file position.
    351                 (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
    352                               SEEK_SET);
    353                 errno = EINVAL;
    354                 newFilePos = INVALID_OFFSET;
    355             } else {
    356                 // The return value should be the file position that lseek64() would have returned
    357                 // for the embedded content file.
    358                 pSession->filePos = newFilePos - pSession->dataOffset;
    359                 newFilePos = pSession->filePos;
    360             }
    361         }
    362     }
    363     return newFilePos;
    364 }
    365 
    366 int FwdLockFile_detach(int fileDesc) {
    367     int sessionId = FwdLockFile_FindSession(fileDesc);
    368     if (sessionId < 0) {
    369         return -1;
    370     }
    371     HMAC_CTX_cleanup(&sessionPtrs[sessionId]->signingContext);
    372     FwdLockFile_ReleaseSession(sessionId);
    373     return 0;
    374 }
    375 
    376 int FwdLockFile_close(int fileDesc) {
    377     return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
    378 }
    379 
    380 int FwdLockFile_CheckDataIntegrity(int fileDesc) {
    381     int result;
    382     int sessionId = FwdLockFile_FindSession(fileDesc);
    383     if (sessionId < 0) {
    384         result = FALSE;
    385     } else {
    386         struct FwdLockFile_CheckDataIntegrity_Data {
    387             unsigned char signature[SHA1_HASH_SIZE];
    388             unsigned char buffer[SIG_CALC_BUFFER_SIZE];
    389         } *pData = malloc(sizeof *pData);
    390         if (pData == NULL) {
    391             result = FALSE;
    392         } else {
    393             FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
    394             if (lseek64(pSession->fileDesc, pSession->dataOffset, SEEK_SET) !=
    395                     pSession->dataOffset) {
    396                 result = FALSE;
    397             } else {
    398                 ssize_t numBytesRead;
    399                 unsigned int signatureSize = SHA1_HASH_SIZE;
    400                 while ((numBytesRead =
    401                         read(pSession->fileDesc, pData->buffer, SIG_CALC_BUFFER_SIZE)) > 0) {
    402                     HMAC_Update(&pSession->signingContext, pData->buffer, (size_t)numBytesRead);
    403                 }
    404                 if (numBytesRead < 0) {
    405                     result = FALSE;
    406                 } else {
    407                     HMAC_Final(&pSession->signingContext, pData->signature, &signatureSize);
    408                     assert(signatureSize == SHA1_HASH_SIZE);
    409                     result = memcmp(pData->signature, pSession->dataSignature, SHA1_HASH_SIZE) == 0;
    410                 }
    411                 HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
    412                 (void)lseek64(pSession->fileDesc, pSession->dataOffset + pSession->filePos,
    413                               SEEK_SET);
    414             }
    415             free(pData);
    416         }
    417     }
    418     return result;
    419 }
    420 
    421 int FwdLockFile_CheckHeaderIntegrity(int fileDesc) {
    422     int result;
    423     int sessionId = FwdLockFile_FindSession(fileDesc);
    424     if (sessionId < 0) {
    425         result = FALSE;
    426     } else {
    427         FwdLockFile_Session_t *pSession = sessionPtrs[sessionId];
    428         unsigned char signature[SHA1_HASH_SIZE];
    429         unsigned int signatureSize = SHA1_HASH_SIZE;
    430         HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE);
    431         HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->pContentType,
    432                     pSession->contentTypeLength);
    433         HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey,
    434                     pSession->encryptedSessionKeyLength);
    435         HMAC_Update(&pSession->signingContext, pSession->dataSignature, SHA1_HASH_SIZE);
    436         HMAC_Final(&pSession->signingContext, signature, &signatureSize);
    437         assert(signatureSize == SHA1_HASH_SIZE);
    438         result = memcmp(signature, pSession->headerSignature, SHA1_HASH_SIZE) == 0;
    439         HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
    440     }
    441     return result;
    442 }
    443 
    444 int FwdLockFile_CheckIntegrity(int fileDesc) {
    445     return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
    446 }
    447 
    448 const char *FwdLockFile_GetContentType(int fileDesc) {
    449     int sessionId = FwdLockFile_FindSession(fileDesc);
    450     if (sessionId < 0) {
    451         return NULL;
    452     }
    453     return sessionPtrs[sessionId]->pContentType;
    454 }
    455