• caglararli@hotmail.com
  • 05386281520

Is XML Encryption 1.1 Key Agreement test cases decryption possible in .NET?

Çağlar Arlı      -    4 Views

Is XML Encryption 1.1 Key Agreement test cases decryption possible in .NET?

I have asked this already on Stack Overflow, but got a suggestion to ask in this community too.

I'm working on implementing an XML encrypting / decrypting application in .NET with support for ECDH-ES because clients will use EC key pairs to encrypt / decrypt messages as descibed in W3C standards, and there's no official implementation for this encryption method. I've tried to implement decryption first using test files found on W3C test cases url for key agreement. Parsing XML was done by creating classes from official XmlEnc and XmlDisg XSDs, and I've encountered an issue when reaching the encrypted key decryption phase. What I've done already step by step using the first test case.

  • Originator public key was recreated as an ECDiffieHellmanCngPublicKey from reading attributes and values of xenc:OriginatorKeyInfo tag.
  • The private key was searched for in p12 files by using Issuer Name and Serial Number values found in xenc:RecipientKeyInfo/dsig:X509Data/dsig:X509IssuerSerial.

Here comes the problem. I'm not experienced in cryptography, so I've not found if there is any Concatenation Key Derivation Function implementation in .NET. But using an open-source JOSE-JWT .NET implementation, which is not for XML but hoped it may work for that too. Even AES Key Wrap was coded here, used that too. Calling DeriveEcdhKey method of ConcatKDF class requires a keyBitLength parameter, which I don't know. Tried 128, because test cases uses AES-128-KW, 192, and even 256. It does not failed, but when trying to unwrap the encrypted key, integrity error occurs. If I try to use Bouncy Castle .NET instead, it does not need to input any keyBitLength parameter, but fails too unwrapping encryption key.

Some lines from my code where I try to decrypt the content encryption key:

XmlDocument xml = new XmlDocument();
xml.LoadXml(File.ReadAllText(cipherTextFile.FullName));
XmlElement encDataElement = XmlUtilities.FindXmlElement(xml, "EncryptedData", XmlSec.DefaultNamespaces.XMLENC_10);
EncryptedData encryptedData = new EncryptedData();
encryptedData.LoadXml(encDataElement);
XmlElement keyInfoElement = encryptedData.KeyInfo.GetXml();
XmlElement encKeyElement = XmlUtilities.FindXmlElement(keyInfoElement, "EncryptedKey", XmlSec.DefaultNamespaces.XMLENC_10);
EncryptedKey encryptedKey = new EncryptedKey();
encryptedKey.LoadXml(encKeyElement);
XmlElement encKeyInfoElement = encryptedKey.KeyInfo.GetXml();
XmlElement agreeMethodElement = XmlUtilities.FindXmlElement(encKeyInfoElement, "AgreementMethod", XmlSec.DefaultNamespaces.XMLENC_10);
AgreementMethodType agreeMethod = XmlUtilities.Deserialize<AgreementMethodType>(agreeMethodElement);
ECKeyValueType originatorKeyValue = XmlUtilities.Deserialize<ECKeyValueType>((agreeMethod.OriginatorKeyInfo.Items.FirstOrDefault() as KeyValueType).Item as XmlElement);
NamedCurveType namedCurve = (originatorKeyValue.Item as NamedCurveType);
Oid originatorCurveOid = new Oid(namedCurve.URI.Replace("urn:oid:", String.Empty));
Byte[] originatorQX = new Byte[(originatorKeyValue.PublicKey.Length - 1) / 2];
Byte[] originatorQY = new Byte[originatorQX.Length];

Buffer.BlockCopy(originatorKeyValue.PublicKey, 1, originatorQX, 0, originatorQX.Length);
Buffer.BlockCopy(originatorKeyValue.PublicKey, originatorQX.Length + 1, originatorQY, 0, originatorQY.Length);
ECParameters originatorEcParams = new ECParameters()
{
    Q = new ECPoint()
    {
        X = originatorQX,
        Y = originatorQY
    },
    Curve = ECCurve.CreateFromOid(originatorCurveOid)

};
ECDiffieHellman originatorPublicKey = ECDiffieHellman.Create(originatorEcParams);

X509IssuerSerialType recipientIssuerAndSn = null;
X509DataType recipientX509Data = agreeMethod.RecipientKeyInfo.Items.FirstOrDefault() as X509DataType;
for (Int32 i = 0; i < recipientX509Data.ItemsElementName.Length; i++)
{
    if (recipientX509Data.ItemsElementName[i] == ItemsChoiceType.X509Certificate)
    {
        X509Certificate2 recipientCer = new X509Certificate2(recipientX509Data.Items[i] as Byte[]);
        recipientIssuerAndSn = new X509IssuerSerialType() { X509IssuerName = recipientCer.Issuer, X509SerialNumber = recipientCer.SerialNumber };
        break;
    }
    if (recipientX509Data.ItemsElementName[i] == ItemsChoiceType.X509IssuerSerial)
    {
        recipientIssuerAndSn = recipientX509Data.Items[i] as X509IssuerSerialType;
        break;
    }
}
X500DistinguishedName recipientIssuer = new X500DistinguishedName(recipientIssuerAndSn.X509IssuerName);
Byte[] ecPrivateKey = null;
foreach (FileInfo ks in keyStores)
{
    using (X509Certificate2 p12 = new X509Certificate2(File.ReadAllBytes(ks.FullName), "passwd", X509KeyStorageFlags.Exportable))
    {
        if (p12.HasPrivateKey && p12.IssuerName.Name.Equals(recipientIssuer.Name) && p12.SerialNumber.Equals(recipientIssuerAndSn.X509SerialNumber))
        {
            ECDsa ecdsaPK = p12.GetECDsaPrivateKey();
            ecPrivateKey = ecdsaPK.ExportECPrivateKey();
            break;
        }
    }
}
ECDiffieHellman recipientPrivKey = ECDiffieHellman.Create();
recipientPrivKey.ImportECPrivateKey(ecPrivateKey, out _);

KeyDerivationMethodType keyDerivationMethod = XmlUtilities.Deserialize<KeyDerivationMethodType>(agreeMethod.Any.FirstOrDefault());

if (KeyDerivationAlgorithms.ConcatKDF.Equals(keyDerivationMethod.Algorithm))
{
    ConcatKDFParamsType concatKdfParams = XmlUtilities.Deserialize<ConcatKDFParamsType>(keyDerivationMethod.Any.FirstOrDefault());
    DigestMethodType digestMethod = concatKdfParams.DigestMethod;
    Oid digestOid = DigestMethods.GetOid(digestMethod.Algorithm);
    Byte[] derivedKey = ConcatKDF.DeriveEcdhKey(originatorPublicKey, recipientPrivKey, 128, concatKdfParams.AlgorithmID, concatKdfParams.PartyVInfo, concatKdfParams.PartyUInfo, concatKdfParams.SuppPubInfo);
    Byte[] encryptedCek = encryptedKey.CipherData.CipherValue;
    Byte[] cek = AesKeyWrap.Unwrap(encryptedCek, derivedKey); // tihs line throws integrity error, checksum fails
}

If I understand it correctly, decryption must be done in this way:

  1. Get the Originator (ephemeral) Public Key from the encrypted XML.
  2. Find the recipient's private key in a key store using any recipient hint found in the encrypted XML.
  3. Convert attribute values from ConcatKDFParams to byte arrays, and join them to a single byte array.
  4. Do a concatenation key derivation using originator's public key, recipient's private key, and the byte array made from ConcatKDFParams' attributes.
  5. Unwrap the encrypted content encryption key using the result of the ConcatKDF as an input key to AES Key Wrap (is it AES-ECB?). This fails for me. Is it because of wrong output from ConcatKDF?
  6. Decrypt the XML using the method found in EncryptionMethod's algorithm attribute.

Am I doing something in wrong order, or missing something? Are XML Encryption 1.1 test cases possible to decrypt? Why even Bouncy Castle fails to unwrap key? And is there any reason why no implementation exists already for this method?