i need to create users in zuora using dotnet framework 4.8. But I'm having trouble porting the code from java to c#
here is the java implementation https://knowledgecenter.zuora.com/Zephr/Developer_Documentation/HMAC_Request_Signing_and_Key_Pairs
package io.blaize.api.utilities.security;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import io.blaize.api.exception.HmacException;
public class HmacSigner {
public static final String TWO_DIGIT_HEX_FORMAT = "%1$02x";
private final String algorithm;
public HmacSigner(String algorithm)
{
if ("SHA256".equals(algorithm)) {
this.algorithm = "SHA-256";
} else {
this.algorithm = algorithm;
}
}
public String signRequest(String secretKey, String body, String path, String query, String method,
String timestamp, String nonce) throws HmacException {
Objects.requireNonNull(secretKey);
Objects.requireNonNull(body);
Objects.requireNonNull(path);
Objects.requireNonNull(query);
Objects.requireNonNull(method);
Objects.requireNonNull(timestamp);
Objects.requireNonNull(nonce);
try {
MessageDigest messageDigest = MessageDigest.getInstance(algorithm);
messageDigest.update(secretKey.getBytes());
messageDigest.update(body.getBytes());
messageDigest.update(path.getBytes());
messageDigest.update(query.getBytes());
messageDigest.update(method.getBytes());
messageDigest.update(timestamp.getBytes());
messageDigest.update(nonce.getBytes());
byte[] digest = messageDigest.digest();
StringBuffer hash = new StringBuffer();
for (byte digestByte : digest) {
Integer unsignedInteger = new Integer(Byte.toUnsignedInt(digestByte));
hash.append(String.format(TWO_DIGIT_HEX_FORMAT, unsignedInteger));
}
return hash.toString();
} catch (NoSuchAlgorithmException e) {
throw new HmacException(e);
}
}
}
code for create user:
String protocol = "http";
String host = "admin.test.blaize.io";
String path = "/v3/users";
String method = "POST";
String body = "{\"identifiers\": { \"email_address\": \"test@test.com\" }, \"validators\": { \"password\": \"sup3rsecre!10t\" }}";
String accessKey = "xyz";
String secretKey = loadSecretKeySecurely(accessKey);
String timestamp = String.valueOf(new Date().getTime());
String nonce = UUID.randomUUID().toString();
String query = "";
String hash = new HmacSigner("SHA-256").
signRequest(secretKey, body, path, query, method, timestamp, nonce);
String authorizationHeaderValue = "ZEPHR-HMAC-SHA256 "
+ accessKey + ":" + timestamp + ":" + nonce + ":" + hash;
// This is a standard library implementation for illustration only
HttpURLConnection connection = (HttpURLConnection) new URL(protocol + "://" + host + path + "?" + query).openConnection();
connection.setRequestMethod(method);
connection.addRequestProperty("Authorization", authorizationHeaderValue);
connection.addRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.writeBytes(body);
outputStream.flush();
outputStream.close();
int status = connection.getResponseCode();
if (status >= 200 && status < 400) {
System.out.println(new BufferedReader(new InputStreamReader(connection.getInputStream())).lines().collect(Collectors.joining("\n")));
} else {
System.err.println(status);
}
Below is the code in C# :
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace HMACClient
{
public class HMACDelegatingHandler : DelegatingHandler
{
// First obtained the APP ID and API Key from the server
// The APIKey MUST be stored securely in db or in the App.Config
private string APPId = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
private string APIKey = "xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpResponseMessage response = null;
string requestContentBase64String = string.Empty;
//Get the Request URI
string requestUri = HttpUtility.UrlEncode(request.RequestUri.AbsoluteUri.ToLower());
//Get the Request HTTP Method type
string requestHttpMethod = request.Method.Method;
//Calculate UNIX time
DateTime epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
TimeSpan timeSpan = DateTime.UtcNow - epochStart;
string requestTimeStamp = Convert.ToUInt64(timeSpan.TotalSeconds).ToString();
//Create the random nonce for each request
string nonce = Guid.NewGuid().ToString("N");
//Checking if the request contains body, usually will be null wiht HTTP GET and DELETE
if (request.Content != null)
{
// Hashing the request body, so any change in request body will result a different hash
// we will achieve message integrity
byte[] content = await request.Content.ReadAsByteArrayAsync();
MD5 md5 = MD5.Create();
byte[] requestContentHash = md5.ComputeHash(content);
requestContentBase64String = Convert.ToBase64String(requestContentHash);
}
//Creating the raw signature string by combinging
//APPId, request Http Method, request Uri, request TimeStamp, nonce, request Content Base64 String
string signatureRawData = String.Format("{0}{1}{2}{3}{4}{5}", APPId, requestHttpMethod, requestUri, requestTimeStamp, nonce, requestContentBase64String);
//Converting the APIKey into byte array
var secretKeyByteArray = Convert.FromBase64String(APIKey);
//Converting the signatureRawData into byte array
byte[] signature = Encoding.UTF8.GetBytes(signatureRawData);
//Generate the hmac signature and set it in the Authorization header
using (HMACSHA256 hmac = new HMACSHA256(secretKeyByteArray))
{
byte[] signatureBytes = hmac.ComputeHash(signature);
string requestSignatureBase64String = Convert.ToBase64String(signatureBytes);
//Setting the values in the Authorization header using custom scheme (hmacauth)
request.Headers.Authorization = new AuthenticationHeaderValue("hmacauth", string.Format("{0}:{1}:{2}:{3}", APPId, requestSignatureBase64String, nonce, requestTimeStamp));
}
response = await base.SendAsync(request, cancellationToken);
return response;
}
}
}
private async void createUser()
{
Console.WriteLine("Calling the back-end API");
string apiBaseAddress = "https://mybusiness.api.zephr.com/";
HMACDelegatingHandler customDelegatingHandler = new HMACDelegatingHandler();
HttpClient client = HttpClientFactory.Create(customDelegatingHandler);
string body = "{\"identifiers\": {\"email_address\": \"joe.blow@company.com\"},\"attributes\": {\"first_name\": \"Joe\",\"surname\": \"Blow\"},\"foreign_keys\" : {\"other_id\" : \"0030C00000Xu1LYQAZ\"}}";
HttpResponseMessage response = await client.PostAsync(apiBaseAddress + "v3/users", new StringContent(body, Encoding.UTF8, "application/json"));
if (response.IsSuccessStatusCode)
{
string responseString = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseString);
Console.WriteLine("HTTP Status: {0}, Reason {1}. Press ENTER to exit", response.StatusCode, response.ReasonPhrase);
}
else
{
Console.WriteLine("Failed to call the API. HTTP Status: {0}, Reason {1}", response.StatusCode, response.ReasonPhrase);
}
}
but get error in this line, input string is not valid base64:
var secretKeyByteArray = Convert.FromBase64String(APIKey);
I did it, if anyone needs to create a user in zuora this the c# code:
HmacSigner implementation
public class HmacSigner
{
public const string TWO_DIGIT_HEX_FORMAT = "{0:x2}";
public string SignRequest(string secretKey, string body, string path, string query, string method,
string timestamp, string nonce)
{
if (secretKey == null || body == null || path == null || query == null || method == null || timestamp == null || nonce == null)
{
throw new ArgumentNullException("All input parameters must not be null");
}
try
{
using (var messageDigest = SHA256.Create())
{
byte[] keyBytes = Encoding.UTF8.GetBytes(secretKey);
byte[] bodyBytes = Encoding.UTF8.GetBytes(body);
byte[] pathBytes = Encoding.UTF8.GetBytes(path);
byte[] queryBytes = Encoding.UTF8.GetBytes(query);
byte[] methodBytes = Encoding.UTF8.GetBytes(method);
byte[] timestampBytes = Encoding.UTF8.GetBytes(timestamp);
byte[] nonceBytes = Encoding.UTF8.GetBytes(nonce);
messageDigest.TransformBlock(keyBytes, 0, keyBytes.Length, keyBytes, 0);
messageDigest.TransformBlock(bodyBytes, 0, bodyBytes.Length, bodyBytes, 0);
messageDigest.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0);
messageDigest.TransformBlock(queryBytes, 0, queryBytes.Length, queryBytes, 0);
messageDigest.TransformBlock(methodBytes, 0, methodBytes.Length, methodBytes, 0);
messageDigest.TransformBlock(timestampBytes, 0, timestampBytes.Length, timestampBytes, 0);
messageDigest.TransformFinalBlock(nonceBytes, 0, nonceBytes.Length);
byte[] digest = messageDigest.Hash;
StringBuilder hash = new StringBuilder();
foreach (byte digestByte in digest)
{
hash.AppendFormat(TWO_DIGIT_HEX_FORMAT, digestByte);
}
return hash.ToString();
}
}
catch (Exception e)
{
throw new ApplicationException("Error occurred while generating HMAC", e);
}
}
}
create user code:
void CreateUser()
{
string protocol = "https";
string host = "xxx.api.zephr.com";
string path = "/v3/users";
string method = "POST";
string body = "{\"identifiers\": { \"email_address\": \"joe.blow@company.com\" }, \"validators\": { \"password\": \"sup3rsecre!10t\" }}";
string accessKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
string secretKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
string timestamp = Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalMilliseconds, 0).ToString();
string nonce = Guid.NewGuid().ToString();
string query = "";
string hash = new HmacSigner().SignRequest(secretKey, body, path, query, method, timestamp, nonce);
string authorizationHeaderValue = "ZEPHR-HMAC-SHA256 " + accessKey + ":" + timestamp + ":" + nonce + ":" + hash;
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(protocol + "://" + host + path + "?" + query);
request.Method = method;
request.Headers.Add("Authorization", authorizationHeaderValue);
request.ContentType = "application/json";
var postBody = Encoding.ASCII.GetBytes(body);
request.GetRequestStream().Write(postBody, 0, postBody.Length);
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
int status = (int)response.StatusCode;
Console.WriteLine(status);
using (Stream responseStream = response.GetResponseStream())
{
StreamReader reader = new StreamReader(responseStream);
Console.WriteLine(reader.ReadToEnd());
reader.Close();
}
}