How to Validate Json objects in a file from asp.net core application C#

583 views Asked by At

Problem: A nodejs application creates a json file which is a license,and an asp.net core application will need to validate the info with public key. The node application uses RSA sha256 to sign it with private key, and .net core app will use public key to validate. THANK YOU. The json file includes the following information:

    {
    "info":{
    "validto":1514678400000,"validfrom":1498608000000,"nodes":10,"email":"[email protected]","name":"TAC Account","phone":"3333333333333","address":"something"
    },

"signature":
    {
    "data":[159,1,86,24,244,199,39,40,251,195,175,80,18,33,232,63,178,213,205,129,150,58,243,154,138,168,61,197,222,50,222,80,33,82,135,243,121,250,176,33,9,25,167,177,235,193,246,236,235,19,187,118,57,64,38,143,42,35,12,207,133,33,166,201,34,76,40,87,242,163,141,14,218,198,247,91,191,132,86,162,194,143,177,147,65,171,160,41,2,244,130,53,82,178,72,39,247,45,242,139,243,59,79,196,12,70,14,202,246,48,231,66,38,94,235,237,204,77,40,252,216,63,41,204,210,228,93,16,201,4,123,104,119,251,56,160,9,105,180,217,129,113,19,166,89,199,203,129,47,218,20,131,94,87,251,193,177,111,151,72,187,79,0,63,0,168,36,147,155,47,5,123,176,203,189,111,199,165,90,12,122,2,148,62,107,115,132,183,76,147,7,238,154,174,198,226,170,204,193,18,197,30,191,189,133,134,33,33,159,14,90,153,219,226,184,149,19,179,210,171,136,39,144,178,229,236,126,11,252,80,65,83,181,117,26,37,231,92,151,110,33,93,239,243,129,58,201,214,231,248,151,23,19,170,0,19],
    "type":"Buffer"
    }

    }

We have tried many things at asp.net core side but no success, the public key given has some unusual characters like \n which not seen typically. Following is one of different things we tried:

using System ;
using System.IO ;
using System.Text ;
using Org.BouncyCastle.Crypto ;
using Org.BouncyCastle.Crypto.Parameters ;
using Org.BouncyCastle.OpenSsl ;
using Org.BouncyCastle.Security ;

namespace ValidateRsa
{
    internal class Program
    {
        private static readonly string info =
                @"{""validto"":1514678400000,""validfrom"":1498608000000,""nodes"":10,""email"":""[email protected]"",""name"":""TAC Account"",""phone"":""3333333333333"",""address"":""something""}"
            ;
        private static readonly byte[] signature =
        {
            159, 1, 86, 24, 244, 199, 39, 40, 251, 195, 175, 80, 18, 33, 232, 63, 178, 213, 205, 129, 150, 58, 243, 154, 138, 168,
            61, 197, 222, 50, 222, 80, 33, 82, 135, 243, 121, 250, 176, 33, 9, 25, 167, 177, 235, 193, 246, 236, 235, 19, 187, 118,
            57, 64, 38, 143, 42, 35, 12, 207, 133, 33, 166, 201, 34, 76, 40, 87, 242, 163, 141, 14, 218, 198, 247, 91, 191, 132, 86,
            162, 194, 143, 177, 147, 65, 171, 160, 41, 2, 244, 130, 53, 82, 178, 72, 39, 247, 45, 242, 139, 243, 59, 79, 196, 12,
            70, 14, 202, 246, 48, 231, 66, 38, 94, 235, 237, 204, 77, 40, 252, 216, 63, 41, 204, 210, 228, 93, 16, 201, 4, 123, 104,
            119, 251, 56, 160, 9, 105, 180, 217, 129, 113, 19, 166, 89, 199, 203, 129, 47, 218, 20, 131, 94, 87, 251, 193, 177, 111,
            151, 72, 187, 79, 0, 63, 0, 168, 36, 147, 155, 47, 5, 123, 176, 203, 189, 111, 199, 165, 90, 12, 122, 2, 148, 62, 107,
            115, 132, 183, 76, 147, 7, 238, 154, 174, 198, 226, 170, 204, 193, 18, 197, 30, 191, 189, 133, 134, 33, 33, 159, 14, 90,
            153, 219, 226, 184, 149, 19, 179, 210, 171, 136, 39, 144, 178, 229, 236, 126, 11, 252, 80, 65, 83, 181, 117, 26, 37,
            231, 92, 151, 110, 33, 93, 239, 243, 129, 58, 201, 214, 231, 248, 151, 23, 19, 170, 0, 19
        } ;
        private static void Main (string[] args)
        {
            Console.WriteLine ("Setting up validator...") ;
            TextReader text = File.OpenText (@"X:\Scratch\pubkey.asc") ;
            var pemr = new PemReader (text) ;
            object pem = pemr.ReadObject () ;
            text.Close () ;
            var keyParams = (RsaKeyParameters) (AsymmetricKeyParameter) pem ;
            ISigner sig = SignerUtilities.GetSigner ("SHA-256withRSA") ;
            sig.Init (false, keyParams) ;
            byte[] infoBytes = Encoding.ASCII.GetBytes (Program.info) ;
            sig.BlockUpdate (infoBytes, 0, infoBytes.Length) ;
            if (sig.VerifySignature (Program.signature))
                Console.WriteLine ("Verified!") ;
            else
                Console.WriteLine ("Failed!") ;
            Console.ReadLine () ;
        }
    }
}

Public key is in the following format:

 -----BEGIN RSA PUBLIC KEY
-----\nMIIBCgKCAQEAqao1ZkAYKDybHSeoy79ySQDcXODByDRaZKT2nYwT8GrYohBle8phB5LgSoQu\nVD7ErRFGHxutcqPrfL3AuTHg874Kmw6/G+25/FdC9uNJzLtCP+Z5mOrF5HlU8dGOOpTeq4y5\n0EPcj//YuO4kScj0wOOp1HMRwxsdVo\nAZUQwMz5w1QIoGL5CoW7RKiL/oQw0Mh0Ju+9ofVbovSzBTo0r7onqw6M0hOJScV86iQ21Ukl\nup/6CmXCMwcYK1Fr5J6YNbeZoQhkII7VazPMgZetJBCfm+iyBPSPARlf13RLM0cHzwIDAQAB\n-----END RSA PUBLIC KEY-----\n

The code where nodejs app is signing the data:

 const licenseSchema = new Schema({
  info: {
    name: String,
    address: String,
    phone: String,
    email: String,
    nodes: Number,
    validfrom: Schema.Types.Mixed,  // validfrom and validto must be either
    validto: Schema.Types.Mixed,    // valid Date strings or ms since epoch
  },

  signature: {
    type: { type: String, required: true },
    data: { type: Schema.Types.Mixed, required: true },
  },
});

// updates license.signature
licenseSchema.statics.createAndSignLicense = function createAndSignLicense(object, privateKey, cb) {
  let arrayInfo = [];
  arrayInfo = [
    object.name,
    object.address,
    object.phone.toString(),
    object.email,
    object.nodes.toString(),
    object.validfrom,
    object.validto,
  ];
  const sign = crypto.createSign('RSA-SHA256');
  sign.update(JSON.stringify(arrayInfo));
  const sig = sign.sign(privateKey);

  const LicenseModel = this;

  const licenseDoc = new LicenseModel({
    info: object,
    signature: {
      type: 'Buffer',
      data: Array.prototype.slice.call(sig, 0),
    },
  });

  licenseDoc.save((err) => {
    if (err) { return cb(err); }

    return cb(null, licenseDoc);
  });
};

There is another nodejs app which is validating the data successfully, example is below, just in .net things are not coming together:

    const Schema = mongoose.Schema;

export const Errors = {LicenseValidationError: 'LicenseValidationError'};

const LicenseDescription = {
    key: { type: String, unique: true },
    info: {
        name: String,
        address: String,
        phone: String,
        email: String,
        nodes: Number,
        validfrom: Schema.Types.Mixed,  // validfrom and validto must be either
        validto: Schema.Types.Mixed,    // valid Date strings or ms since epoch
    },
    signature: {
        type: { type: String, required: true },
        data: Schema.Types.Mixed
    }
};

// Application state variable representing license validity.
let isLicenseValid = false;

/*
 * uses the utils function with a validator to set the system-wide document
 * license is a file (buffer) or JSON license.
 * This method attempts to validate the input.
 *
 * See: License.statics.ValidateLicense()
 *
 * cb: function (err, license) { ... }
 */
const License = utils.createSchema(LicenseDescription, {
    validator: function (license, cb) {
        validateLicense(license, (err, valid) => {
            if (err) {
                return cb(err);
            }
            isLicenseValid = isLicenseWithinDateRange(license);
            return cb(null, true);
        });
    }
});


// license public key, hardcoded.
// Uses RSA-SHA256
    const pair = () => ({
    public: `-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAqao1ZkAYKDybHSeoy79ySQDcXODByDRaZKT2nYwT8GrYohBle8phB5LgSoQu\nVD7ErRFGHxutcqPrfL3AuTHg874Kmw6/G+25/FdC9uNJzLtCP+Z5mOrF5HlU8dGOOpTeq4y5\n0EPcj//YuO4kScj0wOOp1HMRwxsdVo+G00Q\nAZUQwMz5w1QIoGL5CoW7RKiL/oQw0Mh0Ju+9oV86iQ21Ukl\nup/6CmXCMwcYK17VazPMgZetJBCfm+iyBPSPARlf13RLM0cHzwIDAQAB\n-----END RSA PUBLIC KEY-----\n`
});

/*
 * Verifies the supplied license (must be a valid JSON object) against the
 * public key (hardcoded).
 *
 * Returns true if valid, false otherwise.
 */
const verifySignature = license => {
    winston.info('Verifying license signature:');

    // crypto.verify relies on specific key ordering (undefined behavior, unfortunately).
    // To try to force key order, we construct this object (with explicit toString calls
    // where applicable).
    const infoString = [
        license.info.name,
        license.info.address,
        license.info.phone,
        license.info.email,
        license.info.nodes.toString(),
        new Date(license.info.validfrom).toString(),
        new Date(license.info.validto).toString(),
    ];

    const infoMS = [
        license.info.name,
        license.info.address,
        license.info.phone,
        license.info.email,
        license.info.nodes.toString(),
        license.info.validfrom,
        license.info.validto,
    ];

    const verifyString = crypto.createVerify('RSA-SHA256'), verifyMS = crypto.createVerify('RSA-SHA256'), 
    licenseInfoString = JSON.stringify(infoString), licenseInfoMS = JSON.stringify(infoMS);

    // Currently the signature must be a buffer and the type field is ignored.
    let buf;
    try {
        buf = new Buffer(license.signature.data);
    } catch (parseError) {
        return false;
    }

    verifyString.update(licenseInfoString);
    verifyMS.update(licenseInfoMS);

    const isLicenseValid = verifyString.verify(pair().public, buf) || verifyMS.verify(pair().public, buf);
    winston.info(`The license signature is ${(isLicenseValid) ? 'valid.' : 'invalid.'}`);
    return isLicenseValid;
};
1

There are 1 answers

8
Doug On BEST ANSWER

There's something wrong with your key file I guess?

Your code works fine.

Here's an example of generating the signature in node:

var crypto = require('crypto');
var fs = require('fs');

var privateKey = fs.readFileSync('./junk').toString('utf8');
var publicKey = fs.readFileSync('./junk.pem').toString('utf8');

var sign = crypto.createSign('RSA-SHA256');
sign.update("Test123");
var sig = sign.sign(privateKey);
var bytes = Array.prototype.slice.call(sig, 0);
console.log(JSON.stringify(bytes));

var verify = crypto.createVerify('RSA-SHA256');
verify.update("Test123");
var success = verify.verify(publicKey, new Buffer(sig));

console.log("Did sign? " + success);

and here's an example of verifying it using bouncy castle in C# .net core (I've simplified, but its basically exactly what you're doing):

using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

namespace RsaTest
{
  class Program
  {
    static void Main(string[] args)
    {
      var signature = new byte[]
      {
        193, 185, 118, 187, 54, 28, 71, 173, 185, 255, 213, 61, 254, 51, 86, 168, 224, 11, 113, 23, 81, 123, 89,
        31, 89, 183, 142, 194, 7, 250, 194, 165, 230, 254, 74, 61, 15, 12, 137, 246, 151, 61, 83, 107, 112, 178,
        98, 183, 234, 247, 56, 11, 246, 179, 183, 74, 27, 190, 17, 99, 161, 31, 209, 178, 81, 41, 56, 214, 184, 165,
        232, 20, 125, 155, 25, 102, 104, 193, 1, 101, 143, 209, 192, 145, 47, 215, 190, 95, 196, 164, 69, 203, 206, 69,
        142, 18, 196, 155, 221, 8, 31, 179, 5, 165, 143, 29, 34, 148, 218, 177, 94, 31, 174, 218, 153, 52, 85, 156, 67,
        2, 157, 29, 111, 95, 231, 249, 212, 39, 123, 229, 75, 2, 18, 238, 44, 94, 181, 181, 98, 156, 150, 44, 219, 208,
        161, 18, 250, 117, 91, 146, 133, 233, 210, 161, 133, 233, 228, 111, 124, 107, 96, 134, 123, 148, 88, 238, 193,
        50, 216, 187, 42, 131, 51, 28, 52, 55, 150, 31, 49, 95, 63, 245, 58, 212, 205, 26, 223, 32, 124, 233, 20, 148,
        107, 33, 162, 47, 107, 221, 238, 221, 200, 89, 199, 52, 164, 114, 177, 254, 146, 60, 118, 1, 78, 73, 231, 138,
        136, 201, 242, 26, 100, 57, 237, 135, 181, 44, 193, 143, 191, 155, 93, 66, 142, 69, 203, 57, 22, 147, 120, 161,
        117, 167, 54, 16, 200, 6, 27, 160, 187, 15, 197, 138, 201, 114, 52, 202
      };

      var inlinePem = @"
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1I++ulXUpZUopjSe7M74
PJpj0D9JbbpV3MChYvqnu1m9+Rnd5fr1FUg7xQu+EI0XN+YIi9CbZl4tGtxjela0
0MidM2dd1BI/5W2Zl4DHnPPmvfZoybBquK6dwkkTe5AhCCZegYD4TsNhKWGqea8D
fSiCAvTeG0uiXbQGPD6lWei3IoVcOQ0cuz3LheCl440Bbmg2BVMUrZO81r6yH1Nz
0ypYnBfx26Mo7379ngHc3yjxi8vjDF/is4wqOgWj34R3gbbf9EZ5OWS+DpItPpFZ
Ykh82DrNHNCPSyvD0XvmcPhJ6O7yrloAPSiTUP9+u7w1zBpg50Srcp+afUcGODYm
KQIDAQAB
-----END PUBLIC KEY-----
      ";

      var pemr = new PemReader(new StringReader(inlinePem.Trim()));
      var pem = pemr.ReadObject();

      var keyParams = (RsaKeyParameters) (AsymmetricKeyParameter) pem;
      var sig = SignerUtilities.GetSigner("SHA-256withRSA");
      sig.Init(false, keyParams);
      var infoBytes = Encoding.ASCII.GetBytes("Test123");
      sig.BlockUpdate(infoBytes, 0, infoBytes.Length);

      Console.WriteLine($"{sig.VerifySignature(signature)}");
    }
  }
}

With these two key files I just randomly generated:

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEA1I++ulXUpZUopjSe7M74PJpj0D9JbbpV3MChYvqnu1m9+Rnd
5fr1FUg7xQu+EI0XN+YIi9CbZl4tGtxjela00MidM2dd1BI/5W2Zl4DHnPPmvfZo
ybBquK6dwkkTe5AhCCZegYD4TsNhKWGqea8DfSiCAvTeG0uiXbQGPD6lWei3IoVc
OQ0cuz3LheCl440Bbmg2BVMUrZO81r6yH1Nz0ypYnBfx26Mo7379ngHc3yjxi8vj
DF/is4wqOgWj34R3gbbf9EZ5OWS+DpItPpFZYkh82DrNHNCPSyvD0XvmcPhJ6O7y
rloAPSiTUP9+u7w1zBpg50Srcp+afUcGODYmKQIDAQABAoIBAChRh7zycN5jl41H
J+oFLCLaqhojFvuAP68avsH2h4BK+nTYijWIT5qU0/mBS7D6AjBxKqfSjtdw/587
tIbNEYkUtHS+o5aJS6NqEZsiKzrDLL5VkfTHyMZ1IKlskQx7/zf7hyuLWg4ekzx1
MQ/ZuZCw8VA8QDDvPMIHVrNwso6F8/vc/K3tqygeFBh+rfAbkzp2zvK4Tr+reJTT
hmsg68wBzTNAzmeHK8FsMB2NZvwW/zq5ZgEIkZVkJZl7XbYGj+ggVSn2h0J8KleK
H+60nx39wk/KnscsHo9lPpv/7RoYb7OGEVR8W0fY5+QvALTZ8U9HZftm9TfhUd/0
Is5CIT0CgYEA/kaqLKK2hYlEdP2f9N6w/qiF2Gmxu6N1Tk+gvbz1ChukPHW42xUz
iG45So6iGDWNzjVf9IxAP2n0Zltar4tMlyC767jk5Xs7kONZMwqge5a52NYLRRvt
xoklLgXCjurriq+hw6tNAuvVjs/ScOHOQyC4emVDqJ5Z9766zqrpkCsCgYEA1gCt
pGAwt5KoDOXlk3311BDS6XLev536GW25CmKrs9sJH2cOWSRFA0fWcPr0gVotM9F5
+2ymmJmlYLLDRYWTYCX+vL8aysTTDqezpOhJ9VIzgeTh3FwD17LFE5j4W0AqE5jR
2eMItfobMYqHF4iHp+OmfhocLpLQcTC4BlMvZPsCgYBLXOZTFGbEbUq84e7mxJnw
4EHLQohK9Mdvzmn10mtN86NZyAph5IbBiOmyD1Q7mKPO2kL2WBsysFSfgbP/E2o/
4JPR6Zrt6PhemQN2/U9TUfkDK21rrjtq/HroiQyBD1+AW022kK7ijsNc8HuOuV5I
xwnmPN0wvL4tj3oOhtlywQKBgAsXq+h6R+wsAOPyQq0beVONr7EEEEG0aZNJ2a6N
IMNI1jc3e0nplF4wKhBfIa9WwkMOV5lNr3D3fdf+TBrdap8wOP0FltjtzNbUoH4q
wDKkGSFhgMeQSW6zyH1Uj4MDV2r+n9oAZ6IvHZu6x3fTztxH84hTyCQt3foQAWnq
g+ljAoGBAMza/hD+Hsz7uNtVvvqki6a6FMk7cSZTEtZvBvqrywIEEZEnbsuHk5tD
towaZbQQirZp45xsEJZDO/8O5Q0+WEs/0ZG1CPBgIwNEBUt/deid6s1HFFoESp0/
DfekILO1scieEpccfz8aIlgq/CoRFfXQ5m0VetfP6H6Wau1Hfx5A
-----END RSA PRIVATE KEY-----

and:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1I++ulXUpZUopjSe7M74
PJpj0D9JbbpV3MChYvqnu1m9+Rnd5fr1FUg7xQu+EI0XN+YIi9CbZl4tGtxjela0
0MidM2dd1BI/5W2Zl4DHnPPmvfZoybBquK6dwkkTe5AhCCZegYD4TsNhKWGqea8D
fSiCAvTeG0uiXbQGPD6lWei3IoVcOQ0cuz3LheCl440Bbmg2BVMUrZO81r6yH1Nz
0ypYnBfx26Mo7379ngHc3yjxi8vjDF/is4wqOgWj34R3gbbf9EZ5OWS+DpItPpFZ
Ykh82DrNHNCPSyvD0XvmcPhJ6O7yrloAPSiTUP9+u7w1zBpg50Srcp+afUcGODYm
KQIDAQAB
-----END PUBLIC KEY-----

...and it works fine:

$ node test
[193,185,118,187,54,28,71,173,185,255,213,61,254,51,86,168,224,11,113,23,81,123,89,31,89,183,142,194,7,250,194,165,230,254,74,61,15,12,137,246,151,61,83,107,112,178,98,183,234,247,56,11,246,179,183,74,27,190,17,99,161,31,209,178,81,41,56,214,184,165,232,20,125,155,25,102,104,193,1,101,143,209,192,145,47,215,190,95,196,164,69,203,206,69,142,18,196,155,221,8,31,179,5,165,143,29,34,148,218,177,94,31,174,218,153,52,85,156,67,2,157,29,111,95,231,249,212,39,123,229,75,2,18,238,44,94,181,181,98,156,150,44,219,208,161,18,250,117,91,146,133,233,210,161,133,233,228,111,124,107,96,134,123,148,88,238,193,50,216,187,42,131,51,28,52,55,150,31,49,95,63,245,58,212,205,26,223,32,124,233,20,148,107,33,162,47,107,221,238,221,200,89,199,52,164,114,177,254,146,60,118,1,78,73,231,138,136,201,242,26,100,57,237,135,181,44,193,143,191,155,93,66,142,69,203,57,22,147,120,161,117,167,54,16,200,6,27,160,187,15,197,138,201,114,52,202]
Did sign? true
$ dotnet run
True

So my guess?

Either something is wrong with your public key, or the json input you're passing in to the function on both sides isn't quite the same for some reason.

There's nothing wrong with your signing code as far as I can tell.