Home | History | Annotate | Download | only in converter
      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 <ctype.h>
     19 #include <fcntl.h>
     20 #include <limits.h>
     21 #include <pthread.h>
     22 #include <string.h>
     23 #include <sys/stat.h>
     24 #include <unistd.h>
     25 #include <openssl/aes.h>
     26 #include <openssl/hmac.h>
     27 
     28 #include "FwdLockConv.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 MAX_NUM_SESSIONS 32
     37 
     38 #define OUTPUT_BUFFER_SIZE_INCREMENT 1024
     39 #define READ_BUFFER_SIZE 1024
     40 
     41 #define MAX_BOUNDARY_LENGTH 70
     42 #define MAX_DELIMITER_LENGTH (MAX_BOUNDARY_LENGTH + 4)
     43 
     44 #define STRING_LENGTH_INCREMENT 25
     45 
     46 #define KEY_SIZE AES_BLOCK_SIZE
     47 #define KEY_SIZE_IN_BITS (KEY_SIZE * 8)
     48 
     49 #define SHA1_HASH_SIZE 20
     50 
     51 #define FWD_LOCK_VERSION 0
     52 #define FWD_LOCK_SUBFORMAT 0
     53 #define USAGE_RESTRICTION_FLAGS 0
     54 #define CONTENT_TYPE_LENGTH_POS 7
     55 #define TOP_HEADER_SIZE 8
     56 
     57 /**
     58  * Data type for the parser states of the converter.
     59  */
     60 typedef enum FwdLockConv_ParserState {
     61     FwdLockConv_ParserState_WantsOpenDelimiter,
     62     FwdLockConv_ParserState_WantsMimeHeaders,
     63     FwdLockConv_ParserState_WantsBinaryEncodedData,
     64     FwdLockConv_ParserState_WantsBase64EncodedData,
     65     FwdLockConv_ParserState_Done
     66 } FwdLockConv_ParserState_t;
     67 
     68 /**
     69  * Data type for the scanner states of the converter.
     70  */
     71 typedef enum FwdLockConv_ScannerState {
     72     FwdLockConv_ScannerState_WantsFirstDash,
     73     FwdLockConv_ScannerState_WantsSecondDash,
     74     FwdLockConv_ScannerState_WantsCR,
     75     FwdLockConv_ScannerState_WantsLF,
     76     FwdLockConv_ScannerState_WantsBoundary,
     77     FwdLockConv_ScannerState_WantsBoundaryEnd,
     78     FwdLockConv_ScannerState_WantsMimeHeaderNameStart,
     79     FwdLockConv_ScannerState_WantsMimeHeaderName,
     80     FwdLockConv_ScannerState_WantsMimeHeaderNameEnd,
     81     FwdLockConv_ScannerState_WantsContentTypeStart,
     82     FwdLockConv_ScannerState_WantsContentType,
     83     FwdLockConv_ScannerState_WantsContentTransferEncodingStart,
     84     FwdLockConv_ScannerState_Wants_A_OR_I,
     85     FwdLockConv_ScannerState_Wants_N,
     86     FwdLockConv_ScannerState_Wants_A,
     87     FwdLockConv_ScannerState_Wants_R,
     88     FwdLockConv_ScannerState_Wants_Y,
     89     FwdLockConv_ScannerState_Wants_S,
     90     FwdLockConv_ScannerState_Wants_E,
     91     FwdLockConv_ScannerState_Wants_6,
     92     FwdLockConv_ScannerState_Wants_4,
     93     FwdLockConv_ScannerState_Wants_B,
     94     FwdLockConv_ScannerState_Wants_I,
     95     FwdLockConv_ScannerState_Wants_T,
     96     FwdLockConv_ScannerState_WantsContentTransferEncodingEnd,
     97     FwdLockConv_ScannerState_WantsMimeHeaderValueEnd,
     98     FwdLockConv_ScannerState_WantsMimeHeadersEnd,
     99     FwdLockConv_ScannerState_WantsByte1,
    100     FwdLockConv_ScannerState_WantsByte1_AfterCRLF,
    101     FwdLockConv_ScannerState_WantsByte2,
    102     FwdLockConv_ScannerState_WantsByte3,
    103     FwdLockConv_ScannerState_WantsByte4,
    104     FwdLockConv_ScannerState_WantsPadding,
    105     FwdLockConv_ScannerState_WantsWhitespace,
    106     FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF,
    107     FwdLockConv_ScannerState_WantsDelimiter
    108 } FwdLockConv_ScannerState_t;
    109 
    110 /**
    111  * Data type for the content transfer encoding.
    112  */
    113 typedef enum FwdLockConv_ContentTransferEncoding {
    114     FwdLockConv_ContentTransferEncoding_Undefined,
    115     FwdLockConv_ContentTransferEncoding_Binary,
    116     FwdLockConv_ContentTransferEncoding_Base64
    117 } FwdLockConv_ContentTransferEncoding_t;
    118 
    119 /**
    120  * Data type for a dynamically growing string.
    121  */
    122 typedef struct FwdLockConv_String {
    123     char *ptr;
    124     size_t length;
    125     size_t maxLength;
    126     size_t lengthIncrement;
    127 } FwdLockConv_String_t;
    128 
    129 /**
    130  * Data type for the per-file state information needed by the converter.
    131  */
    132 typedef struct FwdLockConv_Session {
    133     FwdLockConv_ParserState_t parserState;
    134     FwdLockConv_ScannerState_t scannerState;
    135     FwdLockConv_ScannerState_t savedScannerState;
    136     off64_t numCharsConsumed;
    137     char delimiter[MAX_DELIMITER_LENGTH];
    138     size_t delimiterLength;
    139     size_t delimiterMatchPos;
    140     FwdLockConv_String_t mimeHeaderName;
    141     FwdLockConv_String_t contentType;
    142     FwdLockConv_ContentTransferEncoding_t contentTransferEncoding;
    143     unsigned char sessionKey[KEY_SIZE];
    144     void *pEncryptedSessionKey;
    145     size_t encryptedSessionKeyLength;
    146     AES_KEY encryptionRoundKeys;
    147     HMAC_CTX signingContext;
    148     unsigned char topHeader[TOP_HEADER_SIZE];
    149     unsigned char counter[AES_BLOCK_SIZE];
    150     unsigned char keyStream[AES_BLOCK_SIZE];
    151     int keyStreamIndex;
    152     unsigned char ch;
    153     size_t outputBufferSize;
    154     size_t dataOffset;
    155     size_t numDataBytes;
    156 } FwdLockConv_Session_t;
    157 
    158 static FwdLockConv_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL };
    159 
    160 static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER;
    161 
    162 static const FwdLockConv_String_t nullString = { NULL, 0, 0, STRING_LENGTH_INCREMENT };
    163 
    164 static const unsigned char topHeaderTemplate[] =
    165     { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS };
    166 
    167 static const char strContent[] = "content-";
    168 static const char strType[] = "type";
    169 static const char strTransferEncoding[] = "transfer-encoding";
    170 static const char strTextPlain[] = "text/plain";
    171 static const char strApplicationVndOmaDrmRightsXml[] = "application/vnd.oma.drm.rights+xml";
    172 static const char strApplicationVndOmaDrmContent[] = "application/vnd.oma.drm.content";
    173 
    174 static const size_t strlenContent = sizeof strContent - 1;
    175 static const size_t strlenTextPlain = sizeof strTextPlain - 1;
    176 
    177 static const signed char base64Values[] = {
    178     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    179     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    180     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
    181     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1,
    182     -1,  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14,
    183     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
    184     -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
    185     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
    186 };
    187 
    188 /**
    189  * Acquires an unused converter session.
    190  *
    191  * @return A session ID.
    192  */
    193 static int FwdLockConv_AcquireSession() {
    194     int sessionId = -1;
    195     int i;
    196     pthread_mutex_lock(&sessionAcquisitionMutex);
    197     for (i = 0; i < MAX_NUM_SESSIONS; ++i) {
    198         if (sessionPtrs[i] == NULL) {
    199             sessionPtrs[i] = malloc(sizeof *sessionPtrs[i]);
    200             if (sessionPtrs[i] != NULL) {
    201                 sessionId = i;
    202             }
    203             break;
    204         }
    205     }
    206     pthread_mutex_unlock(&sessionAcquisitionMutex);
    207     return sessionId;
    208 }
    209 
    210 /**
    211  * Checks whether a session ID is in range and currently in use.
    212  *
    213  * @param[in] sessionID A session ID.
    214  *
    215  * @return A Boolean value indicating whether the session ID is in range and currently in use.
    216  */
    217 static int FwdLockConv_IsValidSession(int sessionId) {
    218     return 0 <= sessionId && sessionId < MAX_NUM_SESSIONS && sessionPtrs[sessionId] != NULL;
    219 }
    220 
    221 /**
    222  * Releases a converter session.
    223  *
    224  * @param[in] sessionID A session ID.
    225  */
    226 static void FwdLockConv_ReleaseSession(int sessionId) {
    227     pthread_mutex_lock(&sessionAcquisitionMutex);
    228     assert(FwdLockConv_IsValidSession(sessionId));
    229     memset(sessionPtrs[sessionId], 0, sizeof *sessionPtrs[sessionId]); // Zero out key data.
    230     free(sessionPtrs[sessionId]);
    231     sessionPtrs[sessionId] = NULL;
    232     pthread_mutex_unlock(&sessionAcquisitionMutex);
    233 }
    234 
    235 /**
    236  * Derives cryptographically independent keys for encryption and signing from the session key.
    237  *
    238  * @param[in,out] pSession A reference to a converter session.
    239  *
    240  * @return A status code.
    241  */
    242 static int FwdLockConv_DeriveKeys(FwdLockConv_Session_t *pSession) {
    243     FwdLockConv_Status_t status;
    244     struct FwdLockConv_DeriveKeys_Data {
    245         AES_KEY sessionRoundKeys;
    246         unsigned char value[KEY_SIZE];
    247         unsigned char key[KEY_SIZE];
    248     } *pData = malloc(sizeof *pData);
    249     if (pData == NULL) {
    250         status = FwdLockConv_Status_OutOfMemory;
    251     } else {
    252         if (AES_set_encrypt_key(pSession->sessionKey, KEY_SIZE_IN_BITS,
    253                                 &pData->sessionRoundKeys) != 0) {
    254             status = FwdLockConv_Status_ProgramError;
    255         } else {
    256             // Encrypt the 16-byte value {0, 0, ..., 0} to produce the encryption key.
    257             memset(pData->value, 0, KEY_SIZE);
    258             AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
    259             if (AES_set_encrypt_key(pData->key, KEY_SIZE_IN_BITS,
    260                                     &pSession->encryptionRoundKeys) != 0) {
    261                 status = FwdLockConv_Status_ProgramError;
    262             } else {
    263                 // Encrypt the 16-byte value {1, 0, ..., 0} to produce the signing key.
    264                 ++pData->value[0];
    265                 AES_encrypt(pData->value, pData->key, &pData->sessionRoundKeys);
    266                 HMAC_CTX_init(&pSession->signingContext);
    267                 HMAC_Init_ex(&pSession->signingContext, pData->key, KEY_SIZE, EVP_sha1(), NULL);
    268                 status = FwdLockConv_Status_OK;
    269             }
    270         }
    271         memset(pData, 0, sizeof pData); // Zero out key data.
    272         free(pData);
    273     }
    274     return status;
    275 }
    276 
    277 /**
    278  * Checks whether a given character is valid in a boundary. Allows some non-standard characters that
    279  * are invalid according to RFC 2046 but nevertheless used by one vendor's DRM packager. Note that
    280  * the boundary may contain leading and internal spaces.
    281  *
    282  * @param[in] ch The character to check.
    283  *
    284  * @return A Boolean value indicating whether the given character is valid in a boundary.
    285  */
    286 static int FwdLockConv_IsBoundaryChar(int ch) {
    287     return isalnum(ch) || ch == '\'' || ch == '(' || ch == ')' || ch == '+' || ch == '_' ||
    288             ch == ',' || ch == '-' || ch == '.' || ch == '/' || ch == ':' || ch == '=' ||
    289             ch == '?' || ch == ' ' || ch == '%' || ch == '[' || ch == '&' || ch == '*' || ch == '^';
    290 }
    291 
    292 /**
    293  * Checks whether a given character should be considered whitespace, using a narrower definition
    294  * than the standard-library isspace() function.
    295  *
    296  * @param[in] ch The character to check.
    297  *
    298  * @return A Boolean value indicating whether the given character should be considered whitespace.
    299  */
    300 static int FwdLockConv_IsWhitespace(int ch) {
    301     return ch == ' ' || ch == '\t';
    302 }
    303 
    304 /**
    305  * Removes trailing spaces from the delimiter.
    306  *
    307  * @param[in,out] pSession A reference to a converter session.
    308  *
    309  * @return A status code.
    310  */
    311 static FwdLockConv_Status_t FwdLockConv_RightTrimDelimiter(FwdLockConv_Session_t *pSession) {
    312     while (pSession->delimiterLength > 4 &&
    313            pSession->delimiter[pSession->delimiterLength - 1] == ' ') {
    314         --pSession->delimiterLength;
    315     }
    316     if (pSession->delimiterLength > 4) {
    317         return FwdLockConv_Status_OK;
    318     }
    319     return FwdLockConv_Status_SyntaxError;
    320 }
    321 
    322 /**
    323  * Matches the open delimiter.
    324  *
    325  * @param[in,out] pSession A reference to a converter session.
    326  * @param[in] ch A character.
    327  *
    328  * @return A status code.
    329  */
    330 static FwdLockConv_Status_t FwdLockConv_MatchOpenDelimiter(FwdLockConv_Session_t *pSession,
    331                                                            int ch) {
    332     FwdLockConv_Status_t status = FwdLockConv_Status_OK;
    333     switch (pSession->scannerState) {
    334     case FwdLockConv_ScannerState_WantsFirstDash:
    335         if (ch == '-') {
    336             pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash;
    337         } else if (ch == '\r') {
    338             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    339         } else {
    340             pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    341         }
    342         break;
    343     case FwdLockConv_ScannerState_WantsSecondDash:
    344         if (ch == '-') {
    345             // The delimiter starts with "\r\n--" (the open delimiter may omit the initial "\r\n").
    346             // The rest is the user-defined boundary that should come next.
    347             pSession->delimiter[0] = '\r';
    348             pSession->delimiter[1] = '\n';
    349             pSession->delimiter[2] = '-';
    350             pSession->delimiter[3] = '-';
    351             pSession->delimiterLength = 4;
    352             pSession->scannerState = FwdLockConv_ScannerState_WantsBoundary;
    353         } else if (ch == '\r') {
    354             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    355         } else {
    356             pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    357         }
    358         break;
    359     case FwdLockConv_ScannerState_WantsCR:
    360         if (ch == '\r') {
    361             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    362         }
    363         break;
    364     case FwdLockConv_ScannerState_WantsLF:
    365         if (ch == '\n') {
    366             pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash;
    367         } else if (ch != '\r') {
    368             pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    369         }
    370         break;
    371     case FwdLockConv_ScannerState_WantsBoundary:
    372         if (FwdLockConv_IsBoundaryChar(ch)) {
    373             // The boundary may contain leading and internal spaces, so trailing spaces will also be
    374             // matched here. These will be removed later.
    375             if (pSession->delimiterLength < MAX_DELIMITER_LENGTH) {
    376                 pSession->delimiter[pSession->delimiterLength++] = ch;
    377             } else if (ch != ' ') {
    378                 status = FwdLockConv_Status_SyntaxError;
    379             }
    380         } else if (ch == '\r') {
    381             status = FwdLockConv_RightTrimDelimiter(pSession);
    382             if (status == FwdLockConv_Status_OK) {
    383                 pSession->scannerState = FwdLockConv_ScannerState_WantsBoundaryEnd;
    384             }
    385         } else if (ch == '\t') {
    386             status = FwdLockConv_RightTrimDelimiter(pSession);
    387             if (status == FwdLockConv_Status_OK) {
    388                 pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace;
    389             }
    390         } else {
    391             status = FwdLockConv_Status_SyntaxError;
    392         }
    393         break;
    394     case FwdLockConv_ScannerState_WantsWhitespace:
    395         if (ch == '\r') {
    396             pSession->scannerState = FwdLockConv_ScannerState_WantsBoundaryEnd;
    397         } else if (!FwdLockConv_IsWhitespace(ch)) {
    398             status = FwdLockConv_Status_SyntaxError;
    399         }
    400         break;
    401     case FwdLockConv_ScannerState_WantsBoundaryEnd:
    402         if (ch == '\n') {
    403             pSession->parserState = FwdLockConv_ParserState_WantsMimeHeaders;
    404             pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameStart;
    405         } else {
    406             status = FwdLockConv_Status_SyntaxError;
    407         }
    408         break;
    409     default:
    410         status = FwdLockConv_Status_ProgramError;
    411         break;
    412     }
    413     return status;
    414 }
    415 
    416 /**
    417  * Checks whether a given character is valid in a MIME header name.
    418  *
    419  * @param[in] ch The character to check.
    420  *
    421  * @return A Boolean value indicating whether the given character is valid in a MIME header name.
    422  */
    423 static int FwdLockConv_IsMimeHeaderNameChar(int ch) {
    424     return isgraph(ch) && ch != ':';
    425 }
    426 
    427 /**
    428  * Checks whether a given character is valid in a MIME header value.
    429  *
    430  * @param[in] ch The character to check.
    431  *
    432  * @return A Boolean value indicating whether the given character is valid in a MIME header value.
    433  */
    434 static int FwdLockConv_IsMimeHeaderValueChar(int ch) {
    435     return isgraph(ch) && ch != ';';
    436 }
    437 
    438 /**
    439  * Appends a character to the specified dynamically growing string.
    440  *
    441  * @param[in,out] pString A reference to a dynamically growing string.
    442  * @param[in] ch The character to append.
    443  *
    444  * @return A status code.
    445  */
    446 static FwdLockConv_Status_t FwdLockConv_StringAppend(FwdLockConv_String_t *pString, int ch) {
    447     if (pString->length == pString->maxLength) {
    448         size_t newMaxLength = pString->maxLength + pString->lengthIncrement;
    449         char *newPtr = realloc(pString->ptr, newMaxLength + 1);
    450         if (newPtr == NULL) {
    451             return FwdLockConv_Status_OutOfMemory;
    452         }
    453         pString->ptr = newPtr;
    454         pString->maxLength = newMaxLength;
    455     }
    456     pString->ptr[pString->length++] = ch;
    457     pString->ptr[pString->length] = '\0';
    458     return FwdLockConv_Status_OK;
    459 }
    460 
    461 /**
    462  * Attempts to recognize the MIME header name and changes the scanner state accordingly.
    463  *
    464  * @param[in,out] pSession A reference to a converter session.
    465  *
    466  * @return A status code.
    467  */
    468 static FwdLockConv_Status_t FwdLockConv_RecognizeMimeHeaderName(FwdLockConv_Session_t *pSession) {
    469     FwdLockConv_Status_t status = FwdLockConv_Status_OK;
    470     if (strncmp(pSession->mimeHeaderName.ptr, strContent, strlenContent) == 0) {
    471         if (strcmp(pSession->mimeHeaderName.ptr + strlenContent, strType) == 0) {
    472             if (pSession->contentType.ptr == NULL) {
    473                 pSession->scannerState = FwdLockConv_ScannerState_WantsContentTypeStart;
    474             } else {
    475                 status = FwdLockConv_Status_SyntaxError;
    476             }
    477         } else if (strcmp(pSession->mimeHeaderName.ptr + strlenContent, strTransferEncoding) == 0) {
    478             if (pSession->contentTransferEncoding ==
    479                     FwdLockConv_ContentTransferEncoding_Undefined) {
    480                 pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingStart;
    481             } else {
    482                 status = FwdLockConv_Status_SyntaxError;
    483             }
    484         } else {
    485             pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    486         }
    487     } else {
    488         pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    489     }
    490     return status;
    491 }
    492 
    493 /**
    494  * Applies defaults to missing MIME header values.
    495  *
    496  * @param[in,out] pSession A reference to a converter session.
    497  *
    498  * @return A status code.
    499  */
    500 static FwdLockConv_Status_t FwdLockConv_ApplyDefaults(FwdLockConv_Session_t *pSession) {
    501     if (pSession->contentType.ptr == NULL) {
    502         // Content type is missing: default to "text/plain".
    503         pSession->contentType.ptr = malloc(sizeof strTextPlain);
    504         if (pSession->contentType.ptr == NULL) {
    505             return FwdLockConv_Status_OutOfMemory;
    506         }
    507         memcpy(pSession->contentType.ptr, strTextPlain, sizeof strTextPlain);
    508         pSession->contentType.length = strlenTextPlain;
    509         pSession->contentType.maxLength = strlenTextPlain;
    510     }
    511     if (pSession->contentTransferEncoding == FwdLockConv_ContentTransferEncoding_Undefined) {
    512         // Content transfer encoding is missing: default to binary.
    513         pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary;
    514     }
    515     return FwdLockConv_Status_OK;
    516 }
    517 
    518 /**
    519  * Verifies that the content type is supported.
    520  *
    521  * @param[in,out] pSession A reference to a converter session.
    522  *
    523  * @return A status code.
    524  */
    525 static FwdLockConv_Status_t FwdLockConv_VerifyContentType(FwdLockConv_Session_t *pSession) {
    526     FwdLockConv_Status_t status;
    527     if (pSession->contentType.ptr == NULL) {
    528         status = FwdLockConv_Status_ProgramError;
    529     } else if (strcmp(pSession->contentType.ptr, strApplicationVndOmaDrmRightsXml) == 0 ||
    530                strcmp(pSession->contentType.ptr, strApplicationVndOmaDrmContent) == 0) {
    531         status = FwdLockConv_Status_UnsupportedFileFormat;
    532     } else {
    533         status = FwdLockConv_Status_OK;
    534     }
    535     return status;
    536 }
    537 
    538 /**
    539  * Writes the header of the output file.
    540  *
    541  * @param[in,out] pSession A reference to a converter session.
    542  * @param[out] pOutput The output from the conversion process.
    543  *
    544  * @return A status code.
    545  */
    546 static FwdLockConv_Status_t FwdLockConv_WriteHeader(FwdLockConv_Session_t *pSession,
    547                                                     FwdLockConv_Output_t *pOutput) {
    548     FwdLockConv_Status_t status;
    549     if (pSession->contentType.length > UCHAR_MAX) {
    550         status = FwdLockConv_Status_SyntaxError;
    551     } else {
    552         pSession->outputBufferSize = OUTPUT_BUFFER_SIZE_INCREMENT;
    553         pOutput->fromConvertData.pBuffer = malloc(pSession->outputBufferSize);
    554         if (pOutput->fromConvertData.pBuffer == NULL) {
    555             status = FwdLockConv_Status_OutOfMemory;
    556         } else {
    557             size_t encryptedSessionKeyPos = TOP_HEADER_SIZE + pSession->contentType.length;
    558             size_t dataSignaturePos = encryptedSessionKeyPos + pSession->encryptedSessionKeyLength;
    559             size_t headerSignaturePos = dataSignaturePos + SHA1_HASH_SIZE;
    560             pSession->dataOffset = headerSignaturePos + SHA1_HASH_SIZE;
    561             memcpy(pSession->topHeader, topHeaderTemplate, sizeof topHeaderTemplate);
    562             pSession->topHeader[CONTENT_TYPE_LENGTH_POS] =
    563                     (unsigned char)pSession->contentType.length;
    564             memcpy(pOutput->fromConvertData.pBuffer, pSession->topHeader, TOP_HEADER_SIZE);
    565             memcpy((char *)pOutput->fromConvertData.pBuffer + TOP_HEADER_SIZE,
    566                    pSession->contentType.ptr, pSession->contentType.length);
    567             memcpy((char *)pOutput->fromConvertData.pBuffer + encryptedSessionKeyPos,
    568                    pSession->pEncryptedSessionKey, pSession->encryptedSessionKeyLength);
    569 
    570             // Set the signatures to all zeros for now; they will have to be updated later.
    571             memset((char *)pOutput->fromConvertData.pBuffer + dataSignaturePos, 0,
    572                    SHA1_HASH_SIZE);
    573             memset((char *)pOutput->fromConvertData.pBuffer + headerSignaturePos, 0,
    574                    SHA1_HASH_SIZE);
    575 
    576             pOutput->fromConvertData.numBytes = pSession->dataOffset;
    577             status = FwdLockConv_Status_OK;
    578         }
    579     }
    580     return status;
    581 }
    582 
    583 /**
    584  * Matches the MIME headers.
    585  *
    586  * @param[in,out] pSession A reference to a converter session.
    587  * @param[in] ch A character.
    588  * @param[out] pOutput The output from the conversion process.
    589  *
    590  * @return A status code.
    591  */
    592 static FwdLockConv_Status_t FwdLockConv_MatchMimeHeaders(FwdLockConv_Session_t *pSession,
    593                                                          int ch,
    594                                                          FwdLockConv_Output_t *pOutput) {
    595     FwdLockConv_Status_t status = FwdLockConv_Status_OK;
    596     switch (pSession->scannerState) {
    597     case FwdLockConv_ScannerState_WantsMimeHeaderNameStart:
    598         if (FwdLockConv_IsMimeHeaderNameChar(ch)) {
    599             pSession->mimeHeaderName.length = 0;
    600             status = FwdLockConv_StringAppend(&pSession->mimeHeaderName, tolower(ch));
    601             if (status == FwdLockConv_Status_OK) {
    602                 pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderName;
    603             }
    604         } else if (ch == '\r') {
    605             pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeadersEnd;
    606         } else if (!FwdLockConv_IsWhitespace(ch)) {
    607             status = FwdLockConv_Status_SyntaxError;
    608         }
    609         break;
    610     case FwdLockConv_ScannerState_WantsMimeHeaderName:
    611         if (FwdLockConv_IsMimeHeaderNameChar(ch)) {
    612             status = FwdLockConv_StringAppend(&pSession->mimeHeaderName, tolower(ch));
    613         } else if (ch == ':') {
    614             status = FwdLockConv_RecognizeMimeHeaderName(pSession);
    615         } else if (FwdLockConv_IsWhitespace(ch)) {
    616             pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameEnd;
    617         } else {
    618             status = FwdLockConv_Status_SyntaxError;
    619         }
    620         break;
    621     case FwdLockConv_ScannerState_WantsMimeHeaderNameEnd:
    622         if (ch == ':') {
    623             status = FwdLockConv_RecognizeMimeHeaderName(pSession);
    624         } else if (!FwdLockConv_IsWhitespace(ch)) {
    625             status = FwdLockConv_Status_SyntaxError;
    626         }
    627         break;
    628     case FwdLockConv_ScannerState_WantsContentTypeStart:
    629         if (FwdLockConv_IsMimeHeaderValueChar(ch)) {
    630             status = FwdLockConv_StringAppend(&pSession->contentType, tolower(ch));
    631             if (status == FwdLockConv_Status_OK) {
    632                 pSession->scannerState = FwdLockConv_ScannerState_WantsContentType;
    633             }
    634         } else if (!FwdLockConv_IsWhitespace(ch)) {
    635             status = FwdLockConv_Status_SyntaxError;
    636         }
    637         break;
    638     case FwdLockConv_ScannerState_WantsContentType:
    639         if (FwdLockConv_IsMimeHeaderValueChar(ch)) {
    640             status = FwdLockConv_StringAppend(&pSession->contentType, tolower(ch));
    641         } else if (ch == ';') {
    642             pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    643         } else if (ch == '\r') {
    644             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    645         } else if (FwdLockConv_IsWhitespace(ch)) {
    646             pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderValueEnd;
    647         } else {
    648             status = FwdLockConv_Status_SyntaxError;
    649         }
    650         break;
    651     case FwdLockConv_ScannerState_WantsContentTransferEncodingStart:
    652         if (ch == 'b' || ch == 'B') {
    653             pSession->scannerState = FwdLockConv_ScannerState_Wants_A_OR_I;
    654         } else if (ch == '7' || ch == '8') {
    655             pSession->scannerState = FwdLockConv_ScannerState_Wants_B;
    656         } else if (!FwdLockConv_IsWhitespace(ch)) {
    657             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    658         }
    659         break;
    660     case FwdLockConv_ScannerState_Wants_A_OR_I:
    661         if (ch == 'i' || ch == 'I') {
    662             pSession->scannerState = FwdLockConv_ScannerState_Wants_N;
    663         } else if (ch == 'a' || ch == 'A') {
    664             pSession->scannerState = FwdLockConv_ScannerState_Wants_S;
    665         } else {
    666             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    667         }
    668         break;
    669     case FwdLockConv_ScannerState_Wants_N:
    670         if (ch == 'n' || ch == 'N') {
    671             pSession->scannerState = FwdLockConv_ScannerState_Wants_A;
    672         } else {
    673             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    674         }
    675         break;
    676     case FwdLockConv_ScannerState_Wants_A:
    677         if (ch == 'a' || ch == 'A') {
    678             pSession->scannerState = FwdLockConv_ScannerState_Wants_R;
    679         } else {
    680             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    681         }
    682         break;
    683     case FwdLockConv_ScannerState_Wants_R:
    684         if (ch == 'r' || ch == 'R') {
    685             pSession->scannerState = FwdLockConv_ScannerState_Wants_Y;
    686         } else {
    687             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    688         }
    689         break;
    690     case FwdLockConv_ScannerState_Wants_Y:
    691         if (ch == 'y' || ch == 'Y') {
    692             pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary;
    693             pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd;
    694         } else {
    695             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    696         }
    697         break;
    698     case FwdLockConv_ScannerState_Wants_S:
    699         if (ch == 's' || ch == 'S') {
    700             pSession->scannerState = FwdLockConv_ScannerState_Wants_E;
    701         } else {
    702             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    703         }
    704         break;
    705     case FwdLockConv_ScannerState_Wants_E:
    706         if (ch == 'e' || ch == 'E') {
    707             pSession->scannerState = FwdLockConv_ScannerState_Wants_6;
    708         } else {
    709             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    710         }
    711         break;
    712     case FwdLockConv_ScannerState_Wants_6:
    713         if (ch == '6') {
    714             pSession->scannerState = FwdLockConv_ScannerState_Wants_4;
    715         } else {
    716             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    717         }
    718         break;
    719     case FwdLockConv_ScannerState_Wants_4:
    720         if (ch == '4') {
    721             pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Base64;
    722             pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd;
    723         } else {
    724             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    725         }
    726         break;
    727     case FwdLockConv_ScannerState_Wants_B:
    728         if (ch == 'b' || ch == 'B') {
    729             pSession->scannerState = FwdLockConv_ScannerState_Wants_I;
    730         } else {
    731             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    732         }
    733         break;
    734     case FwdLockConv_ScannerState_Wants_I:
    735         if (ch == 'i' || ch == 'I') {
    736             pSession->scannerState = FwdLockConv_ScannerState_Wants_T;
    737         } else {
    738             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    739         }
    740         break;
    741     case FwdLockConv_ScannerState_Wants_T:
    742         if (ch == 't' || ch == 'T') {
    743             pSession->contentTransferEncoding = FwdLockConv_ContentTransferEncoding_Binary;
    744             pSession->scannerState = FwdLockConv_ScannerState_WantsContentTransferEncodingEnd;
    745         } else {
    746             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    747         }
    748         break;
    749     case FwdLockConv_ScannerState_WantsContentTransferEncodingEnd:
    750         if (ch == ';') {
    751             pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    752         } else if (ch == '\r') {
    753             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    754         } else if (FwdLockConv_IsWhitespace(ch)) {
    755             pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderValueEnd;
    756         } else {
    757             status = FwdLockConv_Status_UnsupportedContentTransferEncoding;
    758         }
    759         break;
    760     case FwdLockConv_ScannerState_WantsMimeHeaderValueEnd:
    761         if (ch == ';') {
    762             pSession->scannerState = FwdLockConv_ScannerState_WantsCR;
    763         } else if (ch == '\r') {
    764             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    765         } else if (!FwdLockConv_IsWhitespace(ch)) {
    766             status = FwdLockConv_Status_SyntaxError;
    767         }
    768         break;
    769     case FwdLockConv_ScannerState_WantsCR:
    770         if (ch == '\r') {
    771             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    772         }
    773         break;
    774     case FwdLockConv_ScannerState_WantsLF:
    775         if (ch == '\n') {
    776             pSession->scannerState = FwdLockConv_ScannerState_WantsMimeHeaderNameStart;
    777         } else {
    778             status = FwdLockConv_Status_SyntaxError;
    779         }
    780         break;
    781     case FwdLockConv_ScannerState_WantsMimeHeadersEnd:
    782         if (ch == '\n') {
    783             status = FwdLockConv_ApplyDefaults(pSession);
    784             if (status == FwdLockConv_Status_OK) {
    785                 status = FwdLockConv_VerifyContentType(pSession);
    786             }
    787             if (status == FwdLockConv_Status_OK) {
    788                 status = FwdLockConv_WriteHeader(pSession, pOutput);
    789             }
    790             if (status == FwdLockConv_Status_OK) {
    791                 if (pSession->contentTransferEncoding ==
    792                         FwdLockConv_ContentTransferEncoding_Binary) {
    793                     pSession->parserState = FwdLockConv_ParserState_WantsBinaryEncodedData;
    794                 } else {
    795                     pSession->parserState = FwdLockConv_ParserState_WantsBase64EncodedData;
    796                 }
    797                 pSession->scannerState = FwdLockConv_ScannerState_WantsByte1;
    798             }
    799         } else {
    800             status = FwdLockConv_Status_SyntaxError;
    801         }
    802         break;
    803     default:
    804         status = FwdLockConv_Status_ProgramError;
    805         break;
    806     }
    807     return status;
    808 }
    809 
    810 /**
    811  * Increments the counter, treated as a 16-byte little-endian number, by one.
    812  *
    813  * @param[in,out] pSession A reference to a converter session.
    814  */
    815 static void FwdLockConv_IncrementCounter(FwdLockConv_Session_t *pSession) {
    816     size_t i = 0;
    817     while ((++pSession->counter[i] == 0) && (++i < AES_BLOCK_SIZE))
    818         ;
    819 }
    820 
    821 /**
    822  * Encrypts the given character and writes it to the output buffer.
    823  *
    824  * @param[in,out] pSession A reference to a converter session.
    825  * @param[in] ch The character to encrypt and write.
    826  * @param[in,out] pOutput The output from the conversion process.
    827  *
    828  * @return A status code.
    829  */
    830 static FwdLockConv_Status_t FwdLockConv_WriteEncryptedChar(FwdLockConv_Session_t *pSession,
    831                                                            unsigned char ch,
    832                                                            FwdLockConv_Output_t *pOutput) {
    833     if (pOutput->fromConvertData.numBytes == pSession->outputBufferSize) {
    834         void *pBuffer;
    835         pSession->outputBufferSize += OUTPUT_BUFFER_SIZE_INCREMENT;
    836         pBuffer = realloc(pOutput->fromConvertData.pBuffer, pSession->outputBufferSize);
    837         if (pBuffer == NULL) {
    838             return FwdLockConv_Status_OutOfMemory;
    839         }
    840         pOutput->fromConvertData.pBuffer = pBuffer;
    841     }
    842     if (++pSession->keyStreamIndex == AES_BLOCK_SIZE) {
    843         FwdLockConv_IncrementCounter(pSession);
    844         pSession->keyStreamIndex = 0;
    845     }
    846     if (pSession->keyStreamIndex == 0) {
    847         AES_encrypt(pSession->counter, pSession->keyStream, &pSession->encryptionRoundKeys);
    848     }
    849     ch ^= pSession->keyStream[pSession->keyStreamIndex];
    850     ((unsigned char *)pOutput->fromConvertData.pBuffer)[pOutput->fromConvertData.numBytes++] = ch;
    851     ++pSession->numDataBytes;
    852     return FwdLockConv_Status_OK;
    853 }
    854 
    855 /**
    856  * Matches binary-encoded content data and encrypts it, while looking out for the close delimiter.
    857  *
    858  * @param[in,out] pSession A reference to a converter session.
    859  * @param[in] ch A character.
    860  * @param[in,out] pOutput The output from the conversion process.
    861  *
    862  * @return A status code.
    863  */
    864 static FwdLockConv_Status_t FwdLockConv_MatchBinaryEncodedData(FwdLockConv_Session_t *pSession,
    865                                                                int ch,
    866                                                                FwdLockConv_Output_t *pOutput) {
    867     FwdLockConv_Status_t status = FwdLockConv_Status_OK;
    868     switch (pSession->scannerState) {
    869     case FwdLockConv_ScannerState_WantsByte1:
    870         if (ch != pSession->delimiter[pSession->delimiterMatchPos]) {
    871             // The partial match of the delimiter turned out to be spurious. Flush the matched bytes
    872             // to the output buffer and start over.
    873             size_t i;
    874             for (i = 0; i < pSession->delimiterMatchPos; ++i) {
    875                 status = FwdLockConv_WriteEncryptedChar(pSession, pSession->delimiter[i], pOutput);
    876                 if (status != FwdLockConv_Status_OK) {
    877                     return status;
    878                 }
    879             }
    880             pSession->delimiterMatchPos = 0;
    881         }
    882         if (ch != pSession->delimiter[pSession->delimiterMatchPos]) {
    883             // The current character isn't part of the delimiter. Write it to the output buffer.
    884             status = FwdLockConv_WriteEncryptedChar(pSession, ch, pOutput);
    885         } else if (++pSession->delimiterMatchPos == pSession->delimiterLength) {
    886             // The entire delimiter has been matched. The only valid characters now are the "--"
    887             // that complete the close delimiter (no more message parts are expected).
    888             pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash;
    889         }
    890         break;
    891     case FwdLockConv_ScannerState_WantsFirstDash:
    892         if (ch == '-') {
    893             pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash;
    894         } else {
    895             status = FwdLockConv_Status_SyntaxError;
    896         }
    897         break;
    898     case FwdLockConv_ScannerState_WantsSecondDash:
    899         if (ch == '-') {
    900             pSession->parserState = FwdLockConv_ParserState_Done;
    901         } else {
    902             status = FwdLockConv_Status_SyntaxError;
    903         }
    904         break;
    905     default:
    906         status = FwdLockConv_Status_ProgramError;
    907         break;
    908     }
    909     return status;
    910 }
    911 
    912 /**
    913  * Checks whether a given character is valid in base64-encoded data.
    914  *
    915  * @param[in] ch The character to check.
    916  *
    917  * @return A Boolean value indicating whether the given character is valid in base64-encoded data.
    918  */
    919 static int FwdLockConv_IsBase64Char(int ch) {
    920     return 0 <= ch && ch <= 'z' && base64Values[ch] >= 0;
    921 }
    922 
    923 /**
    924  * Matches base64-encoded content data and encrypts it, while looking out for the close delimiter.
    925  *
    926  * @param[in,out] pSession A reference to a converter session.
    927  * @param[in] ch A character.
    928  * @param[in,out] pOutput The output from the conversion process.
    929  *
    930  * @return A status code.
    931  */
    932 static FwdLockConv_Status_t FwdLockConv_MatchBase64EncodedData(FwdLockConv_Session_t *pSession,
    933                                                                int ch,
    934                                                                FwdLockConv_Output_t *pOutput) {
    935     FwdLockConv_Status_t status = FwdLockConv_Status_OK;
    936     switch (pSession->scannerState) {
    937     case FwdLockConv_ScannerState_WantsByte1:
    938     case FwdLockConv_ScannerState_WantsByte1_AfterCRLF:
    939         if (FwdLockConv_IsBase64Char(ch)) {
    940             pSession->ch = base64Values[ch] << 2;
    941             pSession->scannerState = FwdLockConv_ScannerState_WantsByte2;
    942         } else if (ch == '\r') {
    943             pSession->savedScannerState = FwdLockConv_ScannerState_WantsByte1_AfterCRLF;
    944             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    945         } else if (ch == '-') {
    946             if (pSession->scannerState == FwdLockConv_ScannerState_WantsByte1_AfterCRLF) {
    947                 pSession->delimiterMatchPos = 3;
    948                 pSession->scannerState = FwdLockConv_ScannerState_WantsDelimiter;
    949             } else {
    950                 status = FwdLockConv_Status_SyntaxError;
    951             }
    952         } else if (!FwdLockConv_IsWhitespace(ch)) {
    953             status = FwdLockConv_Status_SyntaxError;
    954         }
    955         break;
    956     case FwdLockConv_ScannerState_WantsByte2:
    957         if (FwdLockConv_IsBase64Char(ch)) {
    958             pSession->ch |= base64Values[ch] >> 4;
    959             status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput);
    960             if (status == FwdLockConv_Status_OK) {
    961                 pSession->ch = base64Values[ch] << 4;
    962                 pSession->scannerState = FwdLockConv_ScannerState_WantsByte3;
    963             }
    964         } else if (ch == '\r') {
    965             pSession->savedScannerState = pSession->scannerState;
    966             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    967         } else if (!FwdLockConv_IsWhitespace(ch)) {
    968             status = FwdLockConv_Status_SyntaxError;
    969         }
    970         break;
    971     case FwdLockConv_ScannerState_WantsByte3:
    972         if (FwdLockConv_IsBase64Char(ch)) {
    973             pSession->ch |= base64Values[ch] >> 2;
    974             status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput);
    975             if (status == FwdLockConv_Status_OK) {
    976                 pSession->ch = base64Values[ch] << 6;
    977                 pSession->scannerState = FwdLockConv_ScannerState_WantsByte4;
    978             }
    979         } else if (ch == '\r') {
    980             pSession->savedScannerState = pSession->scannerState;
    981             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    982         } else if (ch == '=') {
    983             pSession->scannerState = FwdLockConv_ScannerState_WantsPadding;
    984         } else if (!FwdLockConv_IsWhitespace(ch)) {
    985             status = FwdLockConv_Status_SyntaxError;
    986         }
    987         break;
    988     case FwdLockConv_ScannerState_WantsByte4:
    989         if (FwdLockConv_IsBase64Char(ch)) {
    990             pSession->ch |= base64Values[ch];
    991             status = FwdLockConv_WriteEncryptedChar(pSession, pSession->ch, pOutput);
    992             if (status == FwdLockConv_Status_OK) {
    993                 pSession->scannerState = FwdLockConv_ScannerState_WantsByte1;
    994             }
    995         } else if (ch == '\r') {
    996             pSession->savedScannerState = pSession->scannerState;
    997             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
    998         } else if (ch == '=') {
    999             pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace;
   1000         } else if (!FwdLockConv_IsWhitespace(ch)) {
   1001             status = FwdLockConv_Status_SyntaxError;
   1002         }
   1003         break;
   1004     case FwdLockConv_ScannerState_WantsLF:
   1005         if (ch == '\n') {
   1006             pSession->scannerState = pSession->savedScannerState;
   1007         } else {
   1008             status = FwdLockConv_Status_SyntaxError;
   1009         }
   1010         break;
   1011     case FwdLockConv_ScannerState_WantsPadding:
   1012         if (ch == '=') {
   1013             pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace;
   1014         } else {
   1015             status = FwdLockConv_Status_SyntaxError;
   1016         }
   1017         break;
   1018     case FwdLockConv_ScannerState_WantsWhitespace:
   1019     case FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF:
   1020         if (ch == '\r') {
   1021             pSession->savedScannerState = FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF;
   1022             pSession->scannerState = FwdLockConv_ScannerState_WantsLF;
   1023         } else if (ch == '-') {
   1024             if (pSession->scannerState == FwdLockConv_ScannerState_WantsWhitespace_AfterCRLF) {
   1025                 pSession->delimiterMatchPos = 3;
   1026                 pSession->scannerState = FwdLockConv_ScannerState_WantsDelimiter;
   1027             } else {
   1028                 status = FwdLockConv_Status_SyntaxError;
   1029             }
   1030         } else if (FwdLockConv_IsWhitespace(ch)) {
   1031             pSession->scannerState = FwdLockConv_ScannerState_WantsWhitespace;
   1032         } else {
   1033             status = FwdLockConv_Status_SyntaxError;
   1034         }
   1035         break;
   1036     case FwdLockConv_ScannerState_WantsDelimiter:
   1037         if (ch != pSession->delimiter[pSession->delimiterMatchPos]) {
   1038             status = FwdLockConv_Status_SyntaxError;
   1039         } else if (++pSession->delimiterMatchPos == pSession->delimiterLength) {
   1040             pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash;
   1041         }
   1042         break;
   1043     case FwdLockConv_ScannerState_WantsFirstDash:
   1044         if (ch == '-') {
   1045             pSession->scannerState = FwdLockConv_ScannerState_WantsSecondDash;
   1046         } else {
   1047             status = FwdLockConv_Status_SyntaxError;
   1048         }
   1049         break;
   1050     case FwdLockConv_ScannerState_WantsSecondDash:
   1051         if (ch == '-') {
   1052             pSession->parserState = FwdLockConv_ParserState_Done;
   1053         } else {
   1054             status = FwdLockConv_Status_SyntaxError;
   1055         }
   1056         break;
   1057     default:
   1058         status = FwdLockConv_Status_ProgramError;
   1059         break;
   1060     }
   1061     return status;
   1062 }
   1063 
   1064 /**
   1065  * Pushes a single character into the converter's state machine.
   1066  *
   1067  * @param[in,out] pSession A reference to a converter session.
   1068  * @param[in] ch A character.
   1069  * @param[in,out] pOutput The output from the conversion process.
   1070  *
   1071  * @return A status code.
   1072  */
   1073 static FwdLockConv_Status_t FwdLockConv_PushChar(FwdLockConv_Session_t *pSession,
   1074                                                  int ch,
   1075                                                  FwdLockConv_Output_t *pOutput) {
   1076     FwdLockConv_Status_t status;
   1077     ++pSession->numCharsConsumed;
   1078     switch (pSession->parserState) {
   1079     case FwdLockConv_ParserState_WantsOpenDelimiter:
   1080         status = FwdLockConv_MatchOpenDelimiter(pSession, ch);
   1081         break;
   1082     case FwdLockConv_ParserState_WantsMimeHeaders:
   1083         status = FwdLockConv_MatchMimeHeaders(pSession, ch, pOutput);
   1084         break;
   1085     case FwdLockConv_ParserState_WantsBinaryEncodedData:
   1086         status = FwdLockConv_MatchBinaryEncodedData(pSession, ch, pOutput);
   1087         break;
   1088     case FwdLockConv_ParserState_WantsBase64EncodedData:
   1089         if (ch == '\n' && pSession->scannerState != FwdLockConv_ScannerState_WantsLF) {
   1090             // Repair base64-encoded data that doesn't have carriage returns in its line breaks.
   1091             status = FwdLockConv_MatchBase64EncodedData(pSession, '\r', pOutput);
   1092             if (status != FwdLockConv_Status_OK) {
   1093                 break;
   1094             }
   1095         }
   1096         status = FwdLockConv_MatchBase64EncodedData(pSession, ch, pOutput);
   1097         break;
   1098     case FwdLockConv_ParserState_Done:
   1099         status = FwdLockConv_Status_OK;
   1100         break;
   1101     default:
   1102         status = FwdLockConv_Status_ProgramError;
   1103         break;
   1104     }
   1105     return status;
   1106 }
   1107 
   1108 FwdLockConv_Status_t FwdLockConv_OpenSession(int *pSessionId, FwdLockConv_Output_t *pOutput) {
   1109     FwdLockConv_Status_t status;
   1110     if (pSessionId == NULL || pOutput == NULL) {
   1111         status = FwdLockConv_Status_InvalidArgument;
   1112     } else {
   1113         *pSessionId = FwdLockConv_AcquireSession();
   1114         if (*pSessionId < 0) {
   1115             status = FwdLockConv_Status_TooManySessions;
   1116         } else {
   1117             FwdLockConv_Session_t *pSession = sessionPtrs[*pSessionId];
   1118             pSession->encryptedSessionKeyLength = FwdLockGlue_GetEncryptedKeyLength(KEY_SIZE);
   1119             if (pSession->encryptedSessionKeyLength < AES_BLOCK_SIZE) {
   1120                 // The encrypted session key is used as the CTR-mode nonce, so it must be at least
   1121                 // the size of a single AES block.
   1122                 status = FwdLockConv_Status_ProgramError;
   1123             } else {
   1124                 pSession->pEncryptedSessionKey = malloc(pSession->encryptedSessionKeyLength);
   1125                 if (pSession->pEncryptedSessionKey == NULL) {
   1126                     status = FwdLockConv_Status_OutOfMemory;
   1127                 } else {
   1128                     if (!FwdLockGlue_GetRandomNumber(pSession->sessionKey, KEY_SIZE)) {
   1129                         status = FwdLockConv_Status_RandomNumberGenerationFailed;
   1130                     } else if (!FwdLockGlue_EncryptKey(pSession->sessionKey, KEY_SIZE,
   1131                                                        pSession->pEncryptedSessionKey,
   1132                                                        pSession->encryptedSessionKeyLength)) {
   1133                         status = FwdLockConv_Status_KeyEncryptionFailed;
   1134                     } else {
   1135                         status = FwdLockConv_DeriveKeys(pSession);
   1136                     }
   1137                     if (status == FwdLockConv_Status_OK) {
   1138                         memset(pSession->sessionKey, 0, KEY_SIZE); // Zero out key data.
   1139                         memcpy(pSession->counter, pSession->pEncryptedSessionKey, AES_BLOCK_SIZE);
   1140                         pSession->parserState = FwdLockConv_ParserState_WantsOpenDelimiter;
   1141                         pSession->scannerState = FwdLockConv_ScannerState_WantsFirstDash;
   1142                         pSession->numCharsConsumed = 0;
   1143                         pSession->delimiterMatchPos = 0;
   1144                         pSession->mimeHeaderName = nullString;
   1145                         pSession->contentType = nullString;
   1146                         pSession->contentTransferEncoding =
   1147                                 FwdLockConv_ContentTransferEncoding_Undefined;
   1148                         pSession->keyStreamIndex = -1;
   1149                         pOutput->fromConvertData.pBuffer = NULL;
   1150                         pOutput->fromConvertData.errorPos = INVALID_OFFSET;
   1151                     } else {
   1152                         free(pSession->pEncryptedSessionKey);
   1153                     }
   1154                 }
   1155             }
   1156             if (status != FwdLockConv_Status_OK) {
   1157                 FwdLockConv_ReleaseSession(*pSessionId);
   1158                 *pSessionId = -1;
   1159             }
   1160         }
   1161     }
   1162     return status;
   1163 }
   1164 
   1165 FwdLockConv_Status_t FwdLockConv_ConvertData(int sessionId,
   1166                                              const void *pBuffer,
   1167                                              size_t numBytes,
   1168                                              FwdLockConv_Output_t *pOutput) {
   1169     FwdLockConv_Status_t status;
   1170     if (!FwdLockConv_IsValidSession(sessionId) || pBuffer == NULL || pOutput == NULL) {
   1171         status = FwdLockConv_Status_InvalidArgument;
   1172     } else {
   1173         size_t i;
   1174         FwdLockConv_Session_t *pSession = sessionPtrs[sessionId];
   1175         pSession->dataOffset = 0;
   1176         pSession->numDataBytes = 0;
   1177         pOutput->fromConvertData.numBytes = 0;
   1178         status = FwdLockConv_Status_OK;
   1179 
   1180         for (i = 0; i < numBytes; ++i) {
   1181             status = FwdLockConv_PushChar(pSession, ((char *)pBuffer)[i], pOutput);
   1182             if (status != FwdLockConv_Status_OK) {
   1183                 break;
   1184             }
   1185         }
   1186         if (status == FwdLockConv_Status_OK) {
   1187             // Update the data signature.
   1188             HMAC_Update(&pSession->signingContext,
   1189                         &((unsigned char *)pOutput->fromConvertData.pBuffer)[pSession->dataOffset],
   1190                         pSession->numDataBytes);
   1191         } else if (status == FwdLockConv_Status_SyntaxError) {
   1192             pOutput->fromConvertData.errorPos = pSession->numCharsConsumed;
   1193         }
   1194     }
   1195     return status;
   1196 }
   1197 
   1198 FwdLockConv_Status_t FwdLockConv_CloseSession(int sessionId, FwdLockConv_Output_t *pOutput) {
   1199     FwdLockConv_Status_t status;
   1200     if (!FwdLockConv_IsValidSession(sessionId) || pOutput == NULL) {
   1201         status = FwdLockConv_Status_InvalidArgument;
   1202     } else {
   1203         FwdLockConv_Session_t *pSession = sessionPtrs[sessionId];
   1204         free(pOutput->fromConvertData.pBuffer);
   1205         if (pSession->parserState != FwdLockConv_ParserState_Done) {
   1206             pOutput->fromCloseSession.errorPos = pSession->numCharsConsumed;
   1207             status = FwdLockConv_Status_SyntaxError;
   1208         } else {
   1209             // Finalize the data signature.
   1210             unsigned int signatureSize = SHA1_HASH_SIZE;
   1211             HMAC_Final(&pSession->signingContext, pOutput->fromCloseSession.signatures,
   1212                        &signatureSize);
   1213             if (signatureSize != SHA1_HASH_SIZE) {
   1214                 status = FwdLockConv_Status_ProgramError;
   1215             } else {
   1216                 // Calculate the header signature, which is a signature of the rest of the header
   1217                 // including the data signature.
   1218                 HMAC_Init_ex(&pSession->signingContext, NULL, KEY_SIZE, NULL, NULL);
   1219                 HMAC_Update(&pSession->signingContext, pSession->topHeader, TOP_HEADER_SIZE);
   1220                 HMAC_Update(&pSession->signingContext, (unsigned char *)pSession->contentType.ptr,
   1221                             pSession->contentType.length);
   1222                 HMAC_Update(&pSession->signingContext, pSession->pEncryptedSessionKey,
   1223                             pSession->encryptedSessionKeyLength);
   1224                 HMAC_Update(&pSession->signingContext, pOutput->fromCloseSession.signatures,
   1225                             SHA1_HASH_SIZE);
   1226                 HMAC_Final(&pSession->signingContext,
   1227                            &pOutput->fromCloseSession.signatures[SHA1_HASH_SIZE], &signatureSize);
   1228                 if (signatureSize != SHA1_HASH_SIZE) {
   1229                     status = FwdLockConv_Status_ProgramError;
   1230                 } else {
   1231                     pOutput->fromCloseSession.fileOffset = TOP_HEADER_SIZE +
   1232                             pSession->contentType.length + pSession->encryptedSessionKeyLength;
   1233                     status = FwdLockConv_Status_OK;
   1234                 }
   1235             }
   1236             pOutput->fromCloseSession.errorPos = INVALID_OFFSET;
   1237         }
   1238         free(pSession->mimeHeaderName.ptr);
   1239         free(pSession->contentType.ptr);
   1240         free(pSession->pEncryptedSessionKey);
   1241         HMAC_CTX_cleanup(&pSession->signingContext);
   1242         FwdLockConv_ReleaseSession(sessionId);
   1243     }
   1244     return status;
   1245 }
   1246 
   1247 FwdLockConv_Status_t FwdLockConv_ConvertOpenFile(int inputFileDesc,
   1248                                                  FwdLockConv_ReadFunc_t *fpReadFunc,
   1249                                                  int outputFileDesc,
   1250                                                  FwdLockConv_WriteFunc_t *fpWriteFunc,
   1251                                                  FwdLockConv_LSeekFunc_t *fpLSeekFunc,
   1252                                                  off64_t *pErrorPos) {
   1253     FwdLockConv_Status_t status;
   1254     if (pErrorPos != NULL) {
   1255         *pErrorPos = INVALID_OFFSET;
   1256     }
   1257     if (fpReadFunc == NULL || fpWriteFunc == NULL || fpLSeekFunc == NULL || inputFileDesc < 0 ||
   1258         outputFileDesc < 0) {
   1259         status = FwdLockConv_Status_InvalidArgument;
   1260     } else {
   1261         char *pReadBuffer = malloc(READ_BUFFER_SIZE);
   1262         if (pReadBuffer == NULL) {
   1263             status = FwdLockConv_Status_OutOfMemory;
   1264         } else {
   1265             int sessionId;
   1266             FwdLockConv_Output_t output;
   1267             status = FwdLockConv_OpenSession(&sessionId, &output);
   1268             if (status == FwdLockConv_Status_OK) {
   1269                 ssize_t numBytesRead;
   1270                 FwdLockConv_Status_t closeStatus;
   1271                 while ((numBytesRead =
   1272                         fpReadFunc(inputFileDesc, pReadBuffer, READ_BUFFER_SIZE)) > 0) {
   1273                     status = FwdLockConv_ConvertData(sessionId, pReadBuffer, (size_t)numBytesRead,
   1274                                                      &output);
   1275                     if (status == FwdLockConv_Status_OK) {
   1276                         if (output.fromConvertData.pBuffer != NULL &&
   1277                             output.fromConvertData.numBytes > 0) {
   1278                             ssize_t numBytesWritten = fpWriteFunc(outputFileDesc,
   1279                                                                   output.fromConvertData.pBuffer,
   1280                                                                   output.fromConvertData.numBytes);
   1281                             if (numBytesWritten != (ssize_t)output.fromConvertData.numBytes) {
   1282                                 status = FwdLockConv_Status_FileWriteError;
   1283                                 break;
   1284                             }
   1285                         }
   1286                     } else {
   1287                         if (status == FwdLockConv_Status_SyntaxError && pErrorPos != NULL) {
   1288                             *pErrorPos = output.fromConvertData.errorPos;
   1289                         }
   1290                         break;
   1291                     }
   1292                 } // end while
   1293                 if (numBytesRead < 0) {
   1294                     status = FwdLockConv_Status_FileReadError;
   1295                 }
   1296                 closeStatus = FwdLockConv_CloseSession(sessionId, &output);
   1297                 if (status == FwdLockConv_Status_OK) {
   1298                     if (closeStatus != FwdLockConv_Status_OK) {
   1299                         if (closeStatus == FwdLockConv_Status_SyntaxError && pErrorPos != NULL) {
   1300                             *pErrorPos = output.fromCloseSession.errorPos;
   1301                         }
   1302                         status = closeStatus;
   1303                     } else if (fpLSeekFunc(outputFileDesc, output.fromCloseSession.fileOffset,
   1304                                            SEEK_SET) < 0) {
   1305                         status = FwdLockConv_Status_FileSeekError;
   1306                     } else if (fpWriteFunc(outputFileDesc, output.fromCloseSession.signatures,
   1307                                            FWD_LOCK_SIGNATURES_SIZE) != FWD_LOCK_SIGNATURES_SIZE) {
   1308                         status = FwdLockConv_Status_FileWriteError;
   1309                     }
   1310                 }
   1311             }
   1312             free(pReadBuffer);
   1313         }
   1314     }
   1315     return status;
   1316 }
   1317 
   1318 FwdLockConv_Status_t FwdLockConv_ConvertFile(const char *pInputFilename,
   1319                                              const char *pOutputFilename,
   1320                                              off64_t *pErrorPos) {
   1321     FwdLockConv_Status_t status;
   1322     if (pErrorPos != NULL) {
   1323         *pErrorPos = INVALID_OFFSET;
   1324     }
   1325     if (pInputFilename == NULL || pOutputFilename == NULL) {
   1326         status = FwdLockConv_Status_InvalidArgument;
   1327     } else {
   1328         int inputFileDesc = open(pInputFilename, O_RDONLY);
   1329         if (inputFileDesc < 0) {
   1330             status = FwdLockConv_Status_FileNotFound;
   1331         } else {
   1332             int outputFileDesc = open(pOutputFilename, O_CREAT | O_TRUNC | O_WRONLY,
   1333                                       S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
   1334             if (outputFileDesc < 0) {
   1335                 status = FwdLockConv_Status_FileCreationFailed;
   1336             } else {
   1337                 status = FwdLockConv_ConvertOpenFile(inputFileDesc, read, outputFileDesc, write,
   1338                                                      lseek64, pErrorPos);
   1339                 if (close(outputFileDesc) == 0 && status != FwdLockConv_Status_OK) {
   1340                     remove(pOutputFilename);
   1341                 }
   1342             }
   1343             (void)close(inputFileDesc);
   1344         }
   1345     }
   1346     return status;
   1347 }
   1348