Commit message: Fix fido2 signature validation bug
CVE: CVE-2021-40818
CWE: CWE-787
Vulnerability description: scheme/webauthn.c in Glewlwyd SSO server through 2.5.3 has a buffer overflow during FIDO2 signature validation in webauthn registration.

Project: glewlwyd
File: webauthn.c

static int validate_certificate_from_root(json_t * j_params, gnutls_x509_crt_t cert_leaf, cbor_item_t * x5c_array) {
  int ret = G_ERROR_NOT_FOUND, res;
  unsigned int result;
  gnutls_datum_t cert_dat = {NULL, 0}, issuer_dat = {NULL, 0};
  gnutls_x509_trust_list_t tlist = NULL;
  gnutls_x509_crt_t cert_x509[cbor_array_size(x5c_array)+1], root_x509 = NULL;
  json_t * j_cert = NULL;
  cbor_item_t * cbor_cert = NULL;
  size_t index = 0, i = 0, x5c_array_size = cbor_array_size(x5c_array);
  char * issuer;
  
  for (i=0; i<x5c_array_size+1; i++) {
    cert_x509[i] = NULL;
  }
  if ((res = gnutls_x509_crt_get_issuer_dn2(cert_leaf, &issuer_dat)) >= 0) {
    issuer = o_strndup((const char *)issuer_dat.data, issuer_dat.size);
    json_array_foreach(json_object_get(j_params, "root-ca-array"), index, j_cert) {
      if (0 == o_strcmp(issuer, json_string_value(json_object_get(j_cert, "dn")))) {
        cert_dat.data = (unsigned char *)json_string_value(json_object_get(j_cert, "x509"));
        cert_dat.size = json_string_length(json_object_get(j_cert, "x509"));
        if (!gnutls_x509_crt_init(&root_x509) && !gnutls_x509_crt_import(root_x509, &cert_dat, GNUTLS_X509_FMT_PEM)) {
          cert_x509[0] = cert_leaf;
          for (i=1; i<x5c_array_size; i++) {
            cbor_cert = cbor_array_get(x5c_array, i);
            cert_dat.data = cbor_bytestring_handle(cbor_cert);
            cert_dat.size = cbor_bytestring_length(cbor_cert);
            if (gnutls_x509_crt_init(&cert_x509[i]) < 0 || gnutls_x509_crt_import(cert_x509[i], &cert_dat, GNUTLS_X509_FMT_DER) < 0) {
              y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error import chain cert at index %zu", i);
              ret = G_ERROR;
            }
            cbor_decref(&cbor_cert);
          }
          cert_x509[x5c_array_size] = root_x509;
          ret = G_OK;
        } else {
          y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error import root cert");
          ret = G_ERROR;
        }
      }
    }
    o_free(issuer);
  } else {
    y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_crt_get_issuer_dn2: %d", res);
    ret = G_ERROR;
  }
  gnutls_free(issuer_dat.data);
  
  if (ret == G_OK) {
    if (!gnutls_x509_trust_list_init(&tlist, 0)) {
      if (gnutls_x509_trust_list_add_cas(tlist, &root_x509, 1, 0) >= 0) {
        if (gnutls_x509_trust_list_verify_crt(tlist, cert_x509, 2, 0, &result, NULL) >= 0) {
          if (result) {
            y_log_message(Y_LOG_LEVEL_DEBUG, "validate_certificate_from_root - certificate chain invalid");
            ret = G_ERROR;
          }
        } else {
          y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_verify_crt");
          ret = G_ERROR;
        }
      } else {
        y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_add_cas");
        ret = G_ERROR;
      }
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "validate_certificate_from_root - Error gnutls_x509_trust_list_init");
      ret = G_ERROR;
    }
  }
  gnutls_x509_crt_deinit(root_x509);
  for (i=1; i<x5c_array_size; i++) {
    gnutls_x509_crt_deinit(cert_x509[i]);
  }
  gnutls_x509_trust_list_deinit(tlist, 0);
  return ret;
}

Project: glewlwyd
File: webauthn.c

