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