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