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