CURLcode Curl_output_aws_sigv4()

in libs/curl/lib/http_aws_sigv4.c [551:847]


CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
{
  CURLcode result = CURLE_OUT_OF_MEMORY;
  struct connectdata *conn = data->conn;
  size_t len;
  const char *arg;
  char provider0[MAX_SIGV4_LEN + 1]="";
  char provider1[MAX_SIGV4_LEN + 1]="";
  char region[MAX_SIGV4_LEN + 1]="";
  char service[MAX_SIGV4_LEN + 1]="";
  bool sign_as_s3 = false;
  const char *hostname = conn->host.name;
  time_t clock;
  struct tm tm;
  char timestamp[TIMESTAMP_SIZE];
  char date[9];
  struct dynbuf canonical_headers;
  struct dynbuf signed_headers;
  struct dynbuf canonical_query;
  struct dynbuf canonical_path;
  char *date_header = NULL;
  Curl_HttpReq httpreq;
  const char *method = NULL;
  char *payload_hash = NULL;
  size_t payload_hash_len = 0;
  unsigned char sha_hash[SHA256_DIGEST_LENGTH];
  char sha_hex[SHA256_HEX_LENGTH];
  char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
  char *canonical_request = NULL;
  char *request_type = NULL;
  char *credential_scope = NULL;
  char *str_to_sign = NULL;
  const char *user = data->state.aptr.user ? data->state.aptr.user : "";
  char *secret = NULL;
  unsigned char sign0[SHA256_DIGEST_LENGTH] = {0};
  unsigned char sign1[SHA256_DIGEST_LENGTH] = {0};
  char *auth_headers = NULL;

  DEBUGASSERT(!proxy);
  (void)proxy;

  if(Curl_checkheaders(data, STRCONST("Authorization"))) {
    /* Authorization already present, Bailing out */
    return CURLE_OK;
  }

  /* we init those buffers here, so goto fail will free initialized dynbuf */
  Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
  Curl_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
  Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
  Curl_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER);

  /*
   * Parameters parsing
   * Google and Outscale use the same OSC or GOOG,
   * but Amazon uses AWS and AMZ for header arguments.
   * AWS is the default because most of non-amazon providers
   * are still using aws:amz as a prefix.
   */
  arg = data->set.str[STRING_AWS_SIGV4] ?
    data->set.str[STRING_AWS_SIGV4] : "aws:amz";

  /* provider1[:provider2[:region[:service]]]

     No string can be longer than N bytes of non-whitespace
  */
  (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]"
               ":%" MAX_SIGV4_LEN_TXT "[^:]"
               ":%" MAX_SIGV4_LEN_TXT "[^:]"
               ":%" MAX_SIGV4_LEN_TXT "s",
               provider0, provider1, region, service);
  if(!provider0[0]) {
    failf(data, "first aws-sigv4 provider cannot be empty");
    result = CURLE_BAD_FUNCTION_ARGUMENT;
    goto fail;
  }
  else if(!provider1[0])
    strcpy(provider1, provider0);

  if(!service[0]) {
    char *hostdot = strchr(hostname, '.');
    if(!hostdot) {
      failf(data, "aws-sigv4: service missing in parameters and hostname");
      result = CURLE_URL_MALFORMAT;
      goto fail;
    }
    len = hostdot - hostname;
    if(len > MAX_SIGV4_LEN) {
      failf(data, "aws-sigv4: service too long in hostname");
      result = CURLE_URL_MALFORMAT;
      goto fail;
    }
    memcpy(service, hostname, len);
    service[len] = '\0';

    infof(data, "aws_sigv4: picked service %s from host", service);

    if(!region[0]) {
      const char *reg = hostdot + 1;
      const char *hostreg = strchr(reg, '.');
      if(!hostreg) {
        failf(data, "aws-sigv4: region missing in parameters and hostname");
        result = CURLE_URL_MALFORMAT;
        goto fail;
      }
      len = hostreg - reg;
      if(len > MAX_SIGV4_LEN) {
        failf(data, "aws-sigv4: region too long in hostname");
        result = CURLE_URL_MALFORMAT;
        goto fail;
      }
      memcpy(region, reg, len);
      region[len] = '\0';
      infof(data, "aws_sigv4: picked region %s from host", region);
    }
  }

  Curl_http_method(data, conn, &method, &httpreq);

  /* AWS S3 requires a x-amz-content-sha256 header, and supports special
   * values like UNSIGNED-PAYLOAD */
  sign_as_s3 = (strcasecompare(provider0, "aws") &&
                strcasecompare(service, "s3"));

  payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);

  if(!payload_hash) {
    if(sign_as_s3)
      result = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
                                    sha_hex, content_sha256_hdr);
    else
      result = calc_payload_hash(data, sha_hash, sha_hex);
    if(result)
      goto fail;

    payload_hash = sha_hex;
    /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
    payload_hash_len = strlen(sha_hex);
  }

#ifdef DEBUGBUILD
  {
    char *force_timestamp = getenv("CURL_FORCETIME");
    if(force_timestamp)
      clock = 0;
    else
      clock = time(NULL);
  }
#else
  clock = time(NULL);
#endif
  result = Curl_gmtime(clock, &tm);
  if(result) {
    goto fail;
  }
  if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
    result = CURLE_OUT_OF_MEMORY;
    goto fail;
  }

  result = make_headers(data, hostname, timestamp, provider1,
                        &date_header, content_sha256_hdr,
                        &canonical_headers, &signed_headers);
  if(result)
    goto fail;

  if(*content_sha256_hdr) {
    /* make_headers() needed this without the \r\n for canonicalization */
    size_t hdrlen = strlen(content_sha256_hdr);
    DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
    memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
  }

  memcpy(date, timestamp, sizeof(date));
  date[sizeof(date) - 1] = 0;

  result = canon_query(data, data->state.up.query, &canonical_query);
  if(result)
    goto fail;

  result = canon_string(data->state.up.path, strlen(data->state.up.path),
                        &canonical_path, NULL);
  if(result)
    goto fail;
  result = CURLE_OUT_OF_MEMORY;

  canonical_request =
    curl_maprintf("%s\n" /* HTTPRequestMethod */
                  "%s\n" /* CanonicalURI */
                  "%s\n" /* CanonicalQueryString */
                  "%s\n" /* CanonicalHeaders */
                  "%s\n" /* SignedHeaders */
                  "%.*s",  /* HashedRequestPayload in hex */
                  method,
                  Curl_dyn_ptr(&canonical_path),
                  Curl_dyn_ptr(&canonical_query) ?
                  Curl_dyn_ptr(&canonical_query) : "",
                  Curl_dyn_ptr(&canonical_headers),
                  Curl_dyn_ptr(&signed_headers),
                  (int)payload_hash_len, payload_hash);
  if(!canonical_request)
    goto fail;

  DEBUGF(infof(data, "Canonical request: %s", canonical_request));

  /* provider 0 lowercase */
  Curl_strntolower(provider0, provider0, strlen(provider0));
  request_type = curl_maprintf("%s4_request", provider0);
  if(!request_type)
    goto fail;

  credential_scope = curl_maprintf("%s/%s/%s/%s",
                                   date, region, service, request_type);
  if(!credential_scope)
    goto fail;

  if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
                   strlen(canonical_request)))
    goto fail;

  sha256_to_hex(sha_hex, sha_hash);

  /* provider 0 uppercase */
  Curl_strntoupper(provider0, provider0, strlen(provider0));

  /*
   * Google allows using RSA key instead of HMAC, so this code might change
   * in the future. For now we only support HMAC.
   */
  str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
                              "%s\n" /* RequestDateTime */
                              "%s\n" /* CredentialScope */
                              "%s",  /* HashedCanonicalRequest in hex */
                              provider0,
                              timestamp,
                              credential_scope,
                              sha_hex);
  if(!str_to_sign) {
    goto fail;
  }

  /* provider 0 uppercase */
  secret = curl_maprintf("%s4%s", provider0,
                         data->state.aptr.passwd ?
                         data->state.aptr.passwd : "");
  if(!secret)
    goto fail;

  HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
  HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1);
  HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0);
  HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
  HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);

  sha256_to_hex(sha_hex, sign0);

  /* provider 0 uppercase */
  auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
                               "Credential=%s/%s, "
                               "SignedHeaders=%s, "
                               "Signature=%s\r\n"
                               /*
                                * date_header is added here, only if it was not
                                * user-specified (using CURLOPT_HTTPHEADER).
                                * date_header includes \r\n
                                */
                               "%s"
                               "%s", /* optional sha256 header includes \r\n */
                               provider0,
                               user,
                               credential_scope,
                               Curl_dyn_ptr(&signed_headers),
                               sha_hex,
                               date_header ? date_header : "",
                               content_sha256_hdr);
  if(!auth_headers) {
    goto fail;
  }

  Curl_safefree(data->state.aptr.userpwd);
  data->state.aptr.userpwd = auth_headers;
  data->state.authhost.done = TRUE;
  result = CURLE_OK;

fail:
  Curl_dyn_free(&canonical_query);
  Curl_dyn_free(&canonical_path);
  Curl_dyn_free(&canonical_headers);
  Curl_dyn_free(&signed_headers);
  free(canonical_request);
  free(request_type);
  free(credential_scope);
  free(str_to_sign);
  free(secret);
  free(date_header);
  return result;
}