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