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 */ 57typedef 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 74static FwdLockFile_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL }; 75 76static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER; 77 78static 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 */ 88static 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 */ 123static 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 */ 151static 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 */ 169static 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 */ 217static 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 */ 240void 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 253int 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 294int 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 303ssize_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 320off64_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 359int 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 369int FwdLockFile_close(int fileDesc) { 370 return (FwdLockFile_detach(fileDesc) == 0) ? close(fileDesc) : -1; 371} 372 373int 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 414int 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 437int FwdLockFile_CheckIntegrity(int fileDesc) { 438 return FwdLockFile_CheckHeaderIntegrity(fileDesc) && FwdLockFile_CheckDataIntegrity(fileDesc); 439} 440 441const 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