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 */
60typedef 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 */
71typedef 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 */
113typedef 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 */
122typedef 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 */
132typedef 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
158static FwdLockConv_Session_t *sessionPtrs[MAX_NUM_SESSIONS] = { NULL };
159
160static pthread_mutex_t sessionAcquisitionMutex = PTHREAD_MUTEX_INITIALIZER;
161
162static const FwdLockConv_String_t nullString = { NULL, 0, 0, STRING_LENGTH_INCREMENT };
163
164static const unsigned char topHeaderTemplate[] =
165    { 'F', 'W', 'L', 'K', FWD_LOCK_VERSION, FWD_LOCK_SUBFORMAT, USAGE_RESTRICTION_FLAGS };
166
167static const char strContent[] = "content-";
168static const char strType[] = "type";
169static const char strTransferEncoding[] = "transfer-encoding";
170static const char strTextPlain[] = "text/plain";
171static const char strApplicationVndOmaDrmRightsXml[] = "application/vnd.oma.drm.rights+xml";
172static const char strApplicationVndOmaDrmContent[] = "application/vnd.oma.drm.content";
173
174static const size_t strlenContent = sizeof strContent - 1;
175static const size_t strlenTextPlain = sizeof strTextPlain - 1;
176
177static 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 */
193static 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 */
217static 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 */
226static 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 */
242static 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 */
286static 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 */
300static 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 */
311static 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 */
330static 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 */
423static 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 */
434static 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 */
446static 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 */
468static 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 */
500static 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 */
525static 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 */
546static 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 */
592static 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 */
815static 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 */
830static 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 */
864static 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 */
919static 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 */
932static 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 */
1073static 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
1108FwdLockConv_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
1165FwdLockConv_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
1198FwdLockConv_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
1247FwdLockConv_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