1 /* 2 curlx.c Authors: Peter Sylvester, Jean-Paul Merlin 3 4 This is a little program to demonstrate the usage of 5 6 - an ssl initialisation callback setting a user key and trustbases 7 coming from a pkcs12 file 8 - using an ssl application callback to find a URI in the 9 certificate presented during ssl session establishment. 10 11 */ 12 /* <DESC> 13 * demonstrates use of SSL context callback, requires OpenSSL 14 * </DESC> 15 */ 16 17 /* 18 * Copyright (c) 2003 The OpenEvidence Project. All rights reserved. 19 * 20 * Redistribution and use in source and binary forms, with or without 21 * modification, are permitted provided that the following conditions 22 * are met: 23 * 24 * 1. Redistributions of source code must retain the above copyright 25 * notice, this list of conditions, the following disclaimer, 26 * and the original OpenSSL and SSLeay Licences below. 27 * 28 * 2. Redistributions in binary form must reproduce the above copyright 29 * notice, this list of conditions, the following disclaimer 30 * and the original OpenSSL and SSLeay Licences below in 31 * the documentation and/or other materials provided with the 32 * distribution. 33 * 34 * 3. All advertising materials mentioning features or use of this 35 * software must display the following acknowledgments: 36 * "This product includes software developed by the Openevidence Project 37 * for use in the OpenEvidence Toolkit. (http://www.openevidence.org/)" 38 * This product includes software developed by the OpenSSL Project 39 * for use in the OpenSSL Toolkit (https://www.openssl.org/)" 40 * This product includes cryptographic software written by Eric Young 41 * (eay (at) cryptsoft.com). This product includes software written by Tim 42 * Hudson (tjh (at) cryptsoft.com)." 43 * 44 * 4. The names "OpenEvidence Toolkit" and "OpenEvidence Project" must not be 45 * used to endorse or promote products derived from this software without 46 * prior written permission. For written permission, please contact 47 * openevidence-core (at) openevidence.org. 48 * 49 * 5. Products derived from this software may not be called "OpenEvidence" 50 * nor may "OpenEvidence" appear in their names without prior written 51 * permission of the OpenEvidence Project. 52 * 53 * 6. Redistributions of any form whatsoever must retain the following 54 * acknowledgments: 55 * "This product includes software developed by the OpenEvidence Project 56 * for use in the OpenEvidence Toolkit (http://www.openevidence.org/) 57 * This product includes software developed by the OpenSSL Project 58 * for use in the OpenSSL Toolkit (https://www.openssl.org/)" 59 * This product includes cryptographic software written by Eric Young 60 * (eay (at) cryptsoft.com). This product includes software written by Tim 61 * Hudson (tjh (at) cryptsoft.com)." 62 * 63 * THIS SOFTWARE IS PROVIDED BY THE OpenEvidence PROJECT ``AS IS'' AND ANY 64 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 65 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 66 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenEvidence PROJECT OR 67 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 68 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 69 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 70 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 71 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 72 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 73 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 74 * OF THE POSSIBILITY OF SUCH DAMAGE. 75 * ==================================================================== 76 * 77 * This product includes software developed by the OpenSSL Project 78 * for use in the OpenSSL Toolkit (https://www.openssl.org/) 79 * This product includes cryptographic software written by Eric Young 80 * (eay (at) cryptsoft.com). This product includes software written by Tim 81 * Hudson (tjh (at) cryptsoft.com). 82 * 83 */ 84 85 #include <stdio.h> 86 #include <stdlib.h> 87 #include <string.h> 88 #include <curl/curl.h> 89 #include <openssl/x509v3.h> 90 #include <openssl/x509_vfy.h> 91 #include <openssl/crypto.h> 92 #include <openssl/lhash.h> 93 #include <openssl/objects.h> 94 #include <openssl/err.h> 95 #include <openssl/evp.h> 96 #include <openssl/x509.h> 97 #include <openssl/pkcs12.h> 98 #include <openssl/bio.h> 99 #include <openssl/ssl.h> 100 101 static const char *curlx_usage[]={ 102 "usage: curlx args\n", 103 " -p12 arg - tia file ", 104 " -envpass arg - environement variable which content the tia private" 105 " key password", 106 " -out arg - output file (response)- default stdout", 107 " -in arg - input file (request)- default stdin", 108 " -connect arg - URL of the server for the connection ex:" 109 " www.openevidence.org", 110 " -mimetype arg - MIME type for data in ex : application/timestamp-query" 111 " or application/dvcs -default application/timestamp-query", 112 " -acceptmime arg - MIME type acceptable for the response ex : " 113 "application/timestamp-response or application/dvcs -default none", 114 " -accesstype arg - an Object identifier in an AIA/SIA method, e.g." 115 " AD_DVCS or ad_timestamping", 116 NULL 117 }; 118 119 /* 120 121 ./curlx -p12 psy.p12 -envpass XX -in request -verbose -accesstype AD_DVCS 122 -mimetype application/dvcs -acceptmime application/dvcs -out response 123 124 */ 125 126 /* 127 * We use this ZERO_NULL to avoid picky compiler warnings, 128 * when assigning a NULL pointer to a function pointer var. 129 */ 130 131 #define ZERO_NULL 0 132 133 /* This is a context that we pass to all callbacks */ 134 135 typedef struct sslctxparm_st { 136 unsigned char *p12file; 137 const char *pst; 138 PKCS12 *p12; 139 EVP_PKEY *pkey; 140 X509 *usercert; 141 STACK_OF(X509) * ca; 142 CURL *curl; 143 BIO *errorbio; 144 int accesstype; 145 int verbose; 146 147 } sslctxparm; 148 149 /* some helper function. */ 150 151 static char *ia5string(ASN1_IA5STRING *ia5) 152 { 153 char *tmp; 154 if(!ia5 || !ia5->length) 155 return NULL; 156 tmp = OPENSSL_malloc(ia5->length + 1); 157 memcpy(tmp, ia5->data, ia5->length); 158 tmp[ia5->length] = 0; 159 return tmp; 160 } 161 162 /* A conveniance routine to get an access URI. */ 163 static unsigned char *my_get_ext(X509 *cert, const int type, 164 int extensiontype) 165 { 166 int i; 167 STACK_OF(ACCESS_DESCRIPTION) * accessinfo; 168 accessinfo = X509_get_ext_d2i(cert, extensiontype, NULL, NULL); 169 170 if(!sk_ACCESS_DESCRIPTION_num(accessinfo)) 171 return NULL; 172 for(i = 0; i < sk_ACCESS_DESCRIPTION_num(accessinfo); i++) { 173 ACCESS_DESCRIPTION * ad = sk_ACCESS_DESCRIPTION_value(accessinfo, i); 174 if(OBJ_obj2nid(ad->method) == type) { 175 if(ad->location->type == GEN_URI) { 176 return ia5string(ad->location->d.ia5); 177 } 178 return NULL; 179 } 180 } 181 return NULL; 182 } 183 184 /* This is an application verification call back, it does not 185 perform any addition verification but tries to find a URL 186 in the presented certificat. If found, this will become 187 the URL to be used in the POST. 188 */ 189 190 static int ssl_app_verify_callback(X509_STORE_CTX *ctx, void *arg) 191 { 192 sslctxparm * p = (sslctxparm *) arg; 193 int ok; 194 195 if(p->verbose > 2) 196 BIO_printf(p->errorbio, "entering ssl_app_verify_callback\n"); 197 198 if((ok= X509_verify_cert(ctx)) && ctx->cert) { 199 unsigned char *accessinfo; 200 if(p->verbose > 1) 201 X509_print_ex(p->errorbio, ctx->cert, 0, 0); 202 203 if(accessinfo = my_get_ext(ctx->cert, p->accesstype, NID_sinfo_access)) { 204 if(p->verbose) 205 BIO_printf(p->errorbio, "Setting URL from SIA to: %s\n", accessinfo); 206 207 curl_easy_setopt(p->curl, CURLOPT_URL, accessinfo); 208 } 209 else if(accessinfo = my_get_ext(ctx->cert, p->accesstype, 210 NID_info_access)) { 211 if(p->verbose) 212 BIO_printf(p->errorbio, "Setting URL from AIA to: %s\n", accessinfo); 213 214 curl_easy_setopt(p->curl, CURLOPT_URL, accessinfo); 215 } 216 } 217 if(p->verbose > 2) 218 BIO_printf(p->errorbio, "leaving ssl_app_verify_callback with %d\n", ok); 219 220 return ok; 221 } 222 223 224 /* The SSL initialisation callback. The callback sets: 225 - a private key and certificate 226 - a trusted ca certificate 227 - a preferred cipherlist 228 - an application verification callback (the function above) 229 */ 230 231 static CURLcode sslctxfun(CURL *curl, void *sslctx, void *parm) 232 { 233 sslctxparm *p = (sslctxparm *) parm; 234 SSL_CTX *ctx = (SSL_CTX *) sslctx; 235 236 if(!SSL_CTX_use_certificate(ctx, p->usercert)) { 237 BIO_printf(p->errorbio, "SSL_CTX_use_certificate problem\n"); 238 goto err; 239 } 240 if(!SSL_CTX_use_PrivateKey(ctx, p->pkey)) { 241 BIO_printf(p->errorbio, "SSL_CTX_use_PrivateKey\n"); 242 goto err; 243 } 244 245 if(!SSL_CTX_check_private_key(ctx)) { 246 BIO_printf(p->errorbio, "SSL_CTX_check_private_key\n"); 247 goto err; 248 } 249 250 SSL_CTX_set_quiet_shutdown(ctx, 1); 251 SSL_CTX_set_cipher_list(ctx, "RC4-MD5"); 252 SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); 253 254 X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), 255 sk_X509_value(p->ca, sk_X509_num(p->ca)-1)); 256 257 SSL_CTX_set_verify_depth(ctx, 2); 258 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, ZERO_NULL); 259 SSL_CTX_set_cert_verify_callback(ctx, ssl_app_verify_callback, parm); 260 261 return CURLE_OK; 262 err: 263 ERR_print_errors(p->errorbio); 264 return CURLE_SSL_CERTPROBLEM; 265 266 } 267 268 int main(int argc, char **argv) 269 { 270 BIO* in=NULL; 271 BIO* out=NULL; 272 273 char *outfile = NULL; 274 char *infile = NULL; 275 276 int tabLength=100; 277 char *binaryptr; 278 char *mimetype; 279 char *mimetypeaccept=NULL; 280 char *contenttype; 281 const char **pp; 282 unsigned char *hostporturl = NULL; 283 BIO *p12bio; 284 char **args = argv + 1; 285 unsigned char *serverurl; 286 sslctxparm p; 287 char *response; 288 289 CURLcode res; 290 struct curl_slist *headers=NULL; 291 int badarg=0; 292 293 binaryptr = malloc(tabLength); 294 295 p.verbose = 0; 296 p.errorbio = BIO_new_fp (stderr, BIO_NOCLOSE); 297 298 curl_global_init(CURL_GLOBAL_DEFAULT); 299 300 /* we need some more for the P12 decoding */ 301 302 OpenSSL_add_all_ciphers(); 303 OpenSSL_add_all_digests(); 304 ERR_load_crypto_strings(); 305 306 while(*args && *args[0] == '-') { 307 if(!strcmp (*args, "-in")) { 308 if(args[1]) { 309 infile=*(++args); 310 } 311 else 312 badarg=1; 313 } 314 else if(!strcmp (*args, "-out")) { 315 if(args[1]) { 316 outfile=*(++args); 317 } 318 else 319 badarg=1; 320 } 321 else if(!strcmp (*args, "-p12")) { 322 if(args[1]) { 323 p.p12file = *(++args); 324 } 325 else 326 badarg=1; 327 } 328 else if(strcmp(*args, "-envpass") == 0) { 329 if(args[1]) { 330 p.pst = getenv(*(++args)); 331 } 332 else 333 badarg=1; 334 } 335 else if(strcmp(*args, "-connect") == 0) { 336 if(args[1]) { 337 hostporturl = *(++args); 338 } 339 else 340 badarg=1; 341 } 342 else if(strcmp(*args, "-mimetype") == 0) { 343 if(args[1]) { 344 mimetype = *(++args); 345 } 346 else 347 badarg=1; 348 } 349 else if(strcmp(*args, "-acceptmime") == 0) { 350 if(args[1]) { 351 mimetypeaccept = *(++args); 352 } 353 else 354 badarg=1; 355 } 356 else if(strcmp(*args, "-accesstype") == 0) { 357 if(args[1]) { 358 if((p.accesstype = OBJ_obj2nid(OBJ_txt2obj(*++args, 0))) == 0) 359 badarg=1; 360 } 361 else 362 badarg=1; 363 } 364 else if(strcmp(*args, "-verbose") == 0) { 365 p.verbose++; 366 } 367 else 368 badarg=1; 369 args++; 370 } 371 372 if(mimetype==NULL || mimetypeaccept == NULL) 373 badarg = 1; 374 375 if(badarg) { 376 for(pp=curlx_usage; (*pp != NULL); pp++) 377 BIO_printf(p.errorbio, "%s\n", *pp); 378 BIO_printf(p.errorbio, "\n"); 379 goto err; 380 } 381 382 /* set input */ 383 384 if((in=BIO_new(BIO_s_file())) == NULL) { 385 BIO_printf(p.errorbio, "Error setting input bio\n"); 386 goto err; 387 } 388 else if(infile == NULL) 389 BIO_set_fp(in, stdin, BIO_NOCLOSE|BIO_FP_TEXT); 390 else if(BIO_read_filename(in, infile) <= 0) { 391 BIO_printf(p.errorbio, "Error opening input file %s\n", infile); 392 BIO_free(in); 393 goto err; 394 } 395 396 /* set output */ 397 398 if((out=BIO_new(BIO_s_file())) == NULL) { 399 BIO_printf(p.errorbio, "Error setting output bio.\n"); 400 goto err; 401 } 402 else if(outfile == NULL) 403 BIO_set_fp(out, stdout, BIO_NOCLOSE|BIO_FP_TEXT); 404 else if(BIO_write_filename(out, outfile) <= 0) { 405 BIO_printf(p.errorbio, "Error opening output file %s\n", outfile); 406 BIO_free(out); 407 goto err; 408 } 409 410 411 p.errorbio = BIO_new_fp (stderr, BIO_NOCLOSE); 412 413 if(!(p.curl = curl_easy_init())) { 414 BIO_printf(p.errorbio, "Cannot init curl lib\n"); 415 goto err; 416 } 417 418 if(!(p12bio = BIO_new_file(p.p12file, "rb"))) { 419 BIO_printf(p.errorbio, "Error opening P12 file %s\n", p.p12file); 420 goto err; 421 } 422 if(!(p.p12 = d2i_PKCS12_bio (p12bio, NULL))) { 423 BIO_printf(p.errorbio, "Cannot decode P12 structure %s\n", p.p12file); 424 goto err; 425 } 426 427 p.ca= NULL; 428 if(!(PKCS12_parse (p.p12, p.pst, &(p.pkey), &(p.usercert), &(p.ca) ) )) { 429 BIO_printf(p.errorbio, "Invalid P12 structure in %s\n", p.p12file); 430 goto err; 431 } 432 433 if(sk_X509_num(p.ca) <= 0) { 434 BIO_printf(p.errorbio, "No trustworthy CA given.%s\n", p.p12file); 435 goto err; 436 } 437 438 if(p.verbose > 1) 439 X509_print_ex(p.errorbio, p.usercert, 0, 0); 440 441 /* determine URL to go */ 442 443 if(hostporturl) { 444 size_t len = strlen(hostporturl) + 9; 445 serverurl = malloc(len); 446 snprintf(serverurl, len, "https://%s", hostporturl); 447 } 448 else if(p.accesstype != 0) { /* see whether we can find an AIA or SIA for a 449 given access type */ 450 if(!(serverurl = my_get_ext(p.usercert, p.accesstype, NID_info_access))) { 451 int j=0; 452 BIO_printf(p.errorbio, "no service URL in user cert " 453 "cherching in others certificats\n"); 454 for(j=0; j<sk_X509_num(p.ca); j++) { 455 if((serverurl = my_get_ext(sk_X509_value(p.ca, j), p.accesstype, 456 NID_info_access))) 457 break; 458 if((serverurl = my_get_ext(sk_X509_value(p.ca, j), p.accesstype, 459 NID_sinfo_access))) 460 break; 461 } 462 } 463 } 464 465 if(!serverurl) { 466 BIO_printf(p.errorbio, "no service URL in certificats," 467 " check '-accesstype (AD_DVCS | ad_timestamping)'" 468 " or use '-connect'\n"); 469 goto err; 470 } 471 472 if(p.verbose) 473 BIO_printf(p.errorbio, "Service URL: <%s>\n", serverurl); 474 475 curl_easy_setopt(p.curl, CURLOPT_URL, serverurl); 476 477 /* Now specify the POST binary data */ 478 479 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDS, binaryptr); 480 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDSIZE, (long)tabLength); 481 482 /* pass our list of custom made headers */ 483 484 contenttype = malloc(15+strlen(mimetype)); 485 snprintf(contenttype, 15+strlen(mimetype), "Content-type: %s", mimetype); 486 headers = curl_slist_append(headers, contenttype); 487 curl_easy_setopt(p.curl, CURLOPT_HTTPHEADER, headers); 488 489 if(p.verbose) 490 BIO_printf(p.errorbio, "Service URL: <%s>\n", serverurl); 491 492 { 493 FILE *outfp; 494 BIO_get_fp(out, &outfp); 495 curl_easy_setopt(p.curl, CURLOPT_WRITEDATA, outfp); 496 } 497 498 res = curl_easy_setopt(p.curl, CURLOPT_SSL_CTX_FUNCTION, sslctxfun); 499 500 if(res != CURLE_OK) 501 BIO_printf(p.errorbio, "%d %s=%d %d\n", __LINE__, 502 "CURLOPT_SSL_CTX_FUNCTION", CURLOPT_SSL_CTX_FUNCTION, res); 503 504 curl_easy_setopt(p.curl, CURLOPT_SSL_CTX_DATA, &p); 505 506 { 507 int lu; int i=0; 508 while((lu = BIO_read (in, &binaryptr[i], tabLength-i)) >0) { 509 i+=lu; 510 if(i== tabLength) { 511 tabLength+=100; 512 binaryptr=realloc(binaryptr, tabLength); /* should be more careful */ 513 } 514 } 515 tabLength = i; 516 } 517 /* Now specify the POST binary data */ 518 519 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDS, binaryptr); 520 curl_easy_setopt(p.curl, CURLOPT_POSTFIELDSIZE, (long)tabLength); 521 522 523 /* Perform the request, res will get the return code */ 524 525 BIO_printf(p.errorbio, "%d %s %d\n", __LINE__, "curl_easy_perform", 526 res = curl_easy_perform(p.curl)); 527 { 528 int result =curl_easy_getinfo(p.curl, CURLINFO_CONTENT_TYPE, &response); 529 if(mimetypeaccept && p.verbose) 530 if(!strcmp(mimetypeaccept, response)) 531 BIO_printf(p.errorbio, "the response has a correct mimetype : %s\n", 532 response); 533 else 534 BIO_printf(p.errorbio, "the response doesn\'t have an acceptable " 535 "mime type, it is %s instead of %s\n", 536 response, mimetypeaccept); 537 } 538 539 /*** code d'erreur si accept mime ***, egalement code return HTTP != 200 ***/ 540 541 /* free the header list*/ 542 543 curl_slist_free_all(headers); 544 545 /* always cleanup */ 546 curl_easy_cleanup(p.curl); 547 548 BIO_free(in); 549 BIO_free(out); 550 return (EXIT_SUCCESS); 551 552 err: BIO_printf(p.errorbio, "error"); 553 exit(1); 554 } 555