0

I'm trying to Verifying that requests originate from Office Online by using proof keys

i'm implementing the wopi host with nodejs, and there are samples of code in the link above that implements the proof keys process using .NET so i'm using the edge package to be able to run the .NET code with nodejs.

the problem is that the code is running fine, but it always returning that the proof keys in the request are not valid even when the requests are coming from the wopi client.

explanation: there are 3 functions:
validateProofKey - the main function - get all the requierd params to validate the proof key and return bool if they are valid using the functions : constructProofKey and TryProofkeyVerification
constructProofKey - declare .NET function (constructProofKey from sample code ) , run it and return the result of the constructed proofkey
TryProofkeyVerification - declare .NET function (TryProofkeyVerification from .NET sample code), run it and return the Boolean result

the code :

    static async validateProofKey(access_token: string, request_url: string, X_WOPI_TimeStamp: string, X_WOPI_Proof: string, X_WOPI_Proof_old: string): Promise<boolean> {

        //get public key provided in WOPI discovery:
        if (!wopiDiscovery) { wopiDiscovery = await this.getWopiDiscovery() }

        const public_Keys: iWopiDiscoveryProofKey = wopiDiscovery["wopi-discovery"]["proof-key"];
        const public__Key: string = public_Keys.value;
        const old_public_Key: string = public_Keys.oldvalue;


        this.printProofkeyArgs(access_token, request_url, X_WOPI_TimeStamp, X_WOPI_Proof, X_WOPI_Proof_old, public__Key, old_public_Key, modulus_b64, exp_b64);
        //1. Create the expected value of the proof headers.
        let expectedProofKey: Buffer = await this.constructProofKey(access_token, request_url, X_WOPI_TimeStamp);



        //2.Use the public key provided in WOPI discovery to decrypt the proof provided in the X-WOPI-Proof header.

        const verified: boolean = await this.TryProofkeyVerification(expectedProofKey, X_WOPI_Proof, public__Key) ||
            await this.TryProofkeyVerification(expectedProofKey, X_WOPI_Proof_old, public__Key) ||
            await this.TryProofkeyVerification(expectedProofKey, X_WOPI_Proof, old_public_Key)

        return verified;
    }

constructProofKey

private static constructProofKey(access_token: string, request_url: string, X_WOPI_TimeStamp: string): Promise<Buffer> {
    return new Promise((resolve, reject) => {
        const proofParams = {
            access_token: access_token,
            request_url: request_url,
            X_WOPI_TimeStamp: X_WOPI_TimeStamp
        }

        const constructProofKey = edge.func(`
        using System.Threading.Tasks;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Security.Cryptography;
        using System.Text;
        public class Startup
        {
            public async Task<object> Invoke(dynamic proofParams)
            {

                return Helper.constructProofKey(proofParams);
            }
        }

        static class Helper
        {
            public static byte[] constructProofKey(dynamic proofParams) 
            {

                // Encode values from headers into byte[]
                long timestamp = Convert.ToInt64(proofParams.X_WOPI_TimeStamp);
                var accessTokenBytes = Encoding.UTF8.GetBytes(proofParams.access_token);
                var hostUrlBytes = Encoding.UTF8.GetBytes(proofParams.request_url.ToUpperInvariant());
                var timeStampBytes = EncodeNumber(timestamp);
                      // prepare a list that will be used to combine all those arrays together
                      List < byte > expectedProof = new List<byte>(
                          4 + accessTokenBytes.Length +
                          4 + hostUrlBytes.Length +
                          4 + timeStampBytes.Length);

                expectedProof.AddRange(EncodeNumber(accessTokenBytes.Length));
                expectedProof.AddRange(accessTokenBytes);
                expectedProof.AddRange(EncodeNumber(hostUrlBytes.Length));
                expectedProof.AddRange(hostUrlBytes);
                expectedProof.AddRange(EncodeNumber(timeStampBytes.Length));
                expectedProof.AddRange(timeStampBytes);

                // create another byte[] from that list
                    byte[] expectedProofArray = expectedProof.ToArray();
                        return expectedProofArray;
            }
            private static byte[] EncodeNumber(long value)
            {
                return BitConverter.GetBytes(value).Reverse().ToArray();
            }
        }
        `)

        constructProofKey(proofParams, (error, result) => {
            if (error) {
                reject(error);
            }
            resolve(result);
        });
    })

}

TryProofkeyVerification

private static TryProofkeyVerification(expectedProofKey: Buffer, private_proofkey: string, public_proofkey: string): Promise<boolean> {
    return new Promise((resolve, reject) => {
        const proofParams = {
            expectedProofKey: expectedProofKey,
            private_proofkey: private_proofkey,
            public_proofkey: public_proofkey
        }
        //declare .NET code
        const TryVerification = edge.func(`
        using System.Threading.Tasks;
        using System;
        using System.Collections.Generic;
        using System.Linq;
        using System.Security.Cryptography;
        using System.Text;
        public class Startup
        {
            public async Task<object> Invoke(dynamic proofParams)
            {
                return Helper.TryVerification(proofParams);
            }
        }

        static class Helper
        {
            public static bool TryVerification(dynamic proofParams)
            {   

                byte[] expectedProofKey = (byte[])proofParams.expectedProofKey;

                 using(RSACryptoServiceProvider rsaAlg = new RSACryptoServiceProvider())
                 {

                    byte[] publicKey = Convert.FromBase64String(proofParams.public_proofkey);
                    byte[] signedProofBytes = Convert.FromBase64String(proofParams.private_proofkey);
                    try
                    {

                        rsaAlg.ImportCspBlob(publicKey);
                        return rsaAlg.VerifyData(expectedProofKey, "SHA256", signedProofBytes);

                    }
                    catch(FormatException)
                    {
                        Console.WriteLine("Format Exception");
                        return false;
                    }
                    catch(CryptographicException e)
                    {
                        Console.WriteLine("CryptographicException Exception");

                        return false;
                    }
                 }
            }
        }
        `);
        //Invoke .NET code
        TryVerification(proofParams, (error, result) => {
            if (error) {
                reject(error);
            }
            resolve(result);
        });
    })

}

NOTE - I've already log the parameters to console to make sure the parameters transferred correctly through all the process

LazyCreep
  • 211
  • 2
  • 14
  • 1
    Could you run the .NET code outside of your Node.js app (under pure .NET Framework) and verify that if you pass the same parameters as in the Node.js app you get the same results? – rocky Feb 19 '18 at 19:11
  • One more thought - this .net code works, BUT make sure your protocol is HTTPS, and you don't use a custom port - otherwise you need to change it to take that into account (fix the code in the appropriate place). – Nikolay Feb 25 '18 at 18:08
  • I know this question is quite old now, but for anyone that stumbles across this, make sure you run the [provided example tests](https://github.com/microsoft/Office-Online-Test-Tools-and-Documentation/tree/master/samples) against your code. I also answered [this question](https://stackoverflow.com/q/57568697/1393498) explaining some of the issues I had implementing this. – Yep_It's_Me Dec 16 '20 at 02:54

0 Answers0