static json_t * check_attestation_android_safetynet(json_t * j_params, cbor_item_t * auth_data, cbor_item_t * att_stmt, const unsigned char * client_data) {
  json_t * j_error = json_array(), * j_return;
  unsigned char pubkey_export[1024] = {0}, cert_export[32] = {0}, cert_export_b64[64], client_data_hash[32], * nonce_base = NULL, nonce_base_hash[32], * nonce_base_hash_b64 = NULL, * header_cert_decoded = NULL;
  char * message = NULL, * response_token = NULL, issued_to[128] = {0}, * jwt_header = NULL;
  size_t pubkey_export_len = 1024, cert_export_len = 32, cert_export_b64_len, issued_to_len = 128, client_data_hash_len = 32, nonce_base_hash_len = 32, nonce_base_hash_b64_len = 0, header_cert_decoded_len = 0;
  gnutls_pubkey_t pubkey = NULL;
  gnutls_x509_crt_t cert = NULL;
  cbor_item_t * key, * response = NULL;
  int i, ret;
  jwt_t * j_response = NULL;
  json_t * j_header_x5c = NULL, * j_cert = NULL, * j_header = NULL, * j_value = NULL;
  gnutls_datum_t cert_dat;
  int has_ver = 0;
  
  if (j_error != NULL) {
    do {
      // Step 1
      if (!cbor_isa_map(att_stmt) || cbor_map_size(att_stmt) != 2) {
        json_array_append_new(j_error, json_string("CBOR map value 'attStmt' invalid format"));
        break;
      }
      for (i=0; i<2; i++) {
        key = cbor_map_handle(att_stmt)[i].key;
        if (cbor_isa_string(key)) {
          if (0 == o_strncmp((const char *)cbor_string_handle(key), "ver", MIN(o_strlen("ver"), cbor_string_length(key))) && cbor_isa_string(cbor_map_handle(att_stmt)[i].value)) {
            has_ver = 1;
          } else if (0 == o_strncmp((const char *)cbor_string_handle(key), "response", MIN(o_strlen("response"), cbor_string_length(key))) && cbor_isa_bytestring(cbor_map_handle(att_stmt)[i].value)) {
            response = cbor_map_handle(att_stmt)[i].value;
          } else {
            message = msprintf("attStmt map element %d key is not valid: '%.*s'", i, cbor_string_length(key), cbor_string_handle(key));
            json_array_append_new(j_error, json_string(message));
            o_free(message);
            break;
          }
        } else {
          message = msprintf("attStmt map element %d key is not a string", i);
          json_array_append_new(j_error, json_string(message));
          o_free(message);
          break;
        }
      }
      
      if (!has_ver) {
        json_array_append_new(j_error, json_string("version invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error ver missing");
        break;
      }

      if (!generate_digest_raw(digest_SHA256, client_data, o_strlen((char *)client_data), client_data_hash, &client_data_hash_len)) {
        json_array_append_new(j_error, json_string("Internal error"));
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error generate_digest_raw client_data");
        break;
      }
      
      if ((nonce_base = o_malloc(32 + cbor_bytestring_length(auth_data))) == NULL) {
        json_array_append_new(j_error, json_string("Internal error"));
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for nonce_base");
        break;
      }
      memcpy(nonce_base, cbor_bytestring_handle(auth_data), cbor_bytestring_length(auth_data));
      memcpy(nonce_base+cbor_bytestring_length(auth_data), client_data_hash, client_data_hash_len);
      
      if (!generate_digest_raw(digest_SHA256, nonce_base, 32 + cbor_bytestring_length(auth_data), nonce_base_hash, &nonce_base_hash_len)) {
        json_array_append_new(j_error, json_string("Internal error"));
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error generate_digest_raw nonce_base");
        break;
      }
      
      if ((nonce_base_hash_b64 = o_malloc(64)) == NULL) {
        json_array_append_new(j_error, json_string("Internal error"));
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for nonce_base_hash_b64");
        break;
      }

      if (!o_base64_encode(nonce_base_hash, 32, nonce_base_hash_b64, &nonce_base_hash_b64_len)) {
        json_array_append_new(j_error, json_string("Internal error"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error o_base64_encode for nonce_base_hash_b64");
        break;
      }
      
      if (response == NULL) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error response missing");
        break;
      }
      
      if ((response_token = o_strndup((const char *)cbor_bytestring_handle(response), cbor_bytestring_length(response))) == NULL) {
        json_array_append_new(j_error, json_string("Internal error"));
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error o_strndup for response_token");
        break;
      }
      
      if (r_jwt_init(&j_response) != RHN_OK) {
        json_array_append_new(j_error, json_string("Internal error"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error r_jwt_init");
        break;
      }
      
      if (r_jwt_parse(j_response, response_token, 0) != RHN_OK) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error r_jwt_parse");
        break;
      }
      
      if (o_strcmp(r_jwt_get_claim_str_value(j_response, "nonce"), (const char *)nonce_base_hash_b64)) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error nonce invalid");
        break;
      }
      
      if (json_integer_value(json_object_get(j_params, "ctsProfileMatch")) != -1 && json_integer_value(json_object_get(j_params, "ctsProfileMatch")) != ((j_value = r_jwt_get_claim_json_t_value(j_response, "ctsProfileMatch"))==json_true()?1:0)) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error ctsProfileMatch invalid");
        json_decref(j_value);
        j_value = NULL;
        break;
      }
      json_decref(j_value);
      j_value = NULL;
      
      if (json_integer_value(json_object_get(j_params, "basicIntegrity")) != -1 && json_integer_value(json_object_get(j_params, "basicIntegrity")) != ((j_value = r_jwt_get_claim_json_t_value(j_response, "basicIntegrity"))==json_true()?1:0)) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error basicIntegrity invalid");
        j_value = NULL;
        break;
      }
      json_decref(j_value);
      j_value = NULL;
      
      if (r_jwt_verify_signature(j_response, NULL, 0) != RHN_OK) {
        json_array_append_new(j_error, json_string("Invalid signature"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error r_jwt_verify_signature");
        break;
      }
      
      if ((j_header_x5c = r_jwt_get_header_json_t_value(j_response, "x5c")) == NULL) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error parsing x5c JSON");
        break;
      }
      
      if (!json_is_string((j_cert = json_array_get(j_header_x5c, 0)))) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error x5c leaf not a string");
        break;
      }
      
      if ((header_cert_decoded = o_malloc(json_string_length(j_cert))) == NULL) {
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for header_cert_decoded");
        break;
      }
      
      if (!o_base64_decode((const unsigned char *)json_string_value(j_cert), json_string_length(j_cert), header_cert_decoded, &header_cert_decoded_len)) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error o_base64_decode x5c leaf");
        break;
      }
      
      if (gnutls_x509_crt_init(&cert)) {
        json_array_append_new(j_error, json_string("internal error"));
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error gnutls_x509_crt_init");
        break;
      }
      if (gnutls_pubkey_init(&pubkey)) {
        json_array_append_new(j_error, json_string("internal error"));
        y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error gnutls_pubkey_init");
        break;
      }
      cert_dat.data = header_cert_decoded;
      cert_dat.size = header_cert_decoded_len;
      if ((ret = gnutls_x509_crt_import(cert, &cert_dat, GNUTLS_X509_FMT_DER)) < 0) {
        json_array_append_new(j_error, json_string("Error importing x509 certificate"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_pcert_import_x509_raw: %d", ret);
        break;
      }
      if ((ret = gnutls_pubkey_import_x509(pubkey, cert, 0)) < 0) {
        json_array_append_new(j_error, json_string("Error importing x509 certificate"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_pubkey_import_x509: %d", ret);
        break;
      }
      if ((ret = gnutls_x509_crt_get_key_id(cert, GNUTLS_KEYID_USE_SHA256, cert_export, &cert_export_len)) < 0) {
        json_array_append_new(j_error, json_string("Error exporting x509 certificate"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_x509_crt_get_key_id: %d", ret);
        break;
      }
      if ((ret = gnutls_x509_crt_get_dn(cert, issued_to, &issued_to_len)) < 0) {
        json_array_append_new(j_error, json_string("Error x509 dn"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_x509_crt_get_dn: %d", ret);
        break;
      }
      if (o_strnstr(issued_to, SAFETYNET_ISSUED_TO, issued_to_len) == NULL) {
        json_array_append_new(j_error, json_string("Error x509 dn"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - safetynet certificate issued for %.*s", issued_to_len, issued_to);
        break;
      }
      if (json_object_get(j_params, "google-root-ca-r2") != json_null()) {
        if ((ret = validate_safetynet_ca_root(j_params, cert, j_header_x5c)) == G_ERROR_UNAUTHORIZED) {
          json_array_append_new(j_error, json_string("Error x509 certificate chain validation"));
          break;
        } else if (ret != G_OK) {
          json_array_append_new(j_error, json_string("response invalid"));
          y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - safetynet certificate chain certificate validation error");
          break;
        }
      }
      if (!o_base64_encode(cert_export, cert_export_len, cert_export_b64, &cert_export_b64_len)) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error o_base64_encode cert_export");
        break;
      }
      if ((ret = gnutls_pubkey_export(pubkey, GNUTLS_X509_FMT_PEM, pubkey_export, &pubkey_export_len)) < 0) {
        json_array_append_new(j_error, json_string("response invalid"));
        y_log_message(Y_LOG_LEVEL_DEBUG, "check_attestation_android_safetynet - Error gnutls_pubkey_export: %d", ret);
        break;
      }
      
    } while (0);

    if (json_array_size(j_error)) {
      j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error);
    } else {
      j_return = json_pack("{sis{ss%}}", "result", G_OK, "data", "certificate", cert_export_b64, cert_export_b64_len);
    }
    json_decref(j_error);
    json_decref(j_header);
    json_decref(j_header_x5c);
    gnutls_pubkey_deinit(pubkey);
    gnutls_x509_crt_deinit(cert);
    r_jwt_free(j_response);
    o_free(nonce_base);
    o_free(nonce_base_hash_b64);
    o_free(response_token);
    o_free(header_cert_decoded);
    o_free(jwt_header);
  } else {
    y_log_message(Y_LOG_LEVEL_ERROR, "check_attestation_android_safetynet - Error allocating resources for j_error");
    j_return = json_pack("{si}", "result", G_ERROR);
  }
  return j_return;
}

Project: glewlwyd
File: webauthn.c

json_t * user_auth_scheme_module_init(struct config_module * config, json_t * j_parameters, const char * mod_name, void ** cls) {
  UNUSED(config);
  json_t * j_result = is_scheme_parameters_valid(j_parameters), * j_element = NULL, * j_return;
  size_t index = 0;
  char * message;
  
  if (check_result_value(j_result, G_OK)) {
    *cls = json_pack("{sO sO sO sO sI sI sO ss so sO sO sO sO sO ss s[]}",
                     "challenge-length", json_object_get(j_parameters, "challenge-length"),
                     "rp-origin", json_object_get(j_parameters, "rp-origin"),
                     "credential-expiration", json_object_get(j_parameters, "credential-expiration"),
                     "credential-assertion", json_object_get(j_parameters, "credential-assertion"),
                     "ctsProfileMatch", json_object_get(j_parameters, "ctsProfileMatch")!=NULL?json_integer_value(json_object_get(j_parameters, "ctsProfileMatch")):-1,
                     "basicIntegrity", json_object_get(j_parameters, "basicIntegrity")!=NULL?json_integer_value(json_object_get(j_parameters, "basicIntegrity")):-1,
                     "session-mandatory", json_object_get(j_parameters, "session-mandatory")!=NULL?json_object_get(j_parameters, "session-mandatory"):json_true(),
                     "seed", !json_string_length(json_object_get(j_parameters, "seed"))?"":json_string_value(json_object_get(j_parameters, "seed")),
                     "fmt", json_object_get(j_parameters, "fmt")!=NULL?json_deep_copy(json_object_get(j_parameters, "fmt")):json_pack("{sosososososo}", "packed", json_true(), "tpm", json_true(), "android-key", json_true(), "android-safetynet", json_true(), "fido-u2f", json_true(), "none", json_true()),
                     "force-fmt-none", json_object_get(j_parameters, "force-fmt-none")!=NULL?json_object_get(j_parameters, "force-fmt-none"):json_false(),
                     "google-root-ca-r2", json_string_length(json_object_get(j_parameters, "google-root-ca-r2"))?json_object_get(j_parameters, "google-root-ca-r2"):json_null(),
                     "google-root-ca-r2-content", json_object_get(j_parameters, "google-root-ca-r2-content")!=NULL?json_object_get(j_parameters, "google-root-ca-r2-content"):json_null(),
                     "root-ca-list", json_array_size(json_object_get(j_parameters, "root-ca-list"))?json_object_get(j_parameters, "root-ca-list"):json_null(),
                     "root-ca-array", json_object_get(j_parameters, "root-ca-array")!=NULL?json_object_get(j_parameters, "root-ca-array"):json_null(),
                     "mod_name", mod_name,
                     "pubKey-cred-params");
    json_array_foreach(json_object_get(j_parameters, "pubKey-cred-params"), index, j_element) {
      json_array_append_new(json_object_get((json_t *)*cls, "pubKey-cred-params"), json_pack("{sssO}", "type", "public-key", "alg", j_element));
    }
    j_return = json_pack("{si}", "result", G_OK);
  } else if (check_result_value(j_result, G_ERROR_PARAM)) {
    message = json_dumps(json_object_get(j_result, "error"), JSON_COMPACT);
    y_log_message(Y_LOG_LEVEL_ERROR, "user_auth_scheme_module_init webauthn - Error input parameters: %s", message);
    o_free(message);
    j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", json_object_get(j_result, "error"));
  } else {
    j_return = json_pack("{si}", "result", G_ERROR);
  }
  json_decref(j_result);
  return j_return;
}

Project: glewlwyd
File: webauthn.c

static json_t * is_scheme_parameters_valid(json_t * j_params) {
  json_t * j_return, * j_error, * j_element = NULL, * j_cert;
  size_t index = 0;
  json_int_t pubkey;
  char * message;
  
  if (json_is_object(j_params)) {
    j_error = json_array();
    if (j_error != NULL) {
      if (!json_is_boolean(json_object_get(j_params, "session-mandatory"))) {
        json_array_append_new(j_error, json_string("session-mandatory is mandatory and must be a boolean"));
      }
      if (json_object_get(j_params, "seed") != NULL && !json_is_string(json_object_get(j_params, "seed"))) {
        json_array_append_new(j_error, json_string("seed is optional and must be a string"));
      }
      if (json_integer_value(json_object_get(j_params, "challenge-length")) <= 0) {
        json_array_append_new(j_error, json_string("challenge-length is mandatory and must be a positive integer"));
      }
      if (json_integer_value(json_object_get(j_params, "credential-expiration")) <= 0) {
        json_array_append_new(j_error, json_string("credential-expiration is mandatory and must be a positive integer"));
      }
      if (json_integer_value(json_object_get(j_params, "credential-assertion")) <= 0) {
        json_array_append_new(j_error, json_string("credential-assertion is mandatory and must be a positive integer"));
      }
      if (!json_string_length(json_object_get(j_params, "rp-origin"))) {
        json_array_append_new(j_error, json_string("rp-origin is mandatory and must be a non empty string"));
      }
      if (!json_array_size(json_object_get(j_params, "pubKey-cred-params"))) {
        json_array_append_new(j_error, json_string("pubKey-cred-params is mandatory and must be a non empty JSON array"));
      } else {
        json_array_foreach(json_object_get(j_params, "pubKey-cred-params"), index, j_element) {
          pubkey = json_integer_value(j_element);
          //if (pubkey != -7 && pubkey != -35 && pubkey != -36 && pubkey != -257 && pubkey != -258 && pubkey != -259) {
          if (pubkey != ECDSA256 && pubkey != ECDSA384 && pubkey != ECDSA512) {
            //json_array_append_new(j_error, json_string("pubKey-cred-params elements values available are -7, -35, -36 (ECDSA) or -257, -258, -259 (RSA)"));
            json_array_append_new(j_error, json_string("pubKey-cred-params elements values available are -7, -35, -36 (ECDSA)"));
          }
        }
      }
      if (json_object_get(j_params, "ctsProfileMatch") != NULL && (!json_is_integer(json_object_get(j_params, "ctsProfileMatch")) || json_integer_value(json_object_get(j_params, "ctsProfileMatch")) < -1 || json_integer_value(json_object_get(j_params, "ctsProfileMatch")) > 1)) {
        json_array_append_new(j_error, json_string("ctsProfileMatch is optional and must be an integer between -1 and 1"));
      }
      if (json_object_get(j_params, "basicIntegrity") != NULL && (!json_is_integer(json_object_get(j_params, "basicIntegrity")) || json_integer_value(json_object_get(j_params, "basicIntegrity")) < -1 || json_integer_value(json_object_get(j_params, "basicIntegrity")) > 1)) {
        json_array_append_new(j_error, json_string("basicIntegrity is optional and must be an integer between -1 and 1"));
      }
      if (json_object_get(j_params, "google-root-ca-r2") != NULL && !json_is_string(json_object_get(j_params, "google-root-ca-r2"))) {
        json_array_append_new(j_error, json_string("google-root-ca-r2 is optional and must be a string"));
      } else if (json_string_length(json_object_get(j_params, "google-root-ca-r2"))) {
        j_cert = get_cert_from_file_path(json_string_value(json_object_get(j_params, "google-root-ca-r2")));
        if (check_result_value(j_cert, G_OK)) {
          json_object_set(j_params, "google-root-ca-r2-content", json_object_get(j_cert, "certificate"));
        } else {
          message = msprintf("Error parsing google-root-ca-r2 certificate file %s", json_string_value(json_object_get(j_params, "google-root-ca-r2")));
          json_array_append_new(j_error, json_string(message));
          o_free(message);
        }
        json_decref(j_cert);
      }
      if (json_object_get(j_params, "root-ca-list") != NULL) {
        if (!json_is_array(json_object_get(j_params, "root-ca-list"))) {
          json_array_append_new(j_error, json_string("root-ca-list is optional and must be an array of strings"));
        } else {
          json_object_set_new(j_params, "root-ca-array", json_array());
          json_array_foreach(json_object_get(j_params, "root-ca-list"), index, j_element) {
            if (!json_string_length(j_element)) {
              json_array_append_new(j_error, json_string("root-ca-list is optional and must be an array of strings"));
            } else {
              j_cert = get_cert_from_file_path(json_string_value(j_element));
              if (check_result_value(j_cert, G_OK)) {
                json_array_append(json_object_get(j_params, "root-ca-array"), json_object_get(j_cert, "certificate"));
              } else {
                message = msprintf("Error parsing certificate file %s", json_string_value(j_element));
                json_array_append_new(j_error, json_string(message));
                o_free(message);
              }
              json_decref(j_cert);
            }
          }
        }
      }
      if (json_object_get(j_params, "force-fmt-none") != NULL && !json_is_boolean(json_object_get(j_params, "force-fmt-none"))) {
        json_array_append_new(j_error, json_string("allow-fmt-none is optional and must be a boolean"));
      }
      if (json_object_get(j_params, "fmt") != NULL && (!json_is_object(json_object_get(j_params, "fmt")) || (json_object_get(json_object_get(j_params, "fmt"), "packed") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "tpm") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "android-key") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "android-safetynet") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "fido-u2f") != json_true() && json_object_get(json_object_get(j_params, "fmt"), "none") != json_true()))) {
        json_array_append_new(j_error, json_string("fmt must be a JSON object filled with supported formats: 'packed' 'tpm', 'android-key', 'android-safetynet', 'fido-u2f', 'none'"));
      }
      if (json_array_size(j_error)) {
        j_return = json_pack("{sisO}", "result", G_ERROR_PARAM, "error", j_error);
      } else {
        j_return = json_pack("{si}", "result", G_OK);
      }
      json_decref(j_error);
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "is_scheme_parameters_valid - Error allocating resources for j_error");
      j_return = json_pack("{si}", "result", G_ERROR);
    }
  } else {
    j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "parameters must be a JSON object");
  }
  return j_return;
}

Project: glewlwyd
File: webauthn.c

static json_t * register_new_attestation(struct config_module * config, json_t * j_params, json_t * j_scheme_data, json_t * j_credential) {
  json_t * j_return, * j_client_data = NULL, * j_error, * j_result, * j_pubkey = NULL, * j_cert = NULL, * j_query, * j_element = NULL;
  unsigned char * client_data = NULL, * challenge_b64 = NULL, * att_obj = NULL, * cbor_bs_handle = NULL, rpid_hash[32], * fmt = NULL, * credential_id_b64 = NULL, * cbor_auth_data, * cred_pub_key, cert_x[256], cert_y[256], pubkey_export[1024];
  char * challenge_hash = NULL, * message = NULL;
  const char * rpid = NULL;
  size_t client_data_len = 0, challenge_b64_len = 0, att_obj_len = 0, rpid_hash_len = 32, fmt_len = 0, credential_id_len = 0, credential_id_b64_len, cbor_auth_data_len, cred_pub_key_len, cert_x_len = 0, cert_y_len = 0, pubkey_export_len = 1024, index = 0, cbor_bs_handle_len, rpid_len;
  uint32_t counter = 0;
  int ret = G_OK, res, status, has_x = 0, has_y = 0, key_type_valid = 0, key_alg_valid = 0;
  unsigned int i;
  struct cbor_load_result cbor_result;
  cbor_item_t * item = NULL, * key = NULL, * auth_data = NULL, * att_stmt = NULL, * cbor_cose = NULL, * cbor_key, * cbor_value;
  gnutls_pubkey_t g_key = NULL;
  gnutls_datum_t g_x, g_y;
  gnutls_ecc_curve_t curve = GNUTLS_ECC_CURVE_INVALID;
  
  if (j_scheme_data != NULL) {
    j_error = json_array();
    if (j_error != NULL) {
      do {
        if (!json_string_length(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId"))) {
          json_array_append_new(j_error, json_string("rawId mandatory"));
          ret = G_ERROR_PARAM;
          break;
        }
        if (!json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON"))) {
          json_array_append_new(j_error, json_string("clientDataJSON mandatory"));
          ret = G_ERROR_PARAM;
          break;
        }
        if ((client_data = o_malloc(json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON"))+1)) == NULL) {
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for client_data");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR_MEMORY;
          break;
        }
        if (!o_base64_decode((const unsigned char *)json_string_value(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON")), json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "clientDataJSON")), client_data, &client_data_len)) {
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error o_base64_decode client_data");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR_PARAM;
          break;
        }
        client_data[client_data_len] = '\0';
        j_client_data = json_loads((const char *)client_data, JSON_DECODE_ANY, NULL);
        if (j_client_data == NULL) {
          json_array_append_new(j_error, json_string("Error parsing JSON client data"));
          ret = G_ERROR_PARAM;
          break;
        }
        // Step 3
        if (0 != o_strcmp(json_string_value(json_object_get(j_client_data, "type")), "webauthn.create")) {
          json_array_append_new(j_error, json_string("clientDataJSON.type invalid"));
          ret = G_ERROR_PARAM;
          break;
        }
        // Step 4
        if (!json_string_length(json_object_get(j_client_data, "challenge"))) {
          json_array_append_new(j_error, json_string("clientDataJSON.challenge mandatory"));
          ret = G_ERROR_PARAM;
          break;
        }
        if ((challenge_b64 = o_malloc(json_string_length(json_object_get(j_client_data, "challenge"))+3)) == NULL) {
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for challenge_b64");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR_MEMORY;
          break;
        }
        if (!o_base64url_2_base64((unsigned char *)json_string_value(json_object_get(j_client_data, "challenge")), json_string_length(json_object_get(j_client_data, "challenge")), challenge_b64, &challenge_b64_len)) {
          json_array_append_new(j_error, json_string("clientDataJSON.challenge invalid format"));
          ret = G_ERROR_PARAM;
          break;
        }
        challenge_b64[challenge_b64_len] = '\0';
        if ((challenge_hash = generate_hash(config->hash_algorithm, (const char *)challenge_b64)) == NULL) {
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error generate_hash for challenge_b64");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR;
          break;
        }
        if (0 != o_strcmp(challenge_hash, json_string_value(json_object_get(j_credential, "challenge_hash")))) {
          json_array_append_new(j_error, json_string("clientDataJSON.challenge invalid"));
          ret = G_ERROR_PARAM;
          break;
        }
        // Step 5
        if (!json_string_length(json_object_get(j_client_data, "origin"))) {
          json_array_append_new(j_error, json_string("clientDataJSON.origin mandatory"));
          ret = G_ERROR_PARAM;
          break;
        }
        if (0 != o_strcmp(json_string_value(json_object_get(j_params, "rp-origin")), json_string_value(json_object_get(j_client_data, "origin")))) {
          message = msprintf("clientDataJSON.origin invalid - Client send %s, required %s", json_string_value(json_object_get(j_client_data, "origin")), json_string_value(json_object_get(j_params, "rp-origin")));
          json_array_append_new(j_error, json_string(message));
          o_free(message);
          ret = G_ERROR_PARAM;
          break;
        }
        // Step 6 ??
        
        if (!json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject"))) {
          json_array_append_new(j_error, json_string("attestationObject required"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        if ((att_obj = o_malloc(json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject")))) == NULL) {
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for o_malloc");
          ret = G_ERROR_MEMORY;
          break;
        }
        if (!o_base64_decode((unsigned char *)json_string_value(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject")), json_string_length(json_object_get(json_object_get(json_object_get(j_scheme_data, "credential"), "response"), "attestationObject")), att_obj, &att_obj_len)) {
          json_array_append_new(j_error, json_string("attestationObject invalid base64"));
          ret = G_ERROR_PARAM;
          break;
        }

        // Step 7
        item = cbor_load(att_obj, att_obj_len, &cbor_result);
        if (cbor_result.error.code != CBOR_ERR_NONE) {
          json_array_append_new(j_error, json_string("attestationObject invalid cbor"));
          ret = G_ERROR_PARAM;
          break;
        }
        if (!cbor_isa_map(item)) {
          json_array_append_new(j_error, json_string("attestationObject invalid cbor item"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        // Check attestation object
        if (cbor_map_size(item) != 3) {
          json_array_append_new(j_error, json_string("attestationObject invalid cbor item"));
          ret = G_ERROR_PARAM;
          break;
        }

        for (i=0; i<3; i++) {
          key = cbor_map_handle(item)[i].key;
          if (cbor_isa_string(key)) {
            if (0 == o_strncmp((const char *)cbor_string_handle(key), "fmt", MIN(o_strlen("fmt"), cbor_string_length(key)))) {
              if (!cbor_isa_string(cbor_map_handle(item)[i].value)) {
                json_array_append_new(j_error, json_string("CBOR map value 'fmt' isnt't a string"));
                ret = G_ERROR_PARAM;
                break;
              } else {
                fmt_len = cbor_string_length(cbor_map_handle(item)[i].value);
                fmt = cbor_string_handle(cbor_map_handle(item)[i].value);
              }
            } else if (0 == o_strncmp((const char *)cbor_string_handle(key), "attStmt", MIN(o_strlen("attStmt"), cbor_string_length(key)))) {
              att_stmt = cbor_map_handle(item)[i].value;
            } else if (0 == o_strncmp((const char *)cbor_string_handle(key), "authData", MIN(o_strlen("authData"), cbor_string_length(key)))) {
              auth_data = cbor_map_handle(item)[i].value;
              if (!cbor_isa_bytestring(auth_data) || cbor_bytestring_length(auth_data) < 56 || cbor_bytestring_is_indefinite(auth_data)) {
                json_array_append_new(j_error, json_string("CBOR map value 'authData' is invalid"));
                ret = G_ERROR_PARAM;
                break;
              }
            } else {
              message = msprintf("CBOR map element %d is not an expected item", i);
              json_array_append_new(j_error, json_string(message));
              o_free(message);
              ret = G_ERROR_PARAM;
              break;
            }
          }
        }
        
        // Step 9
        if (auth_data == NULL) {
          json_array_append_new(j_error, json_string("authData invalid"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        cbor_bs_handle = cbor_bytestring_handle(auth_data);
        cbor_bs_handle_len = cbor_bytestring_length(auth_data);
        if (o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://") == NULL) {
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - rp-origin invalid");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        if (o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://") != NULL) {
          rpid = o_strstr(json_string_value(json_object_get(j_params, "rp-origin")), "://")+3;
        } else {
          rpid = json_string_value(json_object_get(j_params, "rp-origin"));
        }
        if (o_strchr(rpid, ':') != NULL) {
          rpid_len = o_strchr(rpid, ':') - rpid;
        } else {
          rpid_len = o_strlen(rpid);
        }
        
        if (!generate_digest_raw(digest_SHA256, (unsigned char *)rpid, rpid_len, rpid_hash, &rpid_hash_len)) {
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error generate_digest_raw");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        if (0 != memcmp(cbor_bs_handle, rpid_hash, rpid_hash_len)) {
          json_array_append_new(j_error, json_string("authData.rpIdHash invalid"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        // Step 10
        if (!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_USER_PRESENT)) {
          json_array_append_new(j_error, json_string("authData.userPresent not set"));
          ret = G_ERROR_PARAM;
          break;
        }
        if (!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_AT)) {
          json_array_append_new(j_error, json_string("authData.Attested credential data not set"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        // Step 11 ignored for now
        //y_log_message(Y_LOG_LEVEL_DEBUG, "authData.userVerified: %d", !!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_USER_VERIFY));
        
        // Step 12 ignored for now (no extension)
        //y_log_message(Y_LOG_LEVEL_DEBUG, "authData.Extension: %d", !!(cbor_bs_handle[FLAGS_OFFSET] & FLAG_ED));
        
        credential_id_len = cbor_bs_handle[CRED_ID_L_OFFSET+1] | (cbor_bs_handle[CRED_ID_L_OFFSET] << 8);
        if (cbor_bs_handle_len < CRED_ID_L_OFFSET+2+credential_id_len) {
          json_array_append_new(j_error, json_string("auth_data invalid size"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        credential_id_b64 = o_malloc(credential_id_len*2);
        if (credential_id_b64 == NULL) {
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error o_malloc for credential_id_b64");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        if (!o_base64_encode(cbor_bs_handle+CRED_ID_L_OFFSET+2, credential_id_len, credential_id_b64, &credential_id_b64_len)) {
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error o_base64_encode for credential_id_b64");
          json_array_append_new(j_error, json_string("Internal error"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        // Compare credential_id_b64 with rawId
        if (memcmp(credential_id_b64, json_string_value(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId")), MIN(json_string_length(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId")), credential_id_b64_len))) {
          json_array_append_new(j_error, json_string("Invalid rawId"));
          ret = G_ERROR_PARAM;
          break;
        }
        
        // Extract public key from auth_data COSE structure

        // Extract credential ID
        cbor_auth_data_len = cbor_bytestring_length(auth_data);
        cbor_auth_data = cbor_bytestring_handle(auth_data);
        
        cred_pub_key = cbor_auth_data+CREDENTIAL_ID_OFFSET+credential_id_len;
        cred_pub_key_len = cbor_auth_data_len-CREDENTIAL_ID_OFFSET-credential_id_len;
        cbor_cose = cbor_load(cred_pub_key, cred_pub_key_len, &cbor_result);
        if (cbor_result.error.code != CBOR_ERR_NONE) {
          json_array_append_new(j_error, json_string("Invalid COSE key"));
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error cbor_load cbor_cose");
          ret = G_ERROR_PARAM;
          break;
        }
        
        if (!cbor_isa_map(cbor_cose)) {
          json_array_append_new(j_error, json_string("Invalid COSE key"));
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error cbor_cose not a map");
          ret = G_ERROR_PARAM;
          break;
        }
        
        for (i=0; i<cbor_map_size(cbor_cose); i++) {
          cbor_key = cbor_map_handle(cbor_cose)[i].key;
          cbor_value = cbor_map_handle(cbor_cose)[i].value;
          if (cbor_isa_negint(cbor_key) && cbor_get_int(cbor_key) == 1 && cbor_isa_bytestring(cbor_value)) {
            has_x = 1;
            memcpy(cert_x, cbor_bytestring_handle(cbor_value), cbor_bytestring_length(cbor_value));
            cert_x_len = cbor_bytestring_length(cbor_value);
            g_x.data = cert_x;
            g_x.size = cbor_bytestring_length(cbor_value);
          } else if (cbor_isa_negint(cbor_key) && cbor_get_int(cbor_key) == 2 && cbor_isa_bytestring(cbor_value)) {
            has_y = 1;
            memcpy(cert_y, cbor_bytestring_handle(cbor_value), cbor_bytestring_length(cbor_value));
            cert_y_len = cbor_bytestring_length(cbor_value);
            g_y.data = cert_y;
            g_y.size = cbor_bytestring_length(cbor_value);
          } else if (cbor_isa_uint(cbor_key) && cbor_get_int(cbor_key) == 1 && cbor_isa_uint(cbor_value) && cbor_get_int(cbor_value) == 2) {
            key_type_valid = 1;
          } else if (cbor_isa_uint(cbor_key) && cbor_get_int(cbor_key) == 3 && cbor_isa_negint(cbor_value)) {
            if (cbor_get_int(cbor_value) == 6 || cbor_get_int(cbor_value) == 34 || cbor_get_int(cbor_value) == 35) {
              json_array_foreach(json_object_get(j_params, "pubKey-cred-params"), index, j_element) {
                if (cbor_get_int(cbor_value) == 6 && json_integer_value(json_object_get(j_element, "alg")) == ECDSA256) {
                  key_alg_valid = 1;
                  curve = GNUTLS_ECC_CURVE_SECP256R1;
                } else if (cbor_get_int(cbor_value) == 34 && json_integer_value(json_object_get(j_element, "alg")) == ECDSA384) {
                  key_alg_valid = 1;
                  curve = GNUTLS_ECC_CURVE_SECP384R1;
                } else if (cbor_get_int(cbor_value) == 35 && json_integer_value(json_object_get(j_element, "alg")) == ECDSA512) {
                  key_alg_valid = 1;
                  curve = GNUTLS_ECC_CURVE_SECP521R1;
                }
              }
            }
          }
        }
        
        if (!has_x || !has_y || !key_type_valid || !key_alg_valid) {
          json_array_append_new(j_error, json_string("Invalid COSE key"));
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error invalid COSE key has_x %d && has_y %d && key_type_valid %d && key_alg_valid %d", has_x, has_y, key_type_valid, key_alg_valid);
          ret = G_ERROR_PARAM;
          break;
        }
        
        if (gnutls_pubkey_init(&g_key)) {
          json_array_append_new(j_error, json_string("Internal error"));
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error gnutls_pubkey_init");
          ret = G_ERROR_PARAM;
          break;
        }
        if (gnutls_pubkey_import_ecc_raw(g_key, curve, &g_x, &g_y) < 0) {
          json_array_append_new(j_error, json_string("Internal error"));
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - error gnutls_pubkey_import_ecc_raw");
          ret = G_ERROR_PARAM;
          break;
        }
        if ((ret = gnutls_pubkey_export(g_key, GNUTLS_X509_FMT_PEM, pubkey_export, &pubkey_export_len)) < 0) {
          json_array_append_new(j_error, json_string("Error exporting pubkey"));
          y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - Error gnutls_pubkey_export: %d", ret);
          ret = G_ERROR_PARAM;
          break;
        }
        
        // Steps 13-14
        if (0 == o_strncmp("packed", (char *)fmt, MIN(fmt_len, o_strlen("packed"))) && (json_object_get(json_object_get(j_params, "fmt"), "packed") == json_true())) {
          j_result = check_attestation_packed(j_params, auth_data, att_stmt, client_data, g_key);
          if (check_result_value(j_result, G_ERROR_PARAM)) {
            json_array_extend(j_error, json_object_get(j_result, "error"));
            ret = G_ERROR_PARAM;
          } else if (!check_result_value(j_result, G_OK)) {
            ret = G_ERROR_PARAM;
            y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error check_attestation_packed");
            json_array_append_new(j_error, json_string("internal error"));
          } else {
            j_cert = json_incref(json_object_get(json_object_get(j_result, "data"), "certificate"));
          }
          json_decref(j_result);
        } else if (0 == o_strncmp("tpm", (char *)fmt, MIN(fmt_len, o_strlen("tpm"))) && (json_object_get(json_object_get(j_params, "fmt"), "tpm") == json_true())) {
          json_array_append_new(j_error, json_string("Format 'tpm' not supported yet"));
          ret = G_ERROR_PARAM;
        } else if (0 == o_strncmp("android-key", (char *)fmt, MIN(fmt_len, o_strlen("android-key"))) && (json_object_get(json_object_get(j_params, "fmt"), "android-key") == json_true())) {
          json_array_append_new(j_error, json_string("Format 'android-key' not supported yet"));
          ret = G_ERROR_PARAM;
        } else if (0 == o_strncmp("android-safetynet", (char *)fmt, MIN(fmt_len, o_strlen("android-safetynet"))) && (json_object_get(json_object_get(j_params, "fmt"), "android-safetynet") == json_true())) {
          j_result = check_attestation_android_safetynet(j_params, auth_data, att_stmt, client_data);
          if (check_result_value(j_result, G_ERROR_PARAM)) {
            json_array_extend(j_error, json_object_get(j_result, "error"));
            ret = G_ERROR_PARAM;
          } else if (!check_result_value(j_result, G_OK)) {
            ret = G_ERROR_PARAM;
            y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error check_attestation_android_safetynet");
            json_array_append_new(j_error, json_string("internal error"));
          } else {
            j_cert = json_incref(json_object_get(json_object_get(j_result, "data"), "certificate"));
          }
          json_decref(j_result);
        } else if (0 == o_strncmp("fido-u2f", (char *)fmt, MIN(fmt_len, o_strlen("fido-u2f"))) && (json_object_get(json_object_get(j_params, "fmt"), "fido-u2f") == json_true())) {
          j_result = check_attestation_fido_u2f(j_params, (cbor_auth_data+CREDENTIAL_ID_OFFSET), credential_id_len, cert_x, cert_x_len, cert_y, cert_y_len, att_stmt, rpid_hash, rpid_hash_len, client_data);
          if (check_result_value(j_result, G_ERROR_PARAM)) {
            json_array_extend(j_error, json_object_get(j_result, "error"));
            ret = G_ERROR_PARAM;
          } else if (!check_result_value(j_result, G_OK)) {
            ret = G_ERROR_PARAM;
            y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error check_attestation_fido_u2f");
            json_array_append_new(j_error, json_string("internal error"));
          } else {
            j_cert = json_incref(json_object_get(json_object_get(j_result, "data"), "certificate"));
          }
          json_decref(j_result);
        } else if (0 == o_strncmp("none", (char *)fmt, MIN(fmt_len, o_strlen("none"))) && (json_object_get(json_object_get(j_params, "fmt"), "none") == json_true() || json_object_get(j_params, "force-fmt-none") == json_true())) {
          if (att_stmt != NULL && cbor_isa_map(att_stmt) && cbor_map_is_definite(att_stmt) && !cbor_map_size(att_stmt)) {
            j_cert = json_string("");
          } else {
            y_log_message(Y_LOG_LEVEL_DEBUG, "register_new_attestation - response type 'none' has invalid format");
            json_array_append_new(j_error, json_string("response invalid"));
            ret = G_ERROR_PARAM;
          }
        } else {
          message = msprintf("Format '%.*s' is not supported by Glewlwyd WebAuthn scheme", fmt_len, fmt);
          json_array_append_new(j_error, json_string(message));
          o_free(message);
          ret = G_ERROR_PARAM;
        }
      } while (0); // This is not a loop, but a structure where you can easily cancel the rest of the process with breaks
      
      if (ret != G_OK) {
        if (json_array_size(j_error)) {
          j_return = json_pack("{sisO}", "result", ret, "error", j_error);
        } else {
          j_return = json_pack("{si}", "result", ret);
        }
      } else {
        if ((res = check_certificate(config, j_params, json_string_value(json_object_get(json_object_get(j_scheme_data, "credential"), "rawId")), json_integer_value(json_object_get(j_credential, "gswu_id")))) == G_OK) {
          j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Credential already registered");
          status = 2;
        } else if (res == G_ERROR_UNAUTHORIZED) {
          j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Credential unauthorized");
          status = 2;
        } else if (res != G_ERROR_NOT_FOUND) {
          j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "Internal error");
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error check_certificate");
          status = 2;
        } else {
          j_return = json_pack("{si}", "result", G_OK);
          status = 1;
        }
        counter = cbor_bs_handle[COUNTER_OFFSET+3] | (cbor_bs_handle[COUNTER_OFFSET+2] << 8) | (cbor_bs_handle[COUNTER_OFFSET+1] << 16) | (cbor_bs_handle[COUNTER_OFFSET] << 24);
        // Store credential in the database
        j_query = json_pack("{sss{siss%sOss%sOsi}s{sO}}",
                            "table", 
                            G_TABLE_WEBAUTHN_CREDENTIAL,
                            "set",
                              "gswc_status",
                              status,
                              "gswc_name",
                              fmt,
                              fmt_len,
                              "gswc_credential_id",
                              json_object_get(json_object_get(j_scheme_data, "credential"), "rawId"),
                              "gswc_public_key",
                              pubkey_export,
                              pubkey_export_len,
                              "gswc_certificate",
                              j_cert,
                              "gswc_counter",
                              counter,
                            "where",
                              "gswc_id",
                              json_object_get(j_credential, "gswc_id"));
        res = h_update(config->conn, j_query, NULL);
        json_decref(j_query);
        if (res != H_OK) {
          y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error h_update");
        }
      }
      json_decref(j_error);
      json_decref(j_client_data);
      json_decref(j_pubkey);
      json_decref(j_cert);
      o_free(client_data);
      o_free(challenge_b64);
      o_free(challenge_hash);
      o_free(att_obj);
      o_free(credential_id_b64);
      gnutls_pubkey_deinit(g_key);
      if (item != NULL) {
        cbor_decref(&item);
      }
      if (cbor_cose != NULL) {
        cbor_decref(&cbor_cose);
      }
    } else {
      y_log_message(Y_LOG_LEVEL_ERROR, "register_new_attestation - Error allocating resources for j_error");
      j_return = json_pack("{si}", "result", G_ERROR);
    }
  } else {
    j_return = json_pack("{sis[s]}", "result", G_ERROR_PARAM, "error", "scheme_data mandatory");
  }
  return j_return;
}