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     };
    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  */
    222 static 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  */
    245 void 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 
    258 int 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 
    299 ssize_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 
    316 off64_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 
    355 int 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 
    365 int FwdLockFile_close(int fileDesc) {
    366     return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
    367 }
    368 
    369 int 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 
    410 int 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 
    433 int FwdLockFile_CheckIntegrity(int fileDesc) {
    434     return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
    435 }
    436 
    437 const 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