I'm a newbie in jwt and after read a lot of web pages I've not found examples of how to generate a token (signed and encrypted) with keypairs generated from this website https://mkjwk.org/. I think it can't be very difficult. I think this is the way to generate the signed token (I don't know if it's correct):
RSACryptoServiceProvider rsaCrypto = new RSACryptoServiceProvider();
var payload = new Dictionary<string, object>()
{
{"jti", "ORIGEN_" + Guid.NewGuid().ToString()},
{"iat", DateTime.UtcNow},
{"exp", DateTime.UtcNow.AddYears(1)},
{"login", "USER" },
{"password", "PASSWORD" },
{"origen", "ORIGEN" }
};
// Contains both public and private keys to sign
var headers = new Dictionary<string, object>()
{
{ "kty", "RSA" },
{ "d", "A7Q8cttv_CSG4CJkX_xlU5lUoeRrCPZpyZx9eVaD7zi-tE7wDPKNmJPRP6uR_LA2YVXMmfY9w8q1_v_MiYxkYnFgZqNZlKdwucSQUlnfX5Tt806qh_323h5NnHrKweL-98_d8R4RuZXCWEQ3X0QDCVfccaLVVqLJy8S5zlx0aAVuBJxLxBHFRO700qdUN-RaMjHULoOnE1KbwmfKPfGlLL0YWPHQ9t-qIBh6OSZsDZh30K4VLF8sRXkGgn81_Byp4hK9tCfG98R6fWUM2_FCQrC9R1hO-KTsLffRzMboWe-2ymZGQfZKO-gtFaQH7_AjdVnQYMyKhSSCGYAAroSZAQ"},
{ "e", "AQAB" },
{ "use", "enc"},
{ "kid", "RPA" },
{ "alg", "RS256" },
{ "n", "qJPwMcHtb7xFGGczn20IiEtrPVehquyT6lxIJa_e4vcZE33uM6myVZWocTZWzTDmrNT3bJghEpLOhrgYatT3QnJIiTM9KAD01kYPc5cP5yo6Wmu0YjivqL3Rj7dUvi2pvl7juwYxt1_8zfdnBN5GpBIYcaY3ulVo_OSL7TOxJrua5IMhilQz6kqta3-Rgz3GSglOs94RHRvorYxMyHPQ6KhwSlh_zLzJQZ-0-AZ4yaMPdVwEaaEJpL-odYmRudX4E0t42dExLf_q1rpRfvTcdFSwfsJ7FmQcOtlc340WUgr4BHJfwrNIE4i-TFqrB4zSQJVKHlBfLeGKiYZQPD7igw" }
};
string tokenSigned = JWT.Encode(payload, rsaCrypto, JwsAlgorithm.RS256, headers);
I get a token (I think is signed) but if I put this token in this web site https://jwt.io/, I get an error "Invalid Signature".
Also I would like to encrypt this token with the public key. So I do this:
// Another public key to encrypt
headers = new Dictionary<string, object>()
{
{ "kty", "RSA" },
{ "e", "AQAB" },
{ "use", "enc"},
{ "alg", "RS256" },
{ "n", "ldMvqNDlz8-ABqEhqjtT0qvjKKbJMQ4J6GEi-7QrY-EUtyjCE7cOriHrYmbjt3o3zXwUTyOp0-twnF5j1HXFwVk7_XNsZz7LUmGNtmnqgB2iw2xhS7LAicN0RRgIbxWRDLOaaZ-49QumX6_r_jLNtIspKiFiuUNf2s0ipeAjWBFquiiqTMBd98z3pS-vC5y0CfzPbTSLSinikrHkIW2uO4FNHWZpoo8npn7vwWtAJjknWhaFi2s9P5kzUk4Mpbdx4DxUJ9ZvUi9SmdvH2vUzwGe0lxyvlw0DAMMWAT9TmsiKzBeXTY6rQ1-2Edn4F9S5kkPNOh1NqJoebz50-Bpl6w" }
};
string tokenEncrypted = JWT.Encode(payload, tokenSigned , JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM, extraHeaders: headers);
But I always get the error "RsaKeyManagement alg expects key to be of RSACryptoServiceProvider type.". I've already search about this error but I don't have anything clear.
Please, anyone can help me? I'm not sure if I'm on the right way.
I use jose-jwt for .net because I've read that the library System.IdentityModel.Tokens.Jwt can't encrypt.
Thank you.
Regards.
In this case I had a jwk and I wanted to sign it with my private key and encrypt it with a customer public key with c# jose-jwt library. This jwk is the same all the time, so I only need to generate it once. To do that, you need to create an RSA object and then use the Encode method of the library to sign and encrypt.
A jwk has severals parameters:
JSON Web Key (JWK): A JSON object that represents a cryptographic key.
The members of the object represent properties of the key, including
its value.
These are some of the parameters:
p = 'RSA secret prime';
kty = 'Key Type';
q = 'RSA secret prime';
d = 'RSA secret exponent';
e = 'RSA public exponent';
kid = 'Key ID';
n = 'RSA public modulus';
use = 'Public Key Use';
alg = 'Algorithm'
But in my case I only had some of them: d, e, n, kty, use, kid, alg. The problem is that if you have e and d parameters, you also need p and q, because you can't construct private key with .NET without primes (P and Q).
The solution was to part the problem in two parts:
JAVA part: create a complete jwk with the Nimbus JOSE+JWT JAVA library:
C# parts:
Use the previous jwk to create an RSA object in C# with c# jose-jwt library. Like this:
var js = new JavaScriptSerializer();
// json is the result returned by java
var jwk = js.Deserialize<IDictionary<string, string>>(json);
byte[] p = Base64Url.Decode(jwk["p"]);
byte[] q = Base64Url.Decode(jwk["q"]);
byte[] d = Base64Url.Decode(jwk["d"]);
byte[] e = Base64Url.Decode(jwk["e"]);
byte[] qi = Base64Url.Decode(jwk["qi"]);
byte[] dq = Base64Url.Decode(jwk["dq"]);
byte[] dp = Base64Url.Decode(jwk["dp"]);
byte[] n = Base64Url.Decode(jwk["n"]);
RSA key = RSA.Create();
RSAParameters keyParams = new RSAParameters();
keyParams.P = p;
keyParams.Q = q;
keyParams.D = d;
keyParams.Exponent = e;
keyParams.InverseQ = qi;
keyParams.DP = dp;
keyParams.DQ = dq;
keyParams.Modulus = n;
key.ImportParameters(keyParams);
Once you have an RSA object, you can sign it:
var payload = new Dictionary<string, object>()
{
{"user", USER },
{"password", PASSWORD }
};
string tokenSigned = JWT.Encode(payload, key, JwsAlgorithm.RS256);
You can find the original solution in the library author web page.
Regards.
Related
I'm trying to generate a jwt token to connect to AppStore API. I'm using the jwt-dotnet library to do this.
Apple requires ES256 to be used and the jwt-dotnet is asking for a public key to do the job. I only downloaded a private key from AppStore. How do I handle this?
Here's my code:
public static string GenerateAppStoreJwtToken()
{
var header = new Dictionary<string, object>()
{
{ "kid", "MY_VALUE" },
{ "typ", "JWT" }
};
var scope = new string[1] { "GET /v1/apps?filter[platform]=IOS" };
var payload = new Dictionary<string, object>
{
{ "iss", "MY_VALUE" },
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "exp", DateTimeOffset.UtcNow.AddMinutes(20).ToUnixTimeSeconds() },
{ "aud", "appstoreconnect-v1" },
{ "scope", scope }
};
IJwtAlgorithm algorithm = new ES256Algorithm(???); // What am I going to use here?
IJsonSerializer serializer = new JsonNetSerializer();
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();
IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder);
var token = encoder.Encode(header, payload, privateKey);
return token;
}
For anyone, like me, who usew JWT-dotnet elsewhere so doesn't want to use a different JWT package, this worked:
Converted the apple private key by removing the header and footer ("-----BEGIN PRIVATE KEY-----" etc) and removing the end of line characters to make a single string for easier storage.
Convert from Base64 and store in a ReadOnlySpan
ReadOnlySpan<byte> keyAsSpan = Convert.FromBase64String(key);
var prvKey = ECDsa.Create();
prvKey.ImportPkcs8PrivateKey(keyAsSpan,out var read);
Create the algorithm. A blank ECDsa instance is needed to prevent an NullException but it is not needed just for signing the token, only verifying which isn't necessary.
IJwtAlgorithm algorithm = new ES256Algorithm(ECDsa.Create(), prvKey)
I was able to receive a reply token from apple using this method.
Here's the final solution that worked for me. I ended up switching to jose-jwt but I'm pretty sure you can handle the same thing with jwt-dotnet. I just found working with jose-jwt a bit easier. Here's the link to jose-jwt: https://github.com/dvsekhvalnov/jose-jwt
And here's the final code. Please note that I did indeed use the private key I find in the p8 file and didn't have to convert anything. So the privateKey parameter I'm passing to the GenerateAppStoreJwtToken() function comes directly from the p8 file.
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using Jose;
public static string GenerateAppStoreJwtToken(string privateKey)
{
var header = new Dictionary<string, object>()
{
{ "alg", "ES256" },
{ "kid", "MY_VALUE" },
{ "typ", "JWT" }
};
var scope = new string[1] { "GET /v1/apps?filter[platform]=IOS" };
var payload = new Dictionary<string, object>
{
{ "iss", "MY_VALUE" },
{ "iat", DateTimeOffset.UtcNow.ToUnixTimeSeconds() },
{ "exp", DateTimeOffset.UtcNow.AddMinutes(15).ToUnixTimeSeconds() },
{ "aud", "appstoreconnect-v1" },
{ "scope", scope }
};
CngKey key = CngKey.Import(Convert.FromBase64String(privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
string token = JWT.Encode(payload, key, JwsAlgorithm.ES256, header);
return token;
}
I'm trying to encrypt a license file in PHP and decrypt it for verification in an application in C# Sharp but I'm stuck, tried different examples but they either don't work or require a 1MB library
Generating a key pair via:
$dn = array(
"countryName" => "UA",
"stateOrProvinceName" => "test",
"localityName" => "test1",
"organizationName" => "test2",
"organizationalUnitName" => "test3",
"commonName" => "test4",
"emailAddress" => "test5#gmail.com"
);
$privkey = openssl_pkey_new(array(
"private_key_bits" => 4096,
"private_key_type" => OPENSSL_KEYTYPE_RSA,
));
// Create CSR
$csr = openssl_csr_new($dn, $privkey, array('digest_alg' => 'sha512'));
// Create a self-signed certificate with a lifetime of 365 days
$x509 = openssl_csr_sign($csr, null, $privkey, $days=365, array('digest_alg' => 'sha512'));
// Save in the file
$filePrivKey = 'private.key';
openssl_pkey_export_to_file($privkey, $filePrivKey);
// CSR - Export to file
$csrFile = 'cert.crt';
openssl_csr_export_to_file($csr, $csrFile);
//$x509csrFile crt
$x509csrFile = 'x509cert.crt';
openssl_x509_export_to_file($x509 ,$x509csrFile);
// Publick key
$pub_key = openssl_pkey_get_public(file_get_contents('x509cert.crt'));
$keyData = openssl_pkey_get_details($pub_key);
file_put_contents('key.pub', $keyData['key']);
I encrypt in PHP using a private key
// I’ll get the key to the file
$pathPriv = 'private.key';
$PrivKey = file_get_contents($pathPriv);
$LicenseKey = '05.05.2020 | example#gmail.com | Nikname';
openssl_private_encrypt($LicenseKey, $encrypted, $PrivKey);
$message = base64_encode($encrypted);
DecryptKey($message);
Trying to decrypt in C#.NET Framework
public MainWindow()
{
InitializeComponent();
string pubkey = System.IO.File.ReadAllText(#"publicKey.xml");
string message = "bPqM8JCgcGcBhWcJAxZyt6bqngL1POdQuehtWjsk3zE2m6YekpZdRu+c7B9lcLVLFqZ9K88uSC6o/7kUCBDDsxsZH8pvX7Yn11YSfUOgjfJJlThipibbmJl7KryQdYUxD8YEvUf0x2Q2ZlMaeMbKVk9IOSo87LKvSV5US7QmEz23ZiHiwQXNbI+txdwTHN8jyH2Byy7uHDz1BYwgpkmUk00BoXxO4kkz8P5Xgq5OHqisr2j/RvO2MkLHusOyAJYoXlyq/Fqn+EzFf4IPsdd/h5LQosd+Pyha9vR4RXJN+RT87KSXx0ZZuvQqUfYm3U6QzWzxBlPk1gOZ0nG2BWDcK9cJJWbKPn65K6c+U5109n7FRLQE7nnVPS9zU7D9sKm0+rOUGh0wDgClxfMsspESIpanuc/eMUZvIPZ98omNFny+r8Jf2aW4fcm4iydUMk3uRBIS7j1NVd4C5pR/vHTqSVcZDxiMRsLcAZ7PFwmjPik76eOmcxssiMS9CavOKe2h85uFhEfsaMCFim1lJ5rtznqc60l1iGa1/Pq3r0TdG0/RfIUOkZxr46HIwwXd6rfzGu2zwMMxl4Jfv6iWvpQk+DNmXv0Yw1w/Q9PF0i51mEen+z5nQSRJQsl8cmMBukOGTJ+X3ZELe848kni1luE20Icz/DSwv26QVVY6dZqiGV4=";
byte[] ciphertext = Convert.FromBase64String(message);
string decodedString = Encoding.UTF8.GetString(ciphertext);
}
public string decrypt(string elementToDesencrypt)
{
// string pem = System.IO.File.ReadAllText(pathPublicKey);
string pem = System.IO.File.ReadAllText(#"publicKey.xml");
byte[] Buffer = GetPublicKey();
System.Security.Cryptography.RSACryptoServiceProvider rsa = new System.Security.Cryptography.RSACryptoServiceProvider();
System.Security.Cryptography.RSAParameters rsaParam = rsa.ExportParameters(false);
rsaParam.Modulus = Buffer;
rsa.ImportParameters(rsaParam);
byte[] encryptedMessageByte = rsa.Decrypt(Convert.FromBase64String(elementToDesencrypt), false);
return Convert.ToBase64String(encryptedMessageByte);
}
I am getting an exception "The data size to be decrypted exceeds the maximum for this module, 512 bytes"
I have a JWT security token which I need to verify via jwks endpoint.
Data in jwks looks like:
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"alg": "RS256",
"n": "......",
"kid": "2132132-b1e6-47e7-a30f-1831942f74bd"
},
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"alg": "RS256",
"n": "......",
"kid": "tsp-app-a"
},
{
"kty": "RSA",
"e": "AQAB",
"use": "sig",
"alg": "RS256",
"n": ".....",
"kid": "tsp-app-b"
}
]
}
I have tried one third party api but it looks like it is dependent on x5c key which isn't present in my case.
My code is:
public static bool Validate(JwtSecurityToken jsonToken)
{
bool result = false;
try
{
var headers = Jose.JWT.Headers<JWTHeader>(jsonToken.RawData);
var payload = Jose.JWT.Payload<JWTPayload>(jsonToken.RawData);
string jwk = "";
using (HttpClient cli = new HttpClient())
{
jwk = cli.GetStringAsync(MyclientUrlforWellknownjson).Result;
}
var jwkinfo = JsonSerializer.Deserialize<JWKS>(jwk);
//Find right key. Match kid and alg, (To be changed later. It is possible that there are multiple x5c elements in key)
var jwkkey = (from item in jwkinfo.keys where item.kid == headers.kid && item.alg == headers.alg select item).SingleOrDefault();
//If key was found then load its public key
System.Security.Cryptography.X509Certificates.X509Certificate2 cert = null;
if (jwkkey != null)
{
//Get public key from well known information
byte[] key = System.Text.Encoding.ASCII.GetBytes(jwkkey.x5c[0]); //??todo
//Create cert
cert = new System.Security.Cryptography.X509Certificates.X509Certificate2(key);
}
var o = Jose.JWT.Decode(jsonToken.RawData, cert.PublicKey.Key);
}
catch (Exception ex)
{
}
return result;
}
How can I validate a JWT via jwks without x5c?
Using x5c is just one way, but you can also retrieve the public key with the parameters e (public exponent) and n (modulus), which is also documented on the jose-jwt github page:
//If kid was found then load public key
if (jwkkey != null)
{
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.ImportParameters(new RSAParameters
{
Modulus = Base64Url.Decode(jwkkey.n),
Exponent = Base64Url.Decode(jwkkey.e)
});
}
// get the public key in PEM format, e.g. to use it on jwt.io
var pubkey = Convert.ToBase64String(key.ExportSubjectPublicKeyInfo());
const string pemHeader = "-----BEGIN PUBLIC KEY-----";
const string pemFooter = "-----END PUBLIC KEY-----";
var publicKeyPem = pemHeader + Environment.NewLine + pubkey + Environment.NewLine + pemFooter;
var o = Jose.JWT.Decode(jsonToken.RawData, key);
You can also export the public key in PEM format again as shown in the code above, which will look like this:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgIdJV4qWKyt3wkS66yBG5Ii9ew+eofuPU49TjlRIU5Iu5jX2mRMoHdcI7V78iKYSQHKYxz17cqzQyERxKnEiDgy/gwouStRgvPdm3H4rq//7p0t15SunsG2T1rEVf0sZEDnQ5qRkm7iqs6ZG1NqqIUtnOTd1Pd1MhbEqeENFtaPHvN37eZL82WmsQlJviFH4I9iZQVR/QT4GREQlRro8IjJTaloUyeDQTOQ+4ll1+4+g/ug2tZ+s9xleLzl5L9ZKSVJFhtMLn8WGaVldagarwa7kMLfuiVe8B5Lr7poQa4NCAR54ECPWoOHrABdPZKrkkxjVypTXUzL5cPzmzFC2xwIDAQAB
-----END PUBLIC KEY-----
and later use that key to manually verify your token on https://jwt.io
(key export corrected after a hint from #Topaco)
FWIW, I managed to validate a JWT without x5c (which is the X.509 certificate chain) but with only e and n (which are respectively exponent and modulus, see RFC 7517) using native Microsoft.AspNetCore.Authentication.JwtBearer NuGet package.
We have to instanciate a RsaSecurityKey instead of a X509SecurityKey:
.AddJwtBearer(options =>
{
var issuer = Configuration["Issuer"];
var rsaParams = new RSAParameters
{
Exponent = Base64Url.Decode(Configuration["Exponent"]),
Modulus = Base64Url.Decode(Configuration["Modulus"])
};
var issuerSigningKey = new RsaSecurityKey(rsaParams);
options.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = issuerSigningKey,
ValidIssuer = issuer
};
options.Validate();
});
I think this question is not a dupe, so I will try to explain my situation.
I'm testing JWT, more specifically JOSE-JWT lib from Github, and well, I'm having troubles.
I'm generating a private-public key pair and sending to the client the public key, using PHP and phpseclib. Everything is correct as you can see there. My client is receiving the JSON and converting it to a object and extracting it to a string using JSON.NET.
I'm using BouncyCastle and an answer from Stackoverflow with a little modifications to read directly from a string instead from a File.
public static RSACryptoServiceProvider GetRSAProviderFromPemFile(string pemfile)
{
return GetRSAProviderFromPemString(File.ReadAllText(pemfile).Trim());
}
public static RSACryptoServiceProvider GetRSAProviderFromPemString(string pemstr)
{
bool isPrivateKeyFile = true;
if (pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
isPrivateKeyFile = false;
byte[] pemkey;
if (isPrivateKeyFile)
pemkey = DecodeOpenSSLPrivateKey(pemstr);
else
pemkey = DecodeOpenSSLPublicKey(pemstr);
if (pemkey == null)
return null;
if (isPrivateKeyFile)
return DecodeRSAPrivateKey(pemkey);
else
return DecodeX509PublicKey(pemkey);
}
And both of them are giving me problems, with the answer and using docs from Jose repo:
var payload1 = new Dictionary<string, object>()
{
{ "sub", "mr.x#contoso.com" },
{ "exp", 1300819380 }
};
Console.WriteLine("Jose says: {0}", JWT.Encode(payload1, pubkey, JwsAlgorithm.RS256));
Exception:
English equivalent: http://unlocalize.com/es/74799_Keyset-does-not-exist.html
And with Bouncy Castle:
var claims = new List<Claim>();
claims.Add(new Claim("claim1", "value1"));
claims.Add(new Claim("claim2", "value2"));
claims.Add(new Claim("claim3", "value3"));
Console.WriteLine("Bouncy Castle says: {0}", Helpers.CreateToken(claims, pubkeyStr));
Exception:
CreateToken method extracted from here: https://stackoverflow.com/a/44857593/3286975
I did a little modification to this method:
public static string CreateToken(List<Claim> claims, string privateRsaKey)
{
RSAParameters rsaParams;
using (var tr = new StringReader(privateRsaKey))
{
var pemReader = new PemReader(tr);
var keyPair = pemReader.ReadObject() as AsymmetricCipherKeyPair;
if (keyPair == null)
{
throw new Exception("Could not read RSA private key");
}
//var privateRsaParams = keyPair.Private as RsaPrivateCrtKeyParameters;
rsaParams = DotNetUtilities.ToRSAParameters(keyPair.Public as RsaKeyParameters); //DotNetUtilities.ToRSAParameters(privateRsaParams);
}
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(rsaParams);
Dictionary<string, object> payload = claims.ToDictionary(k => k.Type, v => (object)v.Value);
return Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
}
}
In both cases is like the encrypter methods are looking for the private key (in the client client????)... So, My question is why this examples are using private keys in the client-side, if the Wikipedia says this:
Source: https://en.wikipedia.org/wiki/Public-key_cryptography
And in several cases I found what I think is right:
https://connect2id.com/products/nimbus-jose-jwt/examples/jwt-with-rsa-encryption
In this Java example, this uses public key to encrypt data, not private one.
I don't know why C# examples are using private keys in the client-side, this is ilogical, can somebody explain me why, and how can I solve this problems?
I have found an answer for one of the two problems I have, and is that I haven't readed completely the JOSE-JWT repo, it says:
var payload = new Dictionary<string, object>()
{
{ "sub", "mr.x#contoso.com" },
{ "exp", 1300819380 }
};
var publicKey=... //Load it from there you need
string token = Jose.JWT.Encode(payload, publicKey, JweAlgorithm.RSA_OAEP, JweEncryption.A256GCM);
I have realized tha Bouncy Castle is only a API to manipulate public-private keys, the encryptation-decrypting work is done by JOSE-JWT. So, my question is solved.
I create a keypair in serverside with openssl.
string serverPublicKey;
string serverPrivateKey;
using (var serverRsa = new OpenSSL.Crypto.RSA())
{
serverRsa.GenerateKeys(512, 65537, null, null);
serverPublicKey = serverRsa.PublicKeyAsPEM;
serverPrivateKey = serverRsa.PrivateKeyAsPEM;
}
On client side (Xamarin) I would like to import the public key with PCLCrypto, but I get always the error "Unexpected format or unsupported key", see code below.
var publicServerKey = _serverKeys.RsaKey;
var keyBuffer = Encoding.UTF8.GetBytes(publicServerKey);
var asym = WinRTCrypto.AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithm.RsaPkcs1);
var key = asym.ImportPublicKey(keyBuffer, CryptographicPublicKeyBlobType.Pkcs1RsaPublicKey);
How can I Import teh public key, to encrypt data?