2

I need to logon to a Zello API using a JWT authorization token. Zello has provided a private and public RSA256 key, along with an Issuer. They generate a development token, valid for 30 days, which I've used successfully to connect to their API. Now I need to generate my own JWT token. Their documentation (https://github.com/zelloptt/zello-channel-api/tree/master/auth) explains that I must create a claim using the issuer and expiry - that's no problem. Then I'm to use the RSA256 private key string, along with the claim, to generate my JWT token. Their examples in Go, PHP and JS look very simple. e.g. Here's their JS example: https://github.com/zelloptt/zello-channel-api/blob/master/auth/js/tokenmanager.js I've spent quite a few days searching stackoverflow for how to do this in C#. And there are many good answers in this area, but not when it comes to RSA and to solutions without a pem file. I have installed a number of NuGet packages to try to assist me, including: Portable.BouncyCastle, jose-jwt, Newtonsoft.Json and Microsoft.IdentityModel.JsonWebTokens. My roadblock is how to generate the security/authorization JWT token from my claim and private key string (not in a pem file). Note that since this is RSA, I need an Asymmetric Security Key, not a symmetric one. I'm brand new to this and I think I'm likely to be missing something basic.

Here is my code, which I thought was finally my answer (I think I got this in-part from another stackoverflow answer?) However, it throws the exception "Could not read RSA private key". I think this may be because it's expecting both the public and private key to be present - which I could do - but I don't know how to format? I've also wondered if I may be forced to create a PEM file using the public and private key Zello have given me, so I can then read it in and just grab the private key for the token generation. Though I'm not sure how to create and format a PEM file? The private and public keys are just strings Zello has given me, that look like: -----BEGIN PRIVATE KEY-----MIIBvQIBADANBgkqhkiG9w0BAQE...-----END PRIVATE KEY----- -----BEGIN PUBLIC KEY-----.......-----END PUBLIC KEY-----

I think my main hurdle is trying to understand and work with one of the libraries that will do what I need. It seems like something simple has become overly complex. Any advice or suggestions would be greatly appreciated.

public static string CreateJWTSecurityToken(Company company)
{

    DateTime parsedDate = DateTime.UtcNow.AddMinutes(5);
    var claims = new List<System.Security.Claims.Claim>
    {
        new System.Security.Claims.Claim("iss", company.ZelloIssuer),
        new System.Security.Claims.Claim("exp", TimeUtils.ToUnixTime(parsedDate).ToString()),
    };

    RSAParameters rsaParams;
    using (var tr = new StringReader(company.ZelloPrivateKey))
    {
        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(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);
    }
}

UPDATE: I've found part of my answer here: https://mac-blog.org.ua/dotnet-core-firebase-jwt/

This is the snippet which helps me work with just the private key as a string:

using (StringReader sr = new StringReader(company.ZelloPrivateKey))
{
    PemReader pr = new PemReader(sr);
    key = (RsaPrivateCrtKeyParameters)pr.ReadObject(); //no more keyPair because I just have the privKey only
}

My next issue is that the private key returns NULL when read by PemReader. So clearly it doesn't like the format or content of my private key. I've tried with both the header and footer included and excluded, and also tried removing the cr/lfs, as well as leaving them in. No luck yet, but I must be close. The length of my private key, without header or footer is 1674.

UPDATE: By analyzing the Go code file Zello provided as an example, I've discovered that they are using PKCS #8, which is different (but similar) to OpenSSL's 'legacy PEM' encryption. So this may explain why PemReader will not read my private key. Now I'm looking into how to read it in, some other way. I'm looking at this link: How read a PKCS8 encrypted Private key which is also encoded in DER with bouncycastle?

UPDATE:

My final method, used to produce a JWT auth token, is below. However, the token is not accepted by Zello when I attempt logon, so I still have something not quite right. Does anyone know what I'm doing wrong?

public static string CreateToken(Company company)
        {
            DateTime parsedDate = DateTime.UtcNow.AddMinutes(5);
            var claims = new List<System.Security.Claims.Claim>
            {
                new System.Security.Claims.Claim("iss", company.ZelloIssuer),
                new System.Security.Claims.Claim("exp", TimeUtils.ToUnixTime(parsedDate).ToString()),
            };

            var jwt = string.Empty;
            var source = System.Convert.FromBase64String(company.ZelloPrivateKey);

            using (RSA rsa = RSA.Create())
            {
                rsa.ImportPkcs8PrivateKey(source, out int bytesRead);

                Dictionary<string, object> payload = claims.ToDictionary(k => k.Type, v => (object)v.Value);
                jwt = Jose.JWT.Encode(payload, rsa, Jose.JwsAlgorithm.RS256);
            }
            return jwt;
        }
Dave
  • 21
  • 2
  • This may be a strange question but is your system time current? If the exp is out of an expected range the request may not be authorized. – Ross Bush Jun 17 '20 at 01:39
  • Hmm thanks for the comment. My system time is correct but that gave me another idea. The Go sample of this working, had a 60 second expiry, So I reduced my expiry down to 1 minute in-case Zello don't accept tokens that last for 5 minutes. However, still no luck - the token is still "not authorised" (API return). I suspect I have not signed this correctly and so the token is wrong. – Dave Jun 17 '20 at 02:13

0 Answers0