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 ssize_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 
    313 off64_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 
    352 int 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 
    362 int FwdLockFile_close(int fileDesc) {
    363     return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1;
    364 }
    365 
    366 int 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 
    407 int 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 
    430 int FwdLockFile_CheckIntegrity(int fileDesc) {
    431     return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc);
    432 }
    433 
    434 const 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