My application requires secure messaging between a server and client devices. The devices are not directly internet connected so it is not possible to use standard HTTPS or MQTT connections.
My preferred approach is to use asymmetric authenticated encryption such as libsodium's crypto_box API. The devices would encrypt and authenticate messages using their private keys and the server's public key. The server would do the same using its private key and the devices' public keys.
For this to be safe, I believe the server private key(s) need to be managed by a KMS. I am using Google Cloud Platform for the backend, and I do not see a way to have GCP KMS decrypt and authenticate messages encrypted by libsodium: GCP KMS doesn't seem to support libsodium's key algorithms, and it doesn't seem to support authenticated encryption.
I like libsodium because it is well-supported on my chosen embedded platform, and I like GCP KMS because I'm using GCP for the backend. It seems like the two are primarily designed to handle messages encrypted by themselves.
Is there a way for me to use libsodium on my devices, while maintaining the server private keys in GCP KMS? Or is another approach needed?
Libsodium's crypto box uses X25519 to agree a key using one end's private key and the other's public key (and vice versa). It then uses the derived key and random nonce to encrypt the message to encrypt the message with a symmetrical, authenticated stream cipher (Salsa20). (This key exchange happens to give mutual authentication - both ends need their private key. It can be broken down into parts, so that the more expensive EC parts are performed once and the derived key re-used for several messages.)
As you've discovered ECDH isn't supported (though someone did open an feature request once).
Working backwards from what is supported by Cloud KMS with asymmetric keys it's just decryption (and signing). (Encryption is left to the client which can obtain the public key and it it itself.)
So, if the server private key is to remain safe in the KMS, all you can do is encrypt and decrypt small plain-/cipher-texts.
You can emulate many of the parts of crypto box with what you have available. To encrypt a message: generate a random key and IV, use an authenticated symmetric cipher (e.g. Salsa or AES-GCM), encrypt the random key in the receiver's public key and send the encrypted key, IV and ciphertext. As with crypto box, you could partition this by, say, sending the encrypted key once and reusing it for several messages (with a different IV each time, of course).
At the server end you need RSA encrypt (decrypt is done in KMS) and the symmetric cipher. At the client end you need RSA encrypt, decrypt and the same cipher. Libsodium has AES-GCM, so that seems a good choice.
But you are still missing RSA at the client. Looks for alternative C solutions for that (e.g. libtomcrypto or Oryx Embedded CycloneCRYPTO Open). You might find that whatever you need for RSA also happens to do the AES-GCM too.