Creating PublicKey using Oid/ASNEncodeData throws CryptographyException
Asked Answered
T

1

3

I'm trying to create a PublicKey instance using an Oid and RSA public Key but I'm getting a CryptographyException with "ASN1 bad tag value met". I'm following the answer found here to eventually create a RSACryptoServiceProvider.

Here's my code:

string pem = @"-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkxqnUqh5WYis/Q+sQc5h
O9i5aX7XvVEVdrhrnFcbwSb1/GyQWPvn1ZydQB88zW9CnNFq08QRg+IYaBYdqs12
EbxkET20eWY7xvI8kBICPxOdYAHBb0JWpdK4GjSCSxCFrJIXXmHtnRqj6PmSoPrb
uFdC5MTFXfFwphgZi+Ae5MM2nxDu0P/UT8W1VMNVYRkC0dldo+csK1p9NLKga64z
MiNop9nM3meSHpOt+P65l1B+e5EeXM+qzrIeJH4ul95HJdKkPypDM18y4FkFA73S
r6vHYQvQjmBiGy0op1Qs7t+8UkpOX41j28IeiE2yyG7S6/k8Qcu0yv1uaFn3a9VJ
jwIDAQAB
-----END PUBLIC KEY-----";

var rsaPublicKeyStr = pem.Replace(
    "-----BEGIN PUBLIC KEY-----\r\n", "").Replace("\r\n-----END PUBLIC KEY-----", "");
var rsaPublicKey = Convert.FromBase64String(rsaPublicKeyStr);
Oid oid = new Oid("RSA");
AsnEncodedData keyValue = new AsnEncodedData(rsaPublicKey);           
AsnEncodedData keyParam = new AsnEncodedData(new byte[] { 05, 00 });    // ASN.1 code for NULL
PublicKey pubKeyRdr = new PublicKey(oid, keyParam, keyValue);

try
{
    var rsa = (RSACryptoServiceProvider)pubKeyRdr.Key;
}
catch (CryptographicException ce)
{
    Console.WriteLine(ce);
}     

And the output:

System.Security.Cryptography.CryptographicException: ASN1 bad tag value met.

   at System.Security.Cryptography.X509Certificates.PublicKey.DecodePublicKeyObject(UInt32 aiPubKey, Byte[] encodedKeyValue, Byte[] encodedParameters, Byte[]& decodedData)
   at System.Security.Cryptography.X509Certificates.PublicKey.get_CspBlobData()
   at System.Security.Cryptography.X509Certificates.PublicKey.get_Key()
   at License.Crypto.doCryptoStuff()  
Tarpan answered 19/11, 2019 at 18:46 Comment(3)
You need to extract a modulus and public exponent from raw data. Your encoded value includes full public key with object identifier. This part should be stripped from encoded value in order to pass to constructor of PublicKey class.Halfprice
@Crypt32: I didn't even know .NET had ASN.1 classes. I took a look at them and they don't seem to have any useful methods for parsing.Galway
There are no built-in classes to work with ASN.1. You have to use 3rd party libraries to parse ASN data.Halfprice
G
0

After I decoded your public key with the parser here, I can see that is a fully formed ANS.1 key. The solution in the post you linked to there works...but for an incomplete key. An important piece of info that I'm ashamed to say that I omitted from my posted answer - I'll update with the relevant info after this.

So. The short version is that I wasn't ever able to correctly decode a fully formed public key - I had to extract by bytes. We're still awaiting MS's ASN parsing logic to be made public (it looks like it got paused while 3.0 rolled out). In my situation, I had control over how the public key was exported so I was able to control how the public key blob would be created in the PEM.

If this is the case for yourself, load the public + private keypair into an RSACryptoServiceProvider and then export it like so;

var cert = new X509Certificate2(keypairBytes, password,
                                X509KeyStorageFlags.Exportable 
                                | X509KeyStorageFlags.MachineKeySet);
var partialAsnBlockWithPublicKey = cert.GetPublicKey();

// export bytes to PEM format
var base64Encoded = Convert.ToBase64String(partialAsnBlockWithPublicKey, Base64FormattingOptions.InsertLineBreaks);
var pemHeader = "-----BEGIN PUBLIC KEY-----";
var pemFooter = "-----END PUBLIC KEY-----";
var pemFull = string.Format("{0}\r\n{1}\r\n{2}", pemHeader, base64Encoded, pemFooter);

If you create a PEM from this key, you'll be able to load it back in using the method described in the linked question. Why is this different? The call to cert.GetPublicKey() will actually return the ASN.1 block structure;

SEQUENCE(2 elem)
  INTEGER (2048 bit)
  INTEGER 65537

This is actually an incomplete DER blob but one which .NET can decode (full ASN.1 parsing and generation is not supported by .NET at time of writing - https://github.com/dotnet/designs/issues/11).

A correct DER (ASN.1) encoded public key bytes has the following structure;

SEQUENCE(2 elem)
  SEQUENCE(2 elem)
     OBJECT IDENTIFIER   "1.2.840.113549.1.1.1" - rsaEncryption(PKCS #1)
     NULL
BIT STRING(1 elem)
  SEQUENCE(2 elem)
    INTEGER (2048 bit)
    INTEGER 65537

OK, so the above gets you a public key (kind of) that you can load. How to bring it back in? A copy/paste from the linked answer (it assumes you've got the file bytes again);

const string rsaOid = "1.2.840.113549.1.1.1";   // found under System.Security.Cryptography.CngLightup.RsaOid but it's marked as private
Oid oid = new Oid(rsaOid);
AsnEncodedData keyValue = new AsnEncodedData(publicKeyBytes);           // see question
AsnEncodedData keyParam = new AsnEncodedData(new byte[] { 05, 00 });    // ASN.1 code for NULL
PublicKey pubKeyRdr = new PublicKey(oid, keyParam, keyValue);
var rsaCryptoServiceProvider = (RSACryptoServiceProvider)pubKeyRdr.Key;

The above should get you into a working state.

Getup answered 20/11, 2019 at 20:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.