babelouest / ulfius

Web Framework to build REST APIs, Webservices or any HTTP endpoint in C language. Can stream large amount of data, integrate JSON data with Jansson, and create websocket services
https://babelouest.github.io/ulfius
GNU Lesser General Public License v2.1
1.07k stars 183 forks source link

How to setup https mode? #253

Closed dancesWithCycles closed 1 year ago

dancesWithCycles commented 1 year ago

Hi folks, Thank you so much for maintaining this repository.

I have a ulfius-based API service running behind an Apache Proxy Virtual Host. The thing is, that Virtual Host is using HTTPS on port 443 as it is standard for web servers.

When I call an endpoint of my ulfius-based API service the Proxy is complaining about a lack in HTTPS ability like this.

Proxy Error
The proxy server could not handle the request

Reason: Error during SSL Handshake with remote server

To solve this, I could switch to a Proxy in HTTP mode. Anyhow, that is not what I am aiming for. I am aiming for a production solution using standard HTTPS.

I appreciate any hint about how to enable HTTPS in my ulfius-based API service.

Cheers!

dancesWithCycles commented 1 year ago

My guess is that this is the way I am supposed to go for HTTPS, right?

int ulfius_start_secure_framework(struct _u_instance u_instance, const char key_pem, const char * cert_pem);

or

ulfius_start_secure_ca_trust_framework(struct _u_instance u_instance, const char key_pem, const char cert_pem, const char root_ca_pem);

dancesWithCycles commented 1 year ago

Do you allow me a question about ulfius_start_secure_framework?

I am loading the entire key and cert file into a char pointer respectively to use those two pointers as arguments like this.

frameworkRet = ulfius_start_secure_framework(&instance,bufP,bufF);

The function returns 4 instead of U_OK. It looks like the 4 stands for:

define U_ERROR_LIBMHD 4 ///< Error in libmicrohttpd execution

Am I asked to chop first and last line of key and cert of the char buffer?

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

To be sure and rule out an error I made sure libgnutls is installed.

sudo apt install libgnutls28-dev --no-install-recommends
babelouest commented 1 year ago

Hello,

You have to generate or use a pair of private key/certificate to use Ulfius in https mode.

Besides the documentation, you have this thread in the discussion which addresses it. Hope that helps.

dancesWithCycles commented 1 year ago

Hi @babelouest , Thank you so much for your advice and the link. My example is more or less the same as your example. Anyhow, the function

frameworkRet = ulfius_start_secure_framework(&instance,bufP,bufF);

still returns 4 instead of U_OK.

This is how I generated the key/certificate according to your post.

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365

This is my example code.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ulfius.h>

#define ROUTE_HELLO "/hello"

/**
 * static read file function
 */
static char * read_file(const char * filename) {
  char * buffer = NULL;
  long length;
  FILE * f;
  if (filename != NULL) {
    f = fopen (filename, "rb");
    if (f) {
      fseek (f, 0, SEEK_END);
      length = ftell (f);
      fseek (f, 0, SEEK_SET);
      buffer = malloc ((size_t)(length + 1));
      if (buffer != NULL) {
        fread (buffer, 1, (size_t)length, f);
        buffer[length] = '\0';
      }
      fclose (f);
    }
    return buffer;
  } else {
    return NULL;
  }
}

/**
 * callback functions declaration
 */
int callback_get_hello (const struct _u_request * request, struct _u_response * response, void * user_data);

int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data);

int main(int argc, char *argv[]) {
  /*declaration*/
  struct _u_instance instance;
  unsigned int port;
  int ret;

  printf("main() Started...\n");
  if (argc < 2) {
    fprintf(stderr, "Usage: %s <port>\n", argv[0]);
    exit(1);
  }

  printf("main() argv[1]: %s\n", argv[1]);

  /*store command line argument in int variable*/
  /*validate user input*/
  /*omit injection*/
  /*stream:argv*/
  ret = sscanf(argv[1], "%d", &port);

  /*valid user input:1 successfully filled item*/
  if (ret != 1) {
    fprintf(stderr, "The argument must be an integer\n");
    exit(1);
  }

  if (port < 0) {
    fprintf(stderr, "Error passing a negative port\n");
    exit(1);
  }
  printf("main() port: %d\n", port);

  if (ulfius_init_instance(&instance, port, NULL, NULL) != U_OK) {
    printf("main() Error ulfius_init_instance, abort");
    return(1);
  }

  //TODO Why?
  u_map_put(instance.default_headers, "Access-Control-Allow-Origin", "*");  

  // Maximum body size sent by the client is 1 Kb
  instance.max_post_body_size = 1024;

  // Endpoint list declaration
  ulfius_add_endpoint_by_val(&instance, "GET", ROUTE_HELLO, NULL, 0, &callback_get_hello, NULL);

  // default_endpoint declaration
  ulfius_set_default_endpoint(&instance, &callback_default, NULL);

  // Start the framework
  if (argc == 5 && strcmp("-secure", argv[2]) == 0) {
    printf("main() https mode\n");
    // If command-line options are -secure <key_file> <cert_file>, then open an https connection
    char * key_pem = read_file(argv[3]);
    char * cert_pem = read_file(argv[4]);
    ret = ulfius_start_secure_framework(&instance, key_pem, cert_pem);
    free(key_pem);
    free(cert_pem);
  } else {
    printf("main() http mode\n");
    // Open an http connection
    ret = ulfius_start_framework(&instance);
  }

  if (ret == U_OK) {
    printf("main() Start framework on port %d", instance.port);

    // Wait for the user to press <enter> on the console to quit the application
    getchar();
  } else {
    printf("main() Error starting framework; frameworkRet: %d\n",ret);
  }

  printf("main() End framework\n");

  ulfius_stop_framework(&instance);
  ulfius_clean_instance(&instance);

  printf("main() Done.\n");

  return 0;
}

/**
 * Callback function that put a "Hello World!" string in the response
 */
int callback_get_hello (const struct _u_request * request, struct _u_response * response, void * user_data) {
  ulfius_set_string_body_response(response, 200, "Hello World!");
  return U_CALLBACK_CONTINUE;
}

/**
 * Default callback function called if no endpoint has a match
 */
int callback_default (const struct _u_request * request, struct _u_response * response, void * user_data) {
  ulfius_set_string_body_response(response, 404, "Page not found, do what you want");
  return U_CALLBACK_CONTINUE;
}

This is my makefile.

# Others
RM      = /bin/rm -f
#
# Source, Executable, Includes, Library Defines
EXE    = main
#
# Compiler, Linker Defines
CC      = /usr/bin/gcc
#
all: main.o
    $(CC) main.c -L/usr/lib/x86_64-linux-gnu -lulfius -Wall -o $(EXE)
#
main.o:
    $(CC) -c main.c
# Clean Up Objects, Exectuables, Dumps out of source directory
clean:
    $(RM) *.o $(EXE) *~

This is the make instruction.

make

This is the run instruction with the error reply.

 ./main 65535 -secure key.pem cert.pem 
main() Started...
main() argv[1]: 65535
main() port: 65535
main() https mode
main() Error starting framework; frameworkRet: 4
main() End framework
main() Done.

I appreciate any idea to make this example code run in https mode.

Cheers!