WOPI Protocol, Microsoft Office and Proof Key
If you’re already familiar with WOPI or have read our previous article on using WOPI to embed Microsoft Office, you’ll know that one of the hardest parts of implementing WOPI protocol is verifying the inbound API requests using proof keys. Sadly, the documentation for WOPI protocol is incomplete and inaccurate on this crucial point. Those that succeed at implementing this often resort to a long and stressful trial and error process to make it work. Well, we’re here to help make the process as smooth as possible for you, offering a tried-and-tested method to save you time and stress.
Why Should You Verify the WOPI Protocol Request Using a Proof Key?
Requests from Microsoft to your WOPI protocol API implementation are not authenticated in a traditional sense – you don’t receive a JWT or other bearer token in a header or anything like that (other than your own Access Token). Instead, the WOPI protocol uses a cryptographic proof key based on a SHA256 hash of a specified chunk of binary data from your request. The proof is received in the HTTP headers X-WOPI-Proof and X-WOPI-ProofOld.
If you don’t verify that the SHA256 hash is correct, you have no way of knowing if an inbound HTTP request is genuine or malicious.
How Do I Verify a WOPI Protocol Request Using a Proof Key?
There are three main steps to achieve this:
- Verifying the timestamp
- Constructing the content to verify
- Verifying the SHA256 hash
It’s worth noting that these guidelines assume that you already have a copy of the Discovery XML cached from earlier in your implementation. I won’t go into that for now, but please submit a contact form at the end of this article if you’re interested in learning more about this.
1. Verifying the timestamp
This step is often forgotten in a WOPI implementation, but it is important to prevent replay attacks. In theory, it’s easy; you need to get the X-WOPI-Timestamp header and make sure that it was sent within the last 20 minutes. In practice, this is a bit more difficult.
There are two main challenges to verifying the timestamps:
- Timestamp differences
Most systems (e.g. PHP) use Unix timestamps – these measure the time elapsed since January 1st 1970 AD. The WOPI protocol timestamp measures the time elapsed since January 1st, 1 AD. This means a little bit of adjustment is required if you are not using a .Net-based system.
- Time measurement
The typical units used by various systems (e.g. PHP) for measuring time is seconds or milliseconds. Microsoft chose to use neither for the WOPI protocol timestamp. Instead, the timestamp is the number of 100 nano-second units passed since January 1st 1 AD. This means that you may need to do some conversions to get the right timestamp.
Verifying the timestamp is pretty easy if you’re using .Net Framework or .Net Core. However, it can be a pain if you’re using any other system!
2. Construct the content to verify
The next step is to construct the content on which you are going to verify the hash. The key challenges here are:
- The documentation is incorrect.
- The implementation is processor-architecture dependent.
- You need good byte-level handling features in your programming language.
You can see a suggested sample of code in the WOPI protocol documentation. This code is incomplete and inaccurate, but it’s a good starting point if you’re working in C#. You’ll need to work out how to implement EncodeNumber(…), which is non-trivial if you do it right! I should also point out a minor annoyance – the precalculated “expectedProof” list size is wrong too. The size of the byte array for the timeStampBytes is eight as this is a long, not an int. The code still works if you leave it at 4, but it’s wrong!
The next two challenges are all about byte handling; you’ll need a bit of background to understand this. The proof key that you receive with your inbound WOPI protocol requests (X-WOPI-Proof and X-WOPI-ProofOld) assumes a couple of things:
- An int is 4 bytes (32 bits).
- A long int is 8 bytes (64 bits).
- Your system is Little Endian.
If you are a Windows C# programmer, you’re probably okay. However, don’t make too many assumptions, particularly in this modern cloud era when your C# web service might get deployed on Linux!
This is important because as part of constructing your content to verify, you need to convert your 32 bit integers into little endian byte arrays (4 bytes) and convert your 64 bit integers into little endian byte arrays (8 bytes). You should always check the endianness of your system when doing this to make sure you get the bytes in the right order.
If you’re implementing in C#, it’s easy. If you’re implementing in another language with poor byte support (like PHP), it’s a bit more complex but still achievable (we have working implementations in several languages!)
3. Verify The SHA256 Hash
The final step is to verify the hash. Remember that the proof key you are verifying is supplied in the X-WOPI-Proof and X-WOPI-ProofOld headers. First, we should understand why there are two proof keys supplied.
Microsoft periodically changes their private keys used to sign requests (this is good security practice). They also recommend that you cache the Discovery information in which the public key is held for 12 – 24 hours. This means that the key used to sign a request has been rotated from being the current key to being the old key. You need to check three different combinations of proof keys and keys to avoid strange timing problems:
- Current Key + Current Proof
- Old Key + Current Proof
- Current Key + Old Proof
The first challenge is to construct the Microsoft public keys used to create the hashes supplied in these two headers. Again, if you’re a C# .Net Framework / Core programmer, this is easy. Just use the Base64 encoded “value” or “oldvalue” from the Discovery XML along with the RSACryptoServiceProvider class and call VerifyData(…), and you’re done!
If you’re using any other system, it’s a bit harder. For example, Microsoft uses a non-standard format for their public keys. My recommendation for anyone using PHP or Java etc, is to reconstruct the public key using the “modulus” and “exponent” (and “oldmodulus” and “oldexponent”) values from the Discovery XML. You’ll need an encryption library to convert these to a public key. For example, for PHP, you can use phpseclib for this.
Once you’ve constructed the public key, you need to use an encryption library to verify the supplied SHA256. For example, you could use openssl for this.
We’ve built working WOPI protocol and proof key implementations in a variety of languages. If you’re struggling or simply don’t have the time to do it yourself, please get in touch. We can get you up and running with a working WOPI protocol implementation quickly! For more information about WOPI integration, Office 365 integration and other interesting topics, please see our blog.