1/*
2 * Copyright (c) 1998-2003,2010-2012 Apple Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please
7 * obtain a copy of the License at http://www.apple.com/publicsource and
8 * read it before using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
12 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
13 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
14 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
15 * Please see the License for the specific language governing rights and
16 * limitations under the License.
17 *
18 * cuEnc64.c - encode/decode in 64-char IA5 format, per RFC 1421
19 */
20
21#include "cuEnc64.h"
22#include <stdlib.h>
23#include <string.h>
24#include <stddef.h>
25
26#ifndef	NULL
27#define NULL ((void *)0)
28#endif	/* NULL */
29
30/*
31 * map a 6-bit binary value to a printable character.
32 */
33static const
34unsigned char bintoasc[] =
35	"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
36
37/*
38 * Map an 7-bit printable character to its corresponding binary value.
39 * Any illegal characters return high bit set.
40 */
41static const
42unsigned char asctobin[] =
43{
44    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
45    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
46    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
47    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
48    0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
49    0x80, 0x80, 0x80, 0x3e, 0x80, 0x80, 0x80, 0x3f,
50    0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b,
51    0x3c, 0x3d, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
52    0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
53    0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
54    0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
55    0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,
56    0x80, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20,
57    0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
58    0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
59    0x31, 0x32, 0x33, 0x80, 0x80, 0x80, 0x80, 0x80
60};
61
62/*
63 * map 6 bits to a printing char
64 */
65#define ENC(c) (bintoasc[((c) & 0x3f)])
66
67#define PAD		'='
68
69/*
70 * map one group of up to 3 bytes at inp to 4 bytes at outp.
71 * Count is number of valid bytes in *inp; if less than 3, the
72 * 1 or two extras must be zeros.
73 */
74static void encChunk(const unsigned char *inp,
75	unsigned char *outp,
76	int count)
77{
78	unsigned char c1, c2, c3, c4;
79
80	c1 = *inp >> 2;
81	c2 = ((inp[0] << 4) & 0x30) | ((inp[1] >> 4) & 0xf);
82	c3 = ((inp[1] << 2) & 0x3c) | ((inp[2] >> 6) & 0x3);
83	c4 = inp[2] & 0x3f;
84	*outp++ = ENC(c1);
85	*outp++ = ENC(c2);
86	if (count == 1) {
87	    *outp++ = PAD;
88	    *outp   = PAD;
89	} else {
90	    *outp++ = ENC(c3);
91	    if (count == 2) {
92		*outp = PAD;
93	    }
94	    else {
95		*outp = ENC(c4);
96	    }
97	}
98}
99
100/*
101 * Given input buffer inbuf, length inlen, encode to 64-char IA5 format.
102 * Result is fmalloc'd and returned; it is terminated by Microsoft-style
103 * newline and NULL. Its length (including the trailing newline and NULL)
104 * is returned in *outlen.
105 */
106
107unsigned char *cuEnc64(const unsigned char *inbuf,
108	unsigned inlen,
109	unsigned *outlen)		// RETURNED
110{
111	return cuEnc64WithLines(inbuf, inlen, 0, outlen);
112}
113
114unsigned char *cuEnc64WithLines(const unsigned char *inbuf,
115	unsigned inlen,
116	unsigned linelen,
117	unsigned *outlen)
118{
119	unsigned		outTextLen;
120	unsigned 		len;			// to malloc, liberal
121	unsigned		olen = 0;		// actual output size
122	unsigned char 	*outbuf;
123	unsigned char 	endbuf[3];
124	unsigned		i;
125	unsigned char 	*outp;
126	unsigned		numLines;
127	unsigned		thisLine;
128
129	outTextLen = ((inlen + 2) / 3) * 4;
130	if(linelen) {
131	    /*
132	     * linelen must be 0 mod 4 for this to work; round up...
133	     */
134	    if((linelen & 0x03) != 0) {
135	        linelen = (linelen + 3) & 0xfffffffc;
136	    }
137	    numLines = (outTextLen + linelen - 1)/ linelen;
138	}
139	else {
140	    numLines = 1;
141	}
142
143	/*
144	 * Total output size = encoded text size plus one newline per
145	 * line of output, plus trailing NULL. We always generate newlines
146	 * as \n; when decoding, we tolerate \r\n (Microsoft) or \n.
147	 */
148	len = outTextLen + (2 * numLines) + 1;
149	outbuf = (unsigned char*)malloc(len);
150	outp = outbuf;
151	thisLine = 0;
152
153	while(inlen) {
154	    if(inlen < 3) {
155			for(i=0; i<3; i++) {
156				if(i < inlen) {
157					endbuf[i] = inbuf[i];
158				}
159				else {
160					endbuf[i] = 0;
161				}
162			}
163			encChunk(endbuf, outp, inlen);
164			inlen = 0;
165	    }
166	    else {
167			encChunk(inbuf, outp, 3);
168			inlen -= 3;
169			inbuf += 3;
170	    }
171	    outp += 4;
172	    thisLine += 4;
173	    olen += 4;
174	    if((linelen != 0) && (thisLine >= linelen) && inlen) {
175	        /*
176			 * last trailing newline added below
177			 * Note we don't split 4-byte output chunks over newlines
178			 */
179	    	*outp++ = '\n';
180			olen++;
181			thisLine = 0;
182	    }
183	}
184	*outp++ = '\n';
185	*outp = '\0';
186	olen += 2;
187	*outlen = olen;
188	return outbuf;
189}
190
191static inline int isWhite(unsigned char c)
192{
193	switch(c) {
194	    case '\n':
195	    case '\r':
196	    case ' ':
197	    case '\t':
198	    case '\0':
199			return 1;
200	    default:
201			return 0;
202	}
203}
204
205/*
206 * Strip off all whitespace from a (supposedly) enc64-format string.
207 * Returns a malloc'd string.
208 */
209static unsigned char *stringCleanse(const unsigned char *inbuf,
210	unsigned inlen,
211	unsigned *outlen)
212{
213	unsigned char	*news;			// cleansed inbuf
214	unsigned		newsDex;		// index into news
215	unsigned		i;
216
217	news = (unsigned char*)malloc(inlen);
218	newsDex = 0;
219	for(i=0; i<inlen; i++) {
220	    if(!isWhite(inbuf[i])) {
221	        news[newsDex++] = inbuf[i];
222	    }
223	}
224	*outlen = newsDex;
225	return news;
226}
227
228/*
229 * Given input buffer inbuf, length inlen, decode from 64-char IA5 format to
230 * binary. Result is malloced and returned; its length is returned in *outlen.
231 * NULL return indicates corrupted input.
232 *
233 * All whitespace in input is ignored.
234 */
235unsigned char *cuDec64(const unsigned char *inbuf,
236	unsigned inlen,
237	unsigned *outlen)
238{
239	unsigned char 		*outbuf;
240	unsigned char 		*outp;			// malloc'd outbuf size
241	unsigned 			obuflen;
242	const unsigned char	*bp;
243	unsigned 			olen = 0;		// actual output size
244	unsigned char 		c1, c2, c3, c4;
245	unsigned char 		j;
246	unsigned			thisOlen;
247	unsigned char		*news;			// cleansed inbuf
248	unsigned			newsLen;
249
250	/*
251	 * Strip out all whitespace; remainder must be multiple of four
252	 * characters
253	 */
254	news = stringCleanse(inbuf, inlen, &newsLen);
255	if((newsLen & 0x03) != 0) {
256	    free(news);
257	    return (unsigned char*) NULL;
258	}
259	inlen = newsLen;
260	bp = news;
261
262	obuflen = (inlen / 4) * 3;
263	outbuf = (unsigned char*)malloc(obuflen);
264	outp = outbuf;
265
266	while (inlen) {
267	    /*
268	     * Note inlen is always a multiple of four here
269	     */
270	    if (*bp & 0x80 || (c1 = asctobin[*bp]) & 0x80) {
271	        goto errorOut;
272	    }
273	    inlen--;
274	    bp++;
275	    if (*bp & 0x80 || (c2 = asctobin[*bp]) & 0x80){
276	        goto errorOut;
277	    }
278	    inlen--;
279	    bp++;
280	    if (*bp == PAD) {
281			/*
282			 * two input bytes, one output byte
283			 */
284			c3 = c4 = 0;
285			thisOlen = 1;
286			if (c2 & 0xf) {
287				goto errorOut;
288			}
289			bp++;
290			inlen--;
291			if (*bp == PAD) {
292				bp++;
293				inlen--;
294				if(inlen > 0) {
295					goto errorOut;
296				}
297			}
298			else {
299				goto errorOut;
300			}
301	    } else if (*bp & 0x80 || (c3 = asctobin[*bp]) & 0x80) {
302	    	goto errorOut;
303	    } else {
304	        bp++;
305		inlen--;
306		if (*bp == PAD) {
307		    /*
308		     * Three input bytes, two output
309		     */
310		    c4 = 0;
311		    thisOlen = 2;
312		    if (c3 & 3) {
313				goto errorOut;
314		    }
315		} else if (*bp & 0x80 || (c4 = asctobin[*bp]) & 0x80) {
316		    goto errorOut;
317		} else {
318		    /*
319		     * Normal non-pad case
320		     */
321		    thisOlen = 3;
322		}
323		bp++;
324		inlen--;
325	    }
326	    j = (c1 << 2) | (c2 >> 4);
327	    *outp++ = j;
328	    if(thisOlen > 1) {
329			j = (c2 << 4) | (c3 >> 2);
330			*outp++ = j;
331			if(thisOlen == 3) {
332				j = (c3 << 6) | c4;
333				*outp++ = j;
334			}
335	    }
336	    olen += thisOlen;
337	}
338	free(news);
339	*outlen = olen;
340	return outbuf;			/* normal return */
341
342errorOut:
343	free(news);
344	free(outbuf);
345	return (unsigned char*) NULL;
346}
347
348/*
349 * Determine if specified input data is valid enc64 format. Returns 1
350 * if valid, 0 if not.
351 * This doesn't do a full enc64 parse job; it scans for legal characters
352 * and proper sync when a possible pad is found.
353 */
354int cuIsValidEnc64(const unsigned char *inbuf,
355	unsigned inlen)
356{
357	int padChars = 0;	// running count of PAD chars
358	int validEncChars = 0;
359	unsigned char c;
360
361	/*
362	 *   -- scan inbuf
363	 *   -- skip whitespace
364	 *   -- count valid chars
365	 *   -- ensure not more than 2 PAD chars, only at end
366	 *   -- ensure valid chars mod 4 == 0
367	 */
368
369	while(inlen) {
370	    c = *inbuf++;
371	    inlen--;
372	    if(isWhite(c)) {
373	        continue;
374	    }
375	    if(c == PAD) {
376			if(++padChars > 2) {
377				return 0;		// max of 2 PAD chars at end
378			}
379	    }
380	    else if(padChars > 0) {
381			return 0;		// no normal chars after seeing PAD
382	    }
383	    else if((c & 0x80) || ((asctobin[c]) & 0x80)) {
384			return 0;		// invalid encoded char
385	    }
386	    validEncChars++;
387	}
388	if((validEncChars & 0x03) != 0) {
389	    return 0;
390	}
391	else {
392	    return 1;
393	}
394}
395
396/*
397 * Text parsing routines.
398 *
399 * Search incoming text for specified string. Does not assume inText is
400 * NULL terminated. Returns pointer to start of found string in inText.
401 */
402static const char *findStr(
403	const char *inText,
404	unsigned inTextLen,
405	const char *str)				// NULL terminated - search for this
406{
407	/* probably not the hottest string search algorithm... */
408	const char *cp;
409	size_t srchStrLen = strlen(str);
410	char c = str[0];
411
412	/* last char * we can search in inText for start of str */
413	const char *endCp = inText + inTextLen - srchStrLen;
414
415	for(cp=inText; cp<=endCp; cp++) {
416		if(*cp == c) {
417			if(!memcmp(cp, str, srchStrLen)) {
418				return cp;
419			}
420		}
421	}
422	return NULL;
423}
424
425/*
426 * Obtain one line from current text. Returns a mallocd, NULL-terminated string
427 * which caller must free(). Also returns the number of chars consumed including
428 * the returned chars PLUS EOL terminators (\n and/or \r).
429 *
430 * ALWAYS returns a mallocd string if there is ANY data remaining per the
431 * incoming inTextLen. Returns NULL if inTextLen is zero.
432 */
433static const char *getLine(
434	const char *inText,
435	unsigned inTextLen,			// RETURNED
436	unsigned *consumed)			// RETURNED
437
438{
439	*consumed = 0;
440	const char *cp = inText;
441	const char *newline = NULL;		// if we found a newline, this points to the first one
442
443	while(inTextLen) {
444		char c = *cp;
445		if((c == '\r') || (c == '\n')) {
446			if(newline == NULL) {
447				/* first newline */
448				newline = cp;
449			}
450		}
451		else if(newline != NULL) {
452			/* non newline after newline, done */
453			break;
454		}
455		(*consumed)++;
456		inTextLen--;
457		cp++;
458	}
459	ptrdiff_t linelen;
460	if(newline) {
461		linelen = newline - inText;
462	}
463	else {
464		linelen = *consumed;
465	}
466	char *rtn = (char *)malloc(linelen + 1);
467	memmove(rtn, inText, linelen);
468	rtn[linelen] = 0;
469	return rtn;
470}
471
472#define UNSUPPORTED_FORMAT_ERR	-25256
473
474/*
475 * Given input buffer containing a PEM-encoded certificate, convert to DER
476 * and return in outbuf. Result is malloced and must be freed by caller;
477 * its length is returned in *outlen. Returns 0 on success.
478 */
479int cuConvertPem(
480	const unsigned char *inbuf,
481	unsigned inlen,
482	unsigned char **outbuf,	// RETURNED (caller must free)
483	unsigned *outlen)			// RETURNED
484{
485	unsigned lenToGo =  (inlen) ? inlen : 0;
486	const char	 *currCp = (inbuf) ? (const char *)inbuf : NULL;
487	const char *currLine = NULL;		// mallocd by getLine()
488	unsigned consumed;
489	int ortn = 0;
490	const char *start64;
491	unsigned base64Len;
492	const char *end64;
493	unsigned char *decData;
494	unsigned decDataLen;
495
496	/* search to START line, parse it to get type/format/alg */
497	const char *startLine = findStr(currCp, lenToGo, "-----BEGIN");
498	if(startLine != NULL) {
499		/* possibly skip over leading garbage */
500		consumed = (unsigned)(startLine - currCp);
501		lenToGo -= consumed;
502		currCp = startLine;
503
504		/* get C string of START line */
505		currLine = getLine(startLine, lenToGo, &consumed);
506		if(currLine == NULL) {
507			/* somehow got here with no data */
508			// assert(lenToGo == 0);
509			ortn = UNSUPPORTED_FORMAT_ERR;
510			goto errOut;
511		}
512		// assert(consumed <= lenToGo);
513		currCp  += consumed;
514		lenToGo -= consumed;
515
516		free((void *)currLine);
517	}
518
519	/* Skip empty lines.	 */
520	for( ; ; ) {
521		currLine = getLine(currCp, lenToGo, &consumed);
522		if(currLine == NULL) {
523			/* out of data */
524			ortn = UNSUPPORTED_FORMAT_ERR;
525			goto errOut;
526		}
527		int skipThis = 0;
528		size_t lineLen = strlen(currLine);
529		if(lineLen == 0) {
530			/* empty line */
531			skipThis = 1;
532		}
533		free((void *)currLine);
534
535		if(!skipThis) {
536			/* looks like good stuff; process */
537			break;
538		}
539		/* skip this line */
540		// assert(consumed <= lenToGo);
541		currCp  += consumed;
542		lenToGo -= consumed;
543	}
544	if(lenToGo == 0) {
545		/* no valid base64 data */
546		ortn = UNSUPPORTED_FORMAT_ERR;
547		goto errOut;
548	}
549
550	/*
551	 * currCP points to start of base64 data - mark it and search for end line.
552	 * We skip everything after the end line.
553	 */
554	start64 = currCp;
555	base64Len = lenToGo;			// if no END
556	end64 = findStr(currCp, lenToGo, "-----END");
557	if(end64 != NULL) {
558		if(end64 == start64) {
559			/* Empty, nothing between START and END */
560			ortn = UNSUPPORTED_FORMAT_ERR;
561			goto errOut;
562		}
563		base64Len = (unsigned)(end64 - start64);
564	}
565	/* else no END, no reason to complain about that as long as base64 decode works OK */
566
567	/* Base 64 decode */
568	decData = cuDec64((const unsigned char *)start64, base64Len, &decDataLen);
569	if(decData == NULL) {
570		/* bad base64 data */
571		ortn = UNSUPPORTED_FORMAT_ERR;
572		goto errOut;
573	}
574
575	if(outlen) {
576		*outlen = decDataLen;
577	}
578	if(outbuf) {
579		*outbuf = decData;
580	}
581	else {
582		free((void *)decData);
583	}
584
585errOut:
586	return ortn;
587}
588