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\": \"[email protected]\" }, \"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\": \"[email protected]\"},\"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
create user code: