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 <assert.h>
     18 #include <errno.h>
     19 #include <fcntl.h>
     20 #include <limits.h>
     21 #include <pthread.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 #include <unistd.h>
     25 #include <openssl/aes.h>
     26 #include <openssl/hmac.h>
     27 
     28 #include "FwdLockFile.h"
     29 #include "FwdLockGlue.h"
     30 
     31 #define TRUE 1
     32 #define FALSE 0
     33 
     34 #define INVALID_OFFSET ((off64_t)-1)
     35 
     36 #define INVALID_BLOCK_INDEX ((uint64_t)-1)
     37 
     38 #define MAX_NUM_SESSIONS 128
     39 
     40 #define KEY_SIZE AES_BLOCK_SIZE
     41 #define KEY_SIZE_IN_BITS (KEY_SIZE * 8)
     42 
     43 #define SHA1_HASH_SIZE 20
     44 #define SHA1_BLOCK_SIZE 64
     45 
     46 #define FWD_LOCK_VERSION 0
     47 #define FWD_LOCK_SUBFORMAT 0
     48 #define USAGE_RESTRICTION_FLAGS 0
     49 #define CONTENT_TYPE_LENGTH_POS 7
     50 #define TOP_HEADER_SIZE 8
     51 
     52 #define SIG_CALC_BUFFER_SIZE (16 * SHA1_BLOCK_SIZE)
     53 
     54 /**
     55  * Data type for the per-file state information needed by the decoder.
     56  */
     57 typedef struct FwdLockFile_Session {
     58     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 
     74 static FwdLockFile_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL };
     75 
     76 static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER;
     77 
     78 static 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  */
     88 static 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 with the given file descriptor.
    118  *
    119  * @param[in] fileDesc A file descriptor.
    120  *
    121  * @return A session ID.
    122  */
    123 static 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  */
    151 static 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  */
    169 static 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  */
    217 static 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  */
    240 void 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 
    253 int 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 
    294 int 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 
    303 ssize_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 
    320 off64_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 
    359 int 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 
    369 int FwdLockFile_close(int fileDesc) {
    370     return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
    371 }
    372 
    373 int 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                 unsigned int 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, SHA1_HASH_SIZE) == 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 
    414 int 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         unsigned int 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, SHA1_HASH_SIZE);
    429         HMAC_Final(&pSession->signingContext, signature, &signatureSize);
    430         assert(signatureSize == SHA1_HASH_SIZE);
    431         result = memcmp(signature, pSession->headerSignature, SHA1_HASH_SIZE) == 0;
    432         HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
    433     }
    434     return result;
    435 }
    436 
    437 int FwdLockFile_CheckIntegrity(int fileDesc) {
    438     return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
    439 }
    440 
    441 const 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