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