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