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