2

Purpose : Generate a ES256 signed JWT using jose-jwt

Steps:

1.Generate a private key and certificate using openssl:

openssl ecparam -name prime256v1 -genkey > privateKey.pem
openssl req -new -key privateKey.pem -x509 -nodes -days 365 -out public.cer

2.Token generation:

var payload = new Dictionary<string, object>()
{
   { "sub", "mr.x@contoso.com" },
   { "exp", 1300819380   }
};
var certificate = X509Certificate.CreateFromCertFile("public.cer");
byte[] publicKey = certificate.GetPublicKey(); //public key has 65 bytes

//Below step is throwing an error:
var cng = CngKey.Import(publicKey, CngKeyBlobFormat.EccPublicBlob);
var token = JWT.Encode(claims, cng, JwsAlgorithm.ES256);

CngKey.Import() is throwing "The parameter is incorrect" error while trying to generate a CngKey required for the Jose.JWT.Encode function. Not sure what step I am missing. Thanks.

Shawinder Sekhon
  • 1,569
  • 12
  • 23

2 Answers2

2

The "ECCPUBLICBLOB" format isn't the same as the "public key" field from the certificate.

The format of the ECCPUBLICBLOB is explained in another question's answer, but here's a quick summary:

UINT32 Magic
UINT32 cbKey
<cbKey bytes of public key>

The value for Magic will depend on which curve and algorithm you're trying to import (some hints at https://referencesource.microsoft.com/#system.core/System/Security/Cryptography/BCryptNative.cs,fde0749a0a5f70d8,references).

cbKey is how many bytes are in the public key.

The public key bytes will be a bit different than what you get from GetPublicKey(). It'll be just the curve X coordinate, which will be (for NIST P-256) bytes 1..33 (the first byte of GetPublicKey() will be 0x04, which says that the payload is uncompressed, then 32 bytes of the X coordinate, then 32 bytes of the Y coordinate).

IEnumerable<byte> blobBytes = BitConverter.GetBytes(0x31534345);
blobBytes = blobBytes.Append(BitConverter.GetBytes(32));
blobBytes = blobBytes.Append(cert.GetPublicKey().Skip(1).Take(32));

byte[] eccblob = blobBytes.ToArray();

System.Linq extension methods used for brevity.

Though, if you just need an object instance, cert.GetECDsaPublicKey() should do the right thing for you (each call to that returns a new instance, so manage the lifetime appropriately)

Community
  • 1
  • 1
bartonjs
  • 30,352
  • 2
  • 71
  • 111
  • Thanks for your answer. It was partially close to what I was looking for. Also `cert.GetECDsaPublicKey()` is not available in .NET 4.5 – Shawinder Sekhon Mar 21 '17 at 16:26
1

I was able to make the CngKey import working with help from the following post.

Now Jose.JWT.Encode() was throwing "Unable to sign" error at the following line:

return JWT.Encode(claims, cng, JwsAlgorithm.ES256)

And I ended up writing my own implementation of private key signing using .NET 4.6 GetECDsaPrivateKey().

You can see my final solution on the following post

Public key needs to be modified by discarding the first byte, leaving 64 bytes, then prefix with 4 bytes for curve and 4 bytes for key length. Here is the complete solution:

var payload = new Dictionary<string, object>()
{
   { "sub", "mr.x@contoso.com" },
   { "exp", 1300819380     }
};
var certificate = X509Certificate.CreateFromCertFile("public.cer");
byte[] publicKey = certificate.GetPublicKey(); //public key has 65 bytes

//Discard the first byte (it is always 0X04 for ECDSA public key)
publicKey = publicKey.Skip(1).ToArray();:

//Generate 4 bytes for curve and 4 bytes for key length [ 69(E), 67(C), 83(S), 49(1), 32(Key length), 0, 0, 0 ]
byte[] x = { 69, 67, 83, 49, 32, 0, 0, 0 };    

//Prefix above generated array to existing public key array
publicKey = x.Concat(publicKey).ToArray();

var cng = CngKey.Import(publicKey, CngKeyBlobFormat.EccPublicBlob); //This works
return JWT.Encode(claims, cng, JwsAlgorithm.ES256); //Fixed, see my final solution link above
Community
  • 1
  • 1
Shawinder Sekhon
  • 1,569
  • 12
  • 23