Search code examples
c#node.jsasp.net-corecryptojsnode-crypto

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


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":"person@example.com","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"":""person@example.com"",""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;
};

Solution

  • 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.