I'm trying to create an API in Dart to generate a Signed URL to upload a file in Cloud Storage but even following the google documentation I can't successfully generate. In all my attempts, when making the request the API returns me: The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
I think the error is in generating the signature, or in the hash of the canonical request
final canonicalRequest = getCanonicalRequest(
canonicalUri,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
);
final bytes = utf8.encode(canonicalRequest);
final hashedCanonicalRequest = HEX.encode(sha256.convert(bytes).bytes);
final stringToSign = getStringToSign(
dateTimestamp,
credentialScope,
hashedCanonicalRequest,
);
final signature = await getSignature(stringToSign);
Future<String> getSignature(String stringToSign) async {
final privateKey =
await parseKeyFromFile<pointy.RSAPrivateKey>('private.pem');
final signer =
Signer(RSASigner(RSASignDigest.SHA256, privateKey: privateKey));
final signature = signer.sign(stringToSign);
return HEX.encode(signature.bytes);
}
I have a simple module that creates JWT tokens using RsaSha256 (RS256) asymmetric encryption.
let getSigningCredentials (rsa:RSA) (algo)=
try
let key = RsaSecurityKey(rsa)
let signingCredentials = SigningCredentials(key, algo)
signingCredentials.CryptoProviderFactory <- CryptoProviderFactory(CacheSignatureProviders = false)
Ok signingCredentials
with ex ->
Error ex
let getSigningCredentialsSha256 (rsa:RSA) =
getSigningCredentials rsa SecurityAlgorithms.RsaSha256
let createJwtSecurityToken jwtSecurityTokenRecord =
try
Ok
(JwtSecurityToken(
issuer = jwtSecurityTokenRecord.Issuer,
signingCredentials = jwtSecurityTokenRecord.SigningCredentials,
claims = jwtSecurityTokenRecord.Claims,
notBefore = Nullable jwtSecurityTokenRecord.NotBefore,
expires = Nullable jwtSecurityTokenRecord.Expires))
with ex ->
Error ex
This works well, I can create tokens. I can load them into jwt.io (i know, not production token) and verify with the public part of the keypair. I use OpenSSL to generate the keypair so I need to convert the private key to Pkcs8PrivateKey before importing it into the RSA object.
So everything works just fine. Now, I would like to verify the JWT token with the public key using F# (C# code is fine too).
Here is where it gets hairy.
I could not find any documentation on how to do so.
The only validation method I was able to find uses the signing key (private key) for the verification.
let validateJwtToken (rsa:RSA) (tokenString:string) =
try
let tokenValidationParameters =
TokenValidationParameters (
ValidateIssuerSigningKey = true,
IssuerSigningKey = RsaSecurityKey rsa,
ValidateIssuer = false,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero
)
Ok (JwtSecurityTokenHandler().ValidateToken (tokenString, tokenValidationParameters, ref null))
with ex ->
Error ex
Is there a way / method to verify a JWT token with the public key?
I am using System.IdentityModel.Tokens.Jwt and Microsoft.IdentityModel.Tokens.
I have created a service account and downloaded my JSON Credential on Google Cloud Platform. I need to make REST POST call in .NET to DialogFlow Service API. At this moment, I can do it only with a generated token in PowerShell. Since, I need to do it all from script, I need to generate a JWT to pass as my bearer in my REST call. My Problem is that the generated JWT is not honored by Google.
I get my response in PowerShell based on this doc page and I replicate sample codes from this doc page to create my JWT.
public static string GetSignedJwt(string emailClient, string
dialogueFlowServiceApi, string privateKeyId, string privateKey, string
jsonPath)
{
// to get unix time in seconds
var unixTimeSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
// start time of Unix system
var origin = new DateTime(1970, 1, 1, 0, 0, 0, 0);
// adding milliseconds to reach the current time, it will be used for issueAt time
var nowDataTime = origin.AddSeconds(unixTimeSeconds);
// one hour after the current time, it will be used for expiration time
var oneHourFromNow = nowDataTime.AddSeconds(3600);
// holder of signed json web token that we will return at the end
var signedJwt = "";
try
{
// create our payload for Jwt
var payload = new Dictionary<string, object>
{
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
// create our additional headers
var extraHeaders = new Dictionary<string, object>
{
{"kid", privateKeyId}
};
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
}
catch (Exception e)
{
Console.WriteLine(e);
// return null if there has been any error
return null;
}
finally
{
Console.WriteLine(signedJwt);
}
return signedJwt;
}
Notice that, it is needed to be signed in RSA256 by passing public and private keys, as Google did it in Java sample snippet, however, my equivalent in .Net gives me only Object reference not set to an instance of an object when I use that algorithm:
var key = RSA.Create(privateKey);
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
signedJwt = encoder.Encode(extraHeaders, payload, privateKey);
Besides of correct keys, I am using https://dialogflow.googleapis.com/google.cloud.dialogflow.v2beta1.Intents as dialogFlow service API key.
I expect it that my generated JWT gets accepted, however it is rejected by Google.
1) You are using the wrong algorithm
Change this line of code:
IJwtAlgorithm algorithm = new RS256Algorithm(null, key);
To this:
IJwtAlgorithm algorithm = new HMACSHA256Algorithm();
2) For the JWT headers:
var additional_headers = new Dictionary<string, object>
{
{ "kid", privateKeyId },
{ "alg", "RS256" },
{ "typ", "JWT" }
};
3) Your JWT Payload does not include a scope. I am not sure which scope you need but here is an example. Add this to the payload before creating the JWT:
string scope = "https://www.googleapis.com/auth/cloud-platform";
var payload = new Dictionary<string, object>
{
{"scope", scope},
{"iss", emailClient},
{"sub", emailClient},
{"aud", dialogueFlowServiceApi},
{"iat", nowDataTime},
{"exp", oneHourFromNow}
};
4) For most Google APIs (not all) you also need to exchange the Signed JWT for a Google OAuth Access Token:
public static string AuthorizeToken(string token, string auth_url)
{
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var content = new NameValueCollection();
// Request a "Bearer" access token
content["assertion"] = token;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
var response = client.UploadValues(auth_url, "POST", content);
return Encoding.UTF8.GetString(response);
}
The Authorization URL for above:
string auth_url = "https://www.googleapis.com/oauth2/v4/token";
I have managed to get back a JWT token from Identity Server using OAuth2 and would like to extract the claims from the token.
When I use a token decoder such as https://developers.google.com/wallet/digital/docs/jwtdecoder, I can peek inside the token and it looks fine.
However I am not sure what decrypting to use in c# in order to use the Microsoft JwtSecurityTokenHandler.ValidateToken to get back a claims identity.
In identity server, I am using a symmetric key which I have pasted for reference in my code. The JWT token is also valid.
Would really appreciate some help:
string token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vaWRlbnRpdHlzZXJ2ZXIudjIudGhpbmt0ZWN0dXJlLmNvbS90cnVzdC9jaGFuZ2V0aGlzIiwiYXVkIjoidXJuOndlYmFwaXNlY3VyaXR5IiwibmJmIjoxMzk3MTEzMDY5LCJleHAiOjEzOTcxNDkwNjksIm5hbWVpZCI6InN0ZWZhbiIsInVuaXF1ZV9uYW1lIjoic3RlZmFuIiwiYXV0aG1ldGhvZCI6Ik9BdXRoMiIsImF1dGhfdGltZSI6IjIwMTQtMDQtMTBUMDY6NTc6NDguODEyWiIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9jbGllbnQiOiJyZWx5aW5nIHBhcnR5IDMgdGVzdCBjbGllbnQgbmFtZSIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9zY29wZSI6InVybjp3ZWJhcGlzZWN1cml0eSJ9.cFnmgHxrpy2rMg8B6AupVrJwltu7RhBAeIx_D3pxJeI";
string key = "ZHfUES/6wG28LY+SaMtvaeek34t2PBrAiBxur6MAI/w=";
var validationParameters = new TokenValidationParameters()
{
AllowedAudience = "urn:webapisecurity",
SigningToken = new ????
ValidIssuer = #"http://identityserver.v2.thinktecture.com/trust/changethis"
};
var tokenHandler = new JwtSecurityTokenHandler();
var principal = tokenHandler.ValidateToken(token, validationParameters);
What sort of SigningToken should I use for the validationParameters.SigningToken ??
You can use the following website to Decode the token
http://jwt.io/
or here is a code to Decode JWT Token using C#
class Program
{
static void Main(string[] args)
{
string token ="eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vaWRlbnRpdHlzZXJ2ZXIudjIudGhpbmt0ZWN0dXJlLmNvbS90cnVzdC9jaGFuZ2V0aGlzIiwiYXVkIjoidXJuOndlYmFwaXNlY3VyaXR5IiwibmJmIjoxMzk3MTEzMDY5LCJleHAiOjEzOTcxNDkwNjksIm5hbWVpZCI6InN0ZWZhbiIsInVuaXF1ZV9uYW1lIjoic3RlZmFuIiwiYXV0aG1ldGhvZCI6Ik9BdXRoMiIsImF1dGhfdGltZSI6IjIwMTQtMDQtMTBUMDY6NTc6NDguODEyWiIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9jbGllbnQiOiJyZWx5aW5nIHBhcnR5IDMgdGVzdCBjbGllbnQgbmFtZSIsImh0dHA6Ly9pZGVudGl0eXNlcnZlci50aGlua3RlY3R1cmUuY29tL2NsYWltcy9zY29wZSI6InVybjp3ZWJhcGlzZWN1cml0eSJ9.cFnmgHxrpy2rMg8B6AupVrJwltu7RhBAeIx_D3pxJeI";
var parts = token.Split('.');
string partToConvert = parts[1];
var partAsBytes = Convert.FromBase64String(partToConvert);
var partAsUTF8String = Encoding.UTF8.GetString(partAsBytes, 0, partAsBytes.Count());
//JSON.net required
var jwt = JObject.Parse(partAsUTF8String);
Console.Write(jwt.ToString());
Console.ReadLine();
}
}
It's a BinarySecretSecurityToken - base64 decode the stringified key to use it.
I need to use Google Play api service. I try to get the authorization token using "Service Account" authentication method but I always get "400 Bad request" as response and the json: "error": "invalid_grant".
Is there someone that is using this authorization method with Google Play Api as scope and C# as programming language?
I searched everywhere for implementing it by existing libraries or by myself following this guide (https://developers.google.com/accounts/docs/OAuth2ServiceAccount#libraries) and other users suggestions but it doesn't work
Here is the code
// HEADER
Dictionary<string, string> JWTHeaderObject = new Dictionary<string, string>();
JWTHeaderObject.Add("alg", "RS256");
JWTHeaderObject.Add("typ", "JWT");
string JWTHeader = JsonConvert.SerializeObject(JWTHeaderObject);
Dictionary<string, object> JWTContentObject = new Dictionary<string, object>();
JWTContentObject.Add("iss", ".....#developer.gserviceaccount.com");
JWTContentObject.Add("scope", "https:\\/\\/www.googleapis.com\\/auth\\/androidpublisher");
JWTContentObject.Add("aud", "https:\\/\\/accounts.google.com\\/o\\/oauth2\\/token");
DateTime now = DateTime.UtcNow;
// set to maximum expiration time: 1h from now
DateTime expireDate = now.Add(new TimeSpan(0, 55, 0));
// to UNIX timestamp
JWTContentObject.Add("exp", (int)Utils.ConverDateTimeToUnixTimestamp(expireDate));
JWTContentObject.Add("iat", (int)Utils.ConverDateTimeToUnixTimestamp(now));
string JWTContent = JsonConvert.SerializeObject(JWTContentObject);
string JWTBase64Header = Utils.Base64Encode(Encoding.UTF8.GetBytes(JWTHeader));
string JWTBase64Content = Utils.Base64Encode(Encoding.UTF8.GetBytes(JWTContent));
// create JWT signature with encoded header and content and the private key obtained from API console
byte[] JWTSignatureInput = Encoding.UTF8.GetBytes(JWTBase64Header + "." + JWTBase64Content);
X509Certificate2 cert = (X509Certificate2)Utils.GetCertificate(StoreName.My, StoreLocation.CurrentUser, "thumbprint");
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey;
// without these two lines I get invalid algorithm on SHA256
// (see: http://blogs.msdn.com/b/shawnfa/archive/2008/08/25/using-rsacryptoserviceprovider-for-rsa-sha256-signatures.aspx)
RSACryptoServiceProvider rsaClear = new RSACryptoServiceProvider();
// Export RSA parameters from 'rsa' and import them into 'rsaClear'
rsaClear.ImportParameters(rsa.ExportParameters(true));
string JWTBase64Signature = Utils.Base64Encode(rsaClear.SignData(JWTSignatureInput, CryptoConfig.CreateFromName("SHA256")));
return JWTBase64Header + "." + JWTBase64Content + "." + JWTBase64Signature;
and the method specified here to encode to base 64: Utils.Base64Encode()
Then I call command below and I get {"error":"invalid_grant"}:
curl -d 'grant_type=assertion&assertion_type=http%3a%2f%2foauth.net%2fgrant_type%2fjwt%2f1.0%2fbearer&assertion=eyJhbGciOiJSUzI1N---.eyJpc3MiOiIxIiwic2---.KJ-DtTtdp5oIKPWVNqN---' https://accounts.google.com/o/oauth2/token
(encoded JWT reduced for brevity)