How to authenticate with AWS IAM Roles Anywhere in C#

0

I am trying to get the temporary credentials for Iam role anywhere. I am using the input parameters

using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text;

namespace ConsoleApp1 { internal class Program { static void Main(string[] args) { token().GetAwaiter().GetResult();

    }

    static string HexToDecimal(string hex)
    {
        List<int> dec = new List<int> { 0 };   // decimal result
        foreach (char c in hex)
        {
            int carry = Convert.ToInt32(c.ToString(), 16);
            // initially holds decimal value of current hex digit;
            // subsequently holds carry-over for multiplication
            for (int i = 0; i < dec.Count; ++i)
            {
                int val = dec[i] * 16 + carry;
                dec[i] = val % 10;
                carry = val / 10;
            }
            while (carry > 0)
            {
                dec.Add(carry % 10);
                carry /= 10;
            }
        }
        var chars = dec.Select(d => (char)('0' + d));
        var cArr = chars.Reverse().ToArray();
        return new string(cArr);
    }

    static async Task token()
    {
        private_key_file = ''  # PEM format
        public_certificate_file = ''  # PEM format
        region = ''  # AWS Region
        duration_seconds = '1200'  # From 900 to 3600
        profile_arn = ''
        role_arn = ''  # IAM Role ARN
        session_name = 'test_session'
        trust_anchor_arn = ''
        // ************* REQUEST VALUES *************
        string method = "POST";
        string service = "rolesanywhere";
        string host = $"rolesanywhere.{region}.amazonaws.com";
        string endpoint = $"https://rolesanywhere.{region}.amazonaws.com";
        string content_type = "application/json";

        // Load public certificate
        string amz_x509;
        string serial_number_dec;
        try
        {
            string pemContents = File.ReadAllText(public_certificate_file);
            string[] pemLines = pemContents.Split('\n');
            StringBuilder base64String = new StringBuilder();
            for (int i = 1; i < pemLines.Length - 1; i++)
            {
                base64String.Append(pemLines[i]);
            }
            byte[] certBytes = Convert.FromBase64String(base64String.ToString());

            X509Certificate2 cert = new X509Certificate2(certBytes);
            var snum = cert.SerialNumber;

            serial_number_dec = HexToDecimal(snum);
            amz_x509 = Convert.ToBase64String(cert.Export(X509ContentType.Cert));
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Failed to load public certificate: {ex.Message}");
            return;
        }

        // Request parameters for CreateSession--passed in a JSON block.
        string request_parameters = $"{{\"durationSeconds\": {duration_seconds},\"profileArn\": \"{profile_arn}\",\"roleArn\": \"{role_arn}\",\"sessionName\": \"{session_name}\",\"trustAnchorArn\": \"{trust_anchor_arn}\"}}";

        // Create a date for headers and the credential string
        DateTime t = DateTime.UtcNow;
        string amz_date = t.ToString("yyyyMMddTHHmmssZ");
        string date_stamp = t.ToString("yyyyMMdd");

        // ************* TASK 1: CREATE A CANONICAL REQUEST *************
        // Step 1 is to define the verb (GET, POST, etc.)--already done.
        string canonical_uri = "/sessions";
        string canonical_querystring = "";
        string canonical_headers = "content-type:" + content_type + @"\n" + "host:" + host + @"\n" + "x-amz-date:" + amz_date + @"\n" + "x-amz-x509:" + amz_x509 + @"\n";
        string signed_headers = "content-type;host;x-amz-date;x-amz-x509";
        string payload_hash = BitConverter.ToString(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(request_parameters))).Replace("-", "").ToLower();
        string canonical_request = method + @"\n" + canonical_uri + @"\n" + canonical_querystring + @"\n" + canonical_headers + @"\n" + signed_headers + @"\n" + payload_hash;

        // ************* TASK 2: CREATE THE STRING TO SIGN*************
        string algorithm = "AWS4-X509-RSA-SHA256";
        string credential_scope = $"{date_stamp}/{region}/{service}/aws4_request";
        string string_to_sign = algorithm + @"\n" + amz_date + @"\n" + credential_scope + @"\n" + BitConverter.ToString(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(canonical_request))).Replace("-", "").ToLower();

        // ************* TASK 3: CALCULATE THE SIGNATURE *************
        byte[] signature;
        {
            using (var rsa = RSA.Create(8192))
            {
                var dataToSign = Encoding.UTF8.GetBytes(string_to_sign);
                rsa.ImportFromPem(File.ReadAllText(private_key_file));
                signature = rsa.SignData(dataToSign, HashAlgorithmName.SHA512, RSASignaturePadding.Pkcs1);
            }
        }

        string signature_hex = BitConverter.ToString(signature).Replace("-", "").ToLower();

        // ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
        string authorization_header = $"{algorithm} Credential={serial_number_dec.ToString()}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature_hex}";

        HttpClient httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Clear();
        httpClient.DefaultRequestHeaders.Add("X-Amz-Date", amz_date);
        httpClient.DefaultRequestHeaders.Add("X-Amz-X509", amz_x509);
        httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
        httpClient.DefaultRequestHeaders.Add("Authorization", authorization_header);

        // ************* SEND THE REQUEST *************
        Console.WriteLine("\nBEGIN REQUEST++++++++++++++++++++++++++++++++++++");
        Console.WriteLine($"Request URL = {endpoint}");

        StringContent strContent = new StringContent(request_parameters, Encoding.UTF8, "application/json");

        HttpResponseMessage response = await httpClient.PostAsync(endpoint + canonical_uri, strContent);

        Console.WriteLine("\nRESPONSE++++++++++++++++++++++++++++++++++++");
        Console.WriteLine($"Response code: {response.StatusCode}");
        Console.WriteLine(await response.Content.ReadAsStringAsync());
    }
}

}

I am using the method below to add Authorization httpClient.DefaultRequestHeaders.Add("Authorization", authorization_header);

authorization_header value is in this format -> AWS4-X509-RSA-SHA256 Credential={{Certificate_serial_number}}/20240321/{{region}}/rolesanywhere/aws4_request, SignedHeaders=content-type;host;x-amz-date;x-amz-x509, Signature={{signature}} I am facing problem while adding the Authorization header. Error message is authorization_header format is invalid.

We developed this code taking refence of python code which you find using this link https://nerdydrunk.info/aws:roles_anywhere This python code is working.

2 Answers
1

It seems like you're constructing the authorization_header incorrectly. In the AWS Signature Version 4 process, the Credential element should be followed by the Access Key ID, not the Certificate Serial Number (https://docs.aws.amazon.com/rolesanywhere/latest/userguide/authentication-sign-process.html) . Also, the Credential element should include the AWS account ID, not just the Certificate Serial Number. Additionally, make sure that you're following the correct formatting and including all required elements.

Here's how you can modify the authorization_header construction:

string authorization_header = $"{algorithm} Credential={ACCESS_KEY_ID}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature_hex}";

Replace ACCESS_KEY_ID with your AWS Access Key ID.

Make sure that credential_scope is constructed correctly as well, following the format: YYYYMMDD/region/service/aws4_request.

Also, ensure that the signature is correctly generated and formatted according to the AWS Signature Version 4 requirements.

After making these adjustments, try adding the Authorization header again and see if the issue persists.

profile picture
EXPERT
answered 2 months ago
profile picture
EXPERT
reviewed 2 months ago
  • In authorization header there should be serial number as per the documentation. Maybe we are doing something wrong while calculating the signature. Can you guide on how to correctly generate and format signature according to the AWS Signature Version 4 requirements in C#? In the code that we shared can you let us know what mistake we are making while computing the signature?

0

That string you using for your headers does not work with that way of using HttpClient, it will work with this way below:

string authorization_header = $"Credential={serial_number_dec.ToString()}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature_hex}";

HttpClient httpClient = new HttpClient(); 
httpClient.BaseAddress = new Uri(endpoint); 
using var request = new HttpRequestMessage(HttpMethod.Post, canonical_uri);
request.Headers.Add("X-Amz-Date", amz_date); 
request.Headers.Add("X-Amz-X509", amz_x509);
request.Headers.Add("Accept", "application/json");
request.Headers.Authorization = new AuthenticationHeaderValue(algorithm, authorization_header);
request.Content = new StringContent(request_parameters);
var response = await httpClient.SendAsync(request);
answered a month ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions