GoogleSmarthome JWT Creation - c#

The documentation is at Using OAuth 2.0 for Server to Server Applications
You will notice in the documentation the disclaimer "don't do this but use the libraries". Unfortunately, there does NOT appear to be .Net Core libraries and I have suggested to the Smarthome program managers that support for .Net Core should be the same as provided for Java, node.js and Python.
However, I'm hunkered down, socially distanced and have some time available so I gave it a shot. There are a lot of moving parts here but to start I downloaded a p12 file instead of a JSON file (as indicated in the documentation) from the Google Console for the account.
The code:
using System;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace MakeJWTTest
{
class Program
{
static void Main(string[] args)
{
string token = MakeJwt();
Console.WriteLine();
Console.WriteLine("The Final JWT: " + token );
}
public static string MakeJwt()
{
string jwt = string.Empty;
string iss = "XXXXXXXXXXXXXXX-v2.iam.gserviceaccount.com";
string scope = "https://www.googleapis.com/auth/homegraph";
string aud = "https://oauth2.googleapis.com/token";
string exp = EpochExpiration();
string iat = EpochCurrentTime();
string jwtHeader = "{\"alg\":\"RS256\",\"typ\":\"JWT\"}";
Console.WriteLine("iss: " + iss);
Console.WriteLine("scope: " + scope);
Console.WriteLine("aud: " + aud);
Console.WriteLine("exp: " + exp);
Console.WriteLine("iat: " + iat);
Console.WriteLine("JWT Header: " + jwtHeader);
string claim = "{\"iss\": \"" + iss + "\",\"scope\": \"" + scope + "\",\"aud\": \"" + aud + "\",\"exp\": " +
exp + ",\"iat\": " + iat + "}";
var encodedHeader = Base64UrlEncoder.Encode(jwtHeader);
Console.WriteLine("Encoded JWT Header: " + encodedHeader);
var encodedClaim = Base64UrlEncoder.Encode(claim);
string claimSet = encodedHeader + "." + encodedClaim;
string sig = Sign(claimSet);
jwt = claimSet + '.' + sig;
return jwt;
}
public static string EpochExpiration()
{
DateTime epoch = DateTime.UnixEpoch;
DateTime now = DateTime.UtcNow;
DateTime expiration = now.AddMinutes(60);
TimeSpan secondsSinceEpoch = expiration.Subtract(epoch);
return secondsSinceEpoch.TotalSeconds.ToString().Substring(0, 10);
}
public static string EpochCurrentTime()
{
DateTime epoch = DateTime.UnixEpoch;
DateTime now = DateTime.UtcNow;
TimeSpan secondsSinceEpoch = now.Subtract(epoch);
return secondsSinceEpoch.TotalSeconds.ToString().Substring(0, 10);
}
public static string Sign( string toSHA256)
{
try
{
byte[] data = Encoding.ASCII.GetBytes(toSHA256);
var certificate = new X509Certificate2(#"XXXXXXXXXXXXXXX-v2-6790af22aa27.p12", "notasecret", X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
// byte[] key = certificate.PrivateKey.ExportPkcs8PrivateKey();
key.FromXmlString(certificate.PrivateKey.ToXmlString(true));
//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));
return Base64UrlEncoder.Encode(sig);
}
catch { Exception e; return null; }
}
}
}
Run this program and I get:
scope: https://www.googleapis.com/auth/homegraph
aud: https://oauth2.googleapis.com/token
exp: 1586193162
iat: 1586189562
JWT Header: {"alg":"RS256","typ":"JWT"}
Encoded JWT Header: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9
The Final JWT: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiAibGltaXRlZG1vYmlsaXR5djJAbGltaXRlZC1tb2JpbGl0eS1zb2x1dGlvbnMtdjIuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iICJzY29wZSI6ICJodHRwczovL3d3dy5nb29nbGVhcGlzLmNvbS9hdXRoL2hvbWVncmFwaCJhdWQiOiAiaHR0cHM6Ly9vYXV0aDIuZ29vZ2xlYXBpcy5jb20vdG9rZW4iZXhwIjogIjE1ODYxOTMxNjIiaWF0IjogMTU4NjE4OTU2Mg.ZOX93iUhirtH2tf95XzLYrGIbTK8kABipfVa6DnD-sAe3WcRfLmLVIAybtHTHxC8frCuZtCeS4XMT-EC69kLy-ks5BWFTnaRwm0TfIeNzIVrGfJUVRchvaJLFM9-wX6svVa4fGHMp8pKttO22BI3sIEisxXx2tw6Ge_8QRZXjSCXD0rg5P-0S-pnd8omkgPv_PhhALqcwd9RTUpcAqcMDoyP7ZxpBSMt1EwySixctKz2y4sRCGC8xaxp5E5VnH3liz3xTNMY5QRNX-tMxVIunh0Qp9v7bkuuvnhrwbcjRPq9qTKlhfIQBmZvaA5-9hzJZOiWWHmCunMec1pZp0bAgQ
Press any key to close this window . . .
I then run this from Powershell using curl:
.\curl -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3NjEzMjY3OTgwNjktcjVtbGpsbG4xcmQ0bHJiaGc3NWVmZ2lncDM2bTc4ajVAZGV2ZWxvcGVyLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJzY29wZSI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL2F1dGgvcHJlZGljdGlvbiIsImF1ZCI6Imh0dHBzOi8vYWNjb3VudHMuZ29vZ2xlLmNvbS9vL29hdXRoMi90b2tlbiIsImV4cCI6MTMyODU3MzM4MSwiaWF0IjoxMzI4NTY5NzgxfQ.RZVpzWygMLuL-n3GwjW1_yhQhrqDacyvaXkuf8HcJl8EtXYjGjMaW5oiM5cgAaIorrqgYlp4DPF_GuncFqg9uDZrx7pMmCZ_yHfxhSCXru3gbXrZvAIicNQZMFxrEEn4REVuq7DjkTMyCMGCY1dpMa8aWfTQFt3Eh7smLchaZsU' https://oauth2.googleapis.com/token
Which returns:
{"error":"invalid_grant","error_description":"Invalid grant: account not found"}
Could be a bunch of things wrong here. Any thoughts welcomed.

There were two errors in the code:
The JSON string
claim = "{\"iss\": \"" + iss + "\",\"scope\": \"" + scope +
"\",\"aud\": \"" + aud + "\",\"exp\": " +
exp + ",\"iat\": " + iat + "}";
The DateTime needs to be UTC
And now it works.

Related

create certificateRequest from base64 string in .net Framework 4.7.2

In the past I used for our certTool the com CERTENROLLLib to create the csr. Since Version 4.7.2 you can use the .net Framework.
It is possible to create the csr by passing all the necessary Attributes in the CreateSigningRequest Method and convert it into a pem base64 string.
Unfortunately I couldn't find the other way around, copy a csr in pem Format in a field and read all the csr Attributes from it (cn, san, organization, etc.)
I don't want to use the com lib CERTENROLLLib, openssl or other 3rd parties.
Here is what I have done (to get the csr pem string) found good examples in here and at MS Framework class description, thanks for your help
protected void createButton_Click(object sender, EventArgs e)
{
string csr_cn = txtb_csr_cn.Text;
string csr_c = txtb_csr_c.Text;
string csr_l = txtb_csr_l.Text;
string csr_o = txtb_csr_o.Text;
string csr_ou = txtb_csr_ou.Text;
string csr_s = txtb_csr_s.Text;
csr_san = sanMemo.Text.Replace(" ", "");
if (csr_san.IndexOf(csr_cn) == -1)
{
if (csr_san == "")
{
csr_san = csr_cn;
}
else
{
csr_san = csr_cn + "," + csr_san;
}
}
csr_key_size = Convert.ToInt32(combobox_csr_key.Text);
csr_info = "CN=" + csr_cn + "," + "OU=" + csr_ou + "," + "O=" + csr_o + "," + "L=" + csr_l + "," + "S=" + csr_s + "," + "C=" + csr_c;
notesMemo.Text = CreateCSR(); //CreateRequest();
}
public static string CreateCSR()
{
string[] arrSeperator = new string[] { "," };
RSA keySize = RSA.Create(csr_key_size);
CertificateRequest parentReq = new CertificateRequest(csr_info,
//"CN=Experimental Issuing Authority",
keySize,
HashAlgorithmName.SHA256,
RSASignaturePadding.Pkcs1);
parentReq.CertificateExtensions.Add(
new X509BasicConstraintsExtension(true, false, 0, true));
parentReq.CertificateExtensions.Add(
new X509SubjectKeyIdentifierExtension(parentReq.PublicKey, false));
var sanBuilder = new SubjectAlternativeNameBuilder();
Array arrsan = csr_san.Split(arrSeperator, StringSplitOptions.RemoveEmptyEntries);
foreach (string sanvalue in arrsan)
{
sanBuilder.AddDnsName(sanvalue);
}
parentReq.CertificateExtensions.Add(sanBuilder.Build());
string csrdecrypt = PemEncodeSigningRequest(parentReq);
return csrdecrypt;
}
public static string PemEncodeSigningRequest(CertificateRequest request)
{
byte[] pkcs10 = request.CreateSigningRequest();
StringBuilder builder = new StringBuilder();
builder.AppendLine("-----BEGIN CERTIFICATE REQUEST-----");
string base64 = Convert.ToBase64String(pkcs10);
int offset = 0;
const int LineLength = 64;
while (offset < base64.Length)
{
int lineEnd = Math.Min(offset + LineLength, base64.Length);
builder.AppendLine(base64.Substring(offset, lineEnd - offset));
offset = lineEnd;
}
builder.AppendLine("-----END CERTIFICATE REQUEST-----");
string tester2 = builder.ToString();
return builder.ToString();
}
There is no pure-managed way to read a Certification Signing Request without third party libraries.
You could try using a P/Invoke to CryptDecodeObjectEx, possibly using the structure identifier of X509_CERT (per https://learn.microsoft.com/en-us/windows/desktop/SecCrypto/constants-for-cryptencodeobject-and-cryptdecodeobject).
But the CertificateRequest class is a PKCS#10 writer without a reader in .NET Framework.
Update (2023-01-30): LoadSigningRequestPem and friends were added in .NET 7.

Post Redirect to URL with post data

I am working on a post redirect for a payment provider and trying to pass form data to their secure URL.
I am doing this in kentico 8 by using their custom payment gateway method as shown here https://docs.kentico.com/display/K8/Creating+a+custom+payment+gateway
So in the custom payment class I prepared the data to be passed to the payment provider in a 'Form' format with hidden fields using this tutorial http://www.codeproject.com/Articles/37539/Redirect-and-POST-in-ASP-NET
However I am not able to figure out on how to redirect to the secure URL with the form data?
Here is my code so far.
I have tried Response.Redirect however it is a GET function instead of POST.
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Configuration;
using CMS;
using CMS.Base;
using CMS.EcommerceProvider;
using CMS.Helpers;
using System.Security.Cryptography;
using System.Web;
using System.Web.Security;
using System.Text;
using System.IO;
using System.Net;
using System.Web.UI;
[assembly: RegisterCustomClass("CustomGateway", typeof(CustomGateway))]
public class CustomGateway : CMSPaymentGatewayProvider
{
/// <summary>
/// Process payment.
/// </summary>
public override void ProcessPayment()
{
// Get payment gateway url
string url = "https://epage.payandshop.com/epage.cgi";
if (url != "")
{
NameValueCollection postData = getRealexData();
RedirectAndPOST(url, postData);
}
else
{
// Show error message - payment gateway url not found
ErrorMessage = "Unable to finish payment: Payment gateway url not found.";
// Update payment result
PaymentResult.PaymentDescription = ErrorMessage;
PaymentResult.PaymentIsCompleted = false;
// Update order payment result in database
UpdateOrderPaymentResult();
}
}
public static void RedirectAndPOST(string destinationUrl, NameValueCollection data)
{
//Prepare the Posting form
string strForm = PreparePOSTForm(destinationUrl, data);
HttpContext.Current.Response.Write(strForm);
HttpContext.Current.Response.End();
}
private static String PreparePOSTForm(string url, NameValueCollection data)
{
//Set a name for the form
string formID = "PostForm";
//Build the form using the specified data to be posted.
StringBuilder strForm = new StringBuilder();
strForm.Append("<form id=\"" + formID + "\" name=\"" +
formID + "\" action=\"" + url +
"\" method=\"POST\">");
foreach (string key in data)
{
strForm.Append("<input type=\"hidden\" name=\"" + key +
"\" value=\"" + data[key] + "\">");
}
strForm.Append("</form>");
//Build the JavaScript which will do the Posting operation.
StringBuilder strScript = new StringBuilder();
strScript.Append("<script language=\"javascript\">");
strScript.Append("var v" + formID + " = document." +
formID + ";");
strScript.Append("v" + formID + ".submit();");
strScript.Append("</script>");
//Return the form and the script concatenated.
//(The order is important, Form then JavaScript)
return strForm.ToString() + strScript.ToString();
}
public NameValueCollection getRealexData()
{
//format the date expected by Realex
string timestamp = RealexDateFormatter.DateFormatForRealex();
//take the MerchantID and Shared Secret from the web.config
string merchantid = ConfigurationManager.AppSettings["RealexMerchantID"];
string secret = ConfigurationManager.AppSettings["RealexSecret"];
// Order Info
int orderid = ShoppingCartInfoObj.OrderId;
string stringorderid = Convert.ToString(orderid);
string curr = ShoppingCartInfoObj.Currency.CurrencyCode;
double amount = ShoppingCartInfoObj.TotalPrice;
amount = Convert.ToInt32(amount);
string prepareMD5 = timestamp + "." + merchantid + "." + orderid + "." + amount + "." + curr;
//generate the md5 Hash
MD5 md5 = new MD5CryptoServiceProvider();
string temp1 = GetMD5Hash(prepareMD5);
temp1 = temp1.ToLower();
string temp2 = temp1 + "." + secret;
string md5hash = GetMD5Hash(temp2);
md5hash = md5hash.ToLower();
NameValueCollection data = new NameValueCollection();
data.Add("MERCHANT_ID", merchantid);
data.Add("ORDER_ID", stringorderid);
data.Add("CURRENCY", curr);
data.Add("AMOUNT", amount.ToString());
data.Add("TIMESTAMP", timestamp);
data.Add("MD5HASH", md5hash);
data.Add("AUTO_SETTLE_FLAG", "1");
return data;
}
public static String GetMD5Hash(String TextToHash)
{
//Check wether data was passed
if ((TextToHash == null) || (TextToHash.Length == 0))
{
return String.Empty;
}
//Calculate MD5 hash
MD5 md5 = new MD5CryptoServiceProvider();
// Create a new Stringbuilder to collect the bytes
// and create a string.
byte[] data = md5.ComputeHash(Encoding.UTF8.GetBytes(TextToHash));
StringBuilder sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
for (int i = 0; i < data.Length; i++)
{
sBuilder.Append(data[i].ToString("x2"));
}
return sBuilder.ToString();
}
}
Take a look at this answer from a similar question. I would recommend using Method 3 in that answer. By creating an asynchronous call, you could wait for a response back and either redirect on a successful response with Response.Redirect(), or notify the user of any errors.

the request signature we calculated does not match the signature you provided aws

I want to cors upload my video file from browser to Amazon S3 storage via asp.net project. But I keep getting this error:"the request signature we calculated does not match the signature you provided"
Here is the javascript code I have;
function uploadFile() {
var access_key = "xx";
var secret = "xx";
var policy = "xx";
var signature = "xx";
var file = document.getElementById('file').files[0];
var fd = new FormData();
var key = "folder1/" + (new Date).getTime() + '-' + file.name;
fd.append('key', key);
fd.append('acl', 'public-read-write');
fd.append('Content-Type', file.type);
fd.append('AWSAccessKeyId', access_key);
fd.append('policy', policy)
fd.append('signature', signature);
fd.append("file", file);
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", uploadProgress, false);
xhr.addEventListener("load", uploadComplete, false);
xhr.addEventListener("error", uploadFailed, false);
xhr.addEventListener("abort", uploadCanceled, false);
xhr.open('POST', 'https://bucketName.s3.amazonaws.com/', true);
xhr.send(fd);
}
Here is the policy string ;
{" +
"\"Id\": \"Policyxxx\"," +
"\"Version\": \"2012-10-17\"," +
"\"Statement\": [" +
"{" +
"\"Sid\": \"Stmtxxx\"," +
"\"Action\": \"s3:*\"," +
"\"Effect\": \"Allow\"," +
"\"Resource\": \"arn:aws:s3:::bucketName/*\"," +
"\"Principal\": \"*\"" +
"}" +
"]" +
"}
and here is the getsignature method;
public static string GetS3Signature(string policyStr)
{
string b64Policy = Convert.ToBase64String(Encoding.ASCII.GetBytes(policyStr));
byte[] b64Key = Encoding.ASCII.GetBytes(AwsSecretKey);
HMACSHA1 hmacSha1 = new HMACSHA1(b64Key);
var c = Convert.ToBase64String(hmacSha1.ComputeHash(Encoding.ASCII.GetBytes(b64Policy)));
return c;
}
What could be the reason for the error
Any conditions which are mentioned in the policy must also be in the form data. For example if you have a condition like
["starts-with", "x-amz-meta-myelement", ""]
Then you have to send that key. If the policy has constraints on content-type or key prefix these may also cause signature errors.

restsharp how to add key value pair as parameter

I am trying to consume stripe.com api with restsharp, using the charge command
https://stripe.com/docs/api/php#create_charge
there's an opportunity to pass metadata as key value pairs but I don't seem to succeed
const string baseUrl = "https://api.stripe.com/";
const string endPoint = "v1/charges";
var apiKey = this.SecretKey;
var client = new RestClient(baseUrl) { Authenticator = new HttpBasicAuthenticator(apiKey, "") };
var request = new RestRequest(endPoint, Method.POST);
request.AddParameter("card", token);
request.AddParameter("amount", wc.totalToPayForStripe);
request.AddParameter("currency", "eur");
request.AddParameter("description", wc.crt.cartid + " - " + wc.co.oid);
request.AddParameter("metadata", "{cartid: " + wc.crt.cartid + ", oid: " + wc.co.oid + "}");
request.AddParameter("statement_description", "# " + wc.crt.cartid);
request.AddParameter("description", wc.crt.cartid + " - " + wc.co.oid);
Always getting the following error:
Invalid metadata: metadata must be a set of key-value pairs
Clearly I don't pass the key value pair the way I should but I can't find any restsharp documentation on that.
Anyone can help?
Try this:
const string baseUrl = "https://api.stripe.com/";
const string endPoint = "v1/charges";
var apiKey = this.SecretKey;
var client = new RestClient(baseUrl) { Authenticator = new HttpBasicAuthenticator(apiKey, "") };
var request = new RestRequest(endPoint, Method.POST);
request.AddParameter("card", token);
request.AddParameter("amount", wc.totalToPayForStripe);
request.AddParameter("currency", "eur");
request.AddParameter("description", wc.crt.cartid + " - " + wc.co.oid);
request.AddParameter("metadata[cartid]", wc.crt.cartid);
request.AddParameter("metadata[oid]", wc.co.oid);
request.AddParameter("statement_description", "# " + wc.crt.cartid);
request.AddParameter("description", wc.crt.cartid + " - " + wc.co.oid);
For some reason HTTP Post requests can not accept key-value objects and must be sent in this type of format. This isn't a stripe restriction, but HTTP in general.
I think it's telling you to enter them as such:
request.AddParameter("metadata", "[ { cartid: " + wc.crt.cartid + "} ,{ oid: " + wc.co.oid + " }]" );

Code for Google Analytic product hit in c#

I'm new in google analytic. I go through some regarding this. I found that there is no direct method to hit a windows application in google analytic. But i found some solutions in stackoverflow. I tried that, but didn't work for me. Below is the code that I'm using.
private void analyticsmethod4(string trackingId, string pagename)
{
Random rnd = new Random();
long timestampFirstRun, timestampLastRun, timestampCurrentRun, numberOfRuns;
// Get the first run time
timestampFirstRun = DateTime.Now.Ticks;
timestampLastRun = DateTime.Now.Ticks - 5;
timestampCurrentRun = 45;
numberOfRuns = 2;
// Some values we need
string domainHash = "123456789"; // This can be calcualted for your domain online
int uniqueVisitorId = rnd.Next(100000000, 999999999); // Random
string source = "Shop";
string medium = "medium123";
string sessionNumber = "1";
string campaignNumber = "1";
string culture = Thread.CurrentThread.CurrentCulture.Name;
string screenRes = Screen.PrimaryScreen.Bounds.Width + "x" + Screen.PrimaryScreen.Bounds.Height;
string statsRequest = "http://www.google-analytics.com/__utm.gif" +
"?utmwv=4.6.5" +
"&utmn=" + rnd.Next(100000000, 999999999) +
// "&utmhn=hostname.mydomain.com" +
"&utmcs=-" +
"&utmsr=" + screenRes +
"&utmsc=-" +
"&utmul=" + culture +
"&utmje=-" +
"&utmfl=-" +
"&utmdt=" + pagename + // Here i passed my profile name "MyWindowsApp"
"&utmhid=1943799692" +
"&utmr=0" +
"&utmp=" + pagename +
"&utmac=" + trackingId + //Tracking id : ie "UA-XXXXXXXX-X"
"&utmcc=" +
"__utma%3D" + domainHash + "." + uniqueVisitorId + "." + timestampFirstRun + "." + timestampLastRun + "." + timestampCurrentRun + "." + numberOfRuns +
"%3B%2B__utmz%3D" + domainHash + "." + timestampCurrentRun + "." + sessionNumber + "." + campaignNumber + ".utmcsr%3D" + source + "%7Cutmccn%3D(" + medium + ")%7Cutmcmd%3D" + medium + "%7Cutmcct%3D%2Fd31AaOM%3B";
try
{
using (var client = new WebClient())
{
//byte[] bt = client.DownloadData(statsRequest);
Stream data = client.OpenRead(statsRequest);
StreamReader reader = new StreamReader(data);
string s = reader.ReadToEnd();
MessageBox.Show(s);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
This example is also got from this site itself. I don't know where was the problem. Please direct me, how can i make it. This is the output i'm getting "GIF89a".
Thanks
Bobbin Paulose
So it's working. The Google Analytics call loads a tiny GIF image, and the querystring parameters provided in the request trigger all the Google Analytics goodness. If you're getting a response back, you have registered your event successfully with Google.

Categories

Resources