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