We have a proxy that needs to be secured by a ECDSA digital signature. Are there any policies that can verify the signature using a public key? Or, will we need to verify this in Java, Python, or other code?
Here's the security guidance.
Security: Digital Signature
Type: Public/Private Key
Header: The x-webhook-signature header includes a digital signature header signed by the webhook provider
Digital Signature Algorithm: Elliptical Curve Digital Signature Algorithm
Hash: SHA256
Header Example: x-webhook-signature:86-6D-59-BE-8A-...E9-B0-49-F6-39-95
Example cURL:
curl -X POST
https://xyz.com/endpoint1
-H 'Accept: */*'
-H 'Accept-Encoding: gzip, deflate'
-H 'Content-Type: application/json'
-H 'x-webhook-signature: 86-6D-59-BE-8A-E9-B0-49-F6-39-9'
-d '{"application":"data"}'
Solved! Go to Solution.
The reason I asked for test vectors is ... I thought I might be able to put something together for you. And this is what I did:
https://github.com/DinoChiesa/Apigee-CustomPolicy-EcdsaSig
It's pretty simple. Signs or verifies, depending on your configuration. Check the README for further information.
There is no out-of-the-box policy in Apigee that verifies or produces an ECDSA signature. You will need to do that in Java or Python, or something else.
I don't know of an existing example that shows how to do this but it wouldn't be difficult to build. There are lots of related existing examples.
Do you have test vectors?
Thanks for the response! Unfortunately we are not java or python developers, so this will be more work that we thought. We do have test vectors and just need to figure out how we will implement this in Apigee. We will look into how we create a jar file to upload. It looks like that might be the easiest solution at this point.
The reason I asked for test vectors is ... I thought I might be able to put something together for you. And this is what I did:
https://github.com/DinoChiesa/Apigee-CustomPolicy-EcdsaSig
It's pretty simple. Signs or verifies, depending on your configuration. Check the README for further information.
That's awesome! We'll give this a try and let you know how it works. Thanks for putting this together, we really appreciate it!!
Trying to get this to work and we are having a some issues validating so we are trying to just have it create a signature.
Policy to sign the payload
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout async="false" continueOnError="false" enabled="true" name="SignData">
<DisplayName>SignData</DisplayName>
<Properties>
<Property name="action">sign</Property>
<Property name="generate-keypair">true</Property>
<Property name="encode-result">base64</Property>
</Properties>
<ClassName>com.google.apigee.callouts.EcdsaSigCallout</ClassName>
<ResourceURL>java://apigee-callout-ecdsa-sig-20210929.jar</ResourceURL>
</JavaCallout>
Output
From the output, I'm not sure what the signature is. I don't see an "ecdsa_signature". Also, is the "ecdsa_output" variable the public key?
Let me see
The ecdsa_output is the signature. I could change that to ecdsa_signature if you like.
As you can see, it's a base64-encoded value. Since you didn't specify the curve it uses prime256v1 (aka secp256r1, aka P-256).
The ecdsa_output_key is the PEM-encoded private key that the callout generated for you. Currently the callout does not emit the public key. I can modify the callout to emit both keys in PEM format. That seems like a good idea.
If you give me some test vectors, or a tool that you use for generating, I can test for you.
I think I did not document this in the README, but this callout uses SHA256withECDSA. If you are using SHA1withECDSA, it won't validate. I currently don't have an option in the callout to swap to SHA1. Could add that if necessary. SHA1 is deprecated since 2017, though.
EDIT: I updated the callout to use ecdsa_signature as the name of the output variable, when signing.
Also updated it to emit the public key (ecdsa_output_publickey). And modified the variable name for the private key to ecdsa_output_privatekey.
You need to get the 20210930 version of the callout to get these changes.
We are using the SHA256withECDSA so that's correct for us. Yeah, renaming it to ecdsa_signature would be helpful and more clear.
Here's our test vectors that we are trying to verify with the proxy using your code.
Message: Live long and prosper
Message signature base 64: AEYo2JeQXtEG3sMf5uISVd+Iri2Sq6h9lmibWAtpGkNwI6gtqnASMaGPnE6fdvPM9tqzWzPeMCONASz+Zr1pUATiAGKLUPBb25XLVBXDBeCbRk94gvkZ2pZWbtvCWNuaWl3MfBglySTj0bTy43IqB0Q5J2dS+RuVFmGEZOq84o5uy5py
Message public key: MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBHPYWDznBjvs9Wl7Nw51DI90D7G+7JQCIssdj5nktQ+KgBZsQ28eWkJyEBz0oSquTa876XXA9C8N3F0CQs6UgrrQAMfuuFS6b/XJH3RolJVZNdN7xmZxGhbP+sz75nIB1NVym5y7nRgzyUxX2FPo7PoRAlWZQM2V6CDt6NdUFLHHM19U=
Here's the c# code that does the signing. It's a test right now, so it's just a console app using .NET 5.0
using System;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Encodings;
namespace DigitalSignature
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
DigitalSignature sig = new DigitalSignature();
sig.SignPayload();
}
}
public class DigitalSignature
{
public void SignPayload()
{
Console.WriteLine("***** Message signature demo *****");
//Create public and private keys. Store private in Azure Key Vault and provide public to CA.
var alg = ECDsa.Create();
ECParameters privateAndPublicKeys, publicKeyOnly;
privateAndPublicKeys = alg.ExportParameters(includePrivateParameters: true);
publicKeyOnly = alg.ExportParameters(includePrivateParameters: false);
var message = "Live long and prosper";
byte[] signatureHeader;
//Convert message to byte array
var algArray = Encoding.ASCII.GetBytes(message);
//var algArray = JsonSerializer.SerializeToUtf8Bytes(message);
//Signing
using (var signingAlg = ECDsa.Create())
{
//import private key from Azure Key Vault
signingAlg.ImportParameters(privateAndPublicKeys);
var s = Convert.ToBase64String(signingAlg.ExportSubjectPublicKeyInfo());
var signature = signingAlg.SignData(algArray, 0, algArray.Length, HashAlgorithmName.SHA256);
signatureHeader = signature;
Console.WriteLine($"Message: {message} \n");
Console.WriteLine("Message signature: " + BitConverter.ToString(signature) + "\n");
Console.WriteLine("Message signature base 64: " + Convert.ToBase64String(signature) + "\n");
Console.WriteLine("Message public key: " + s);
}
//Verify Signature
using (var verifyAlg = ECDsa.Create())
{
//use provided public key
verifyAlg.ImportParameters(publicKeyOnly);
//verify data and signature header. CA will need to convert JSON message to byte array for .Net verification
var isTampered = verifyAlg.VerifyData(algArray, signatureHeader, HashAlgorithmName.SHA256);
Console.WriteLine("Verified: " + isTampered.ToString());
Console.Write(Environment.NewLine);
}
}
}
}
Again, thank you for all of the help. I really appreciate it!
Here's the trace of the error I get when trying to verify a signature from the c# code.
Here's the Verify Digital Sig policy
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<JavaCallout async="false" continueOnError="false" enabled="true" name="Verify-Digital-Sig">
<DisplayName>Verify Digital Sig</DisplayName>
<Properties>
<Property name="action">verify</Property>
<Property name="source">message.content</Property>
<Property name="public-key">
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBHPYWDznBjvs9Wl7Nw51DI90D7G+7JQCIssdj5nktQ+KgBZsQ28eWkJyEBz0oSquTa876XXA9C8N3F0CQs6UgrrQAMfuuFS6b/XJH3RolJVZNdN7xmZxGhbP+sz75nIB1NVym5y7nRgzyUxX2FPo7PoRAlWZQM2V6CDt6NdUFLHHM19U=
-----END PUBLIC KEY-----
</Property>
<Property name="decode-signature">base64</Property>
<Property name="debug">true</Property>
<Property name="signature">{request.header.x-webhook-signature}</Property>
</Properties>
<ClassName>com.google.apigee.callouts.EcdsaSigCallout</ClassName>
<ResourceURL>java://apigee-callout-ecdsa-sig-20210929.jar</ResourceURL>
</JavaCallout>
I got it to work! This issue is with the difference in how C# encodes the signature and how Java expects it. By default C# does not do DER encoding, but Java expects the signature to be DER encoded (70 bytes is expected vs 64 bytes). Once I made the following code change in C#, the Java code was able to verify the signature.
var signature = signingAlg.SignData(algArray, 0, algArray.Length, HashAlgorithmName.SHA256, DSASignatureFormat.Rfc3279DerSequence);
Now I'm wondering which way is the correct way to do it. If the webhook provider is sending a 64 byte non DER signature, I need to figure out how to get the java code to verify it. Or maybe convert it to a DER encoded signature...
Yes,
oh! Excellent.
I have tested and see the same error you reported here.
The reason is that the C# method ECDsa.SignData(), formats the signature data in a way that is different than the format that the Apigee callout expects. Today I learned that there are two commonly used ways to format ECDSA signatures. I agree with you: C# SignData is producing the IEEE P1363 format, and Java is expecting the RFC3279 ASN.1 DER Format. (reference).
I have built some logic in the Apigee callout to convert from P1363 to ASN.1 (DER), and the callout can now verify the P1363 signature as produced by your original C# code. This will satisfy your requirement.
I have one more thing to do: convert between ASN.1 to P1363 in signature generation. This isn't something you're asking for right now, but I want to include it in the update.
I should be able to provide this shortly.
ok, try pulling the latest (20210930a)
You need to use a configuration like this:
<JavaCallout name="Java-ECDSA-Verify">
<Properties>
<Property name='action'>verify</Property>
<Property name='public-key'>
-----BEGIN PUBLIC KEY-----
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBHPYWDznBjvs9Wl7Nw51DI90D7G+7JQCIssdj5nkt
Q+KgBZsQ28eWkJyEBz0oSquTa876XXA9C8N3F0CQs6UgrrQAMfuuFS6b/XJH3RolJVZNdN7xmZxG
hbP+sz75nIB1NVym5y7nRgzyUxX2FPo7PoRAlWZQM2V6CDt6NdUFLHHM19U=
-----END PUBLIC KEY-----
</Property>
<Property name='signature'>{request.header.ecdsa-signature}</Property>
<Property name='format'>P1363</Property>
<Property name='decode-signature'>base64</Property>
</Properties>
<ClassName>com.google.apigee.callouts.EcdsaSigCallout</ClassName>
<ResourceURL>java://apigee-callout-ecdsa-sig-20210930a.jar</ResourceURL>
</JavaCallout>
The latest version is working with the .NET generated signature. Thank you so much for helping us with this, we greatly appreciate it! You seriously saved us a lot of time.
I'm not getting the functions ExportParameters, ImportParameters, VerifyData and SignData
Are you asking a question about the .NET API for ECDSA ? This forum is dedicated to Apigee Q&A. You;d probably have better luck asking about the .NET classes in a Microsoft forum dedicated to .NET.