azur3
azur3

Reputation: 43

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

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, 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.

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.

When I try to use Bouncy Castle .NET instead, it does not need to input any keyBitLength parameter, but fails too unwrapping 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:

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?

Edit: Maybe the problem is the ConcatKDF implementation, but I'm not sure. Bouncy Castle and built-in .NET classes produces the same bytes when making a simple Key Agreement.

// Key Agreement with built-in classes
public static Byte[] DeriveKey(ECDiffieHellman recipientPrivKey, ECDiffieHellman otherPartyKey)
{
    // Generate the shared secret using ECDH
    Byte[] sharedSecret = privateKey.DeriveKeyMaterial(otherPartyKey.PublicKey);
    return sharedSecret;
}

// Key Agreement with Bouncy Castle
public static Byte[] EcdhKeyExchange(ECPrivateKeyParameters recipientPrivParams, ECPublicKeyParameters otherPartyParams)
{
    IBasicAgreement keyAgree = AgreementUtilities.GetBasicAgreement("ECDH");
    keyAgree.Init(recipientPrivParams);
    BigInteger sharedSecret = keyAgree.CalculateAgreement(otherPartyParams);
    Byte[] sharedSecretBytes = sharedSecret.ToByteArray();
    return sharedSecretBytes;
}

// Key Agreement with Bouncy Castle FIPS
public static Byte[] BasicAgreementWithCofactor(AsymmetricECPrivateKey recipientPrivKey, AsymmetricECPublicKey otherPartyKey)
{
    IAgreementCalculatorService dhFact = CryptoServicesRegistrar.CreateService(recipientPrivKey);
    IAgreementCalculator<FipsEC.AgreementParameters> agreement = dhFact.CreateAgreementCalculator(FipsEC.Cdh);
    return agreement.Calculate(otherPartyKey);
}

Compared output of these functions, and they are exactly the same.

So to define the question more precisely: What modifications should be made in ConcatKDF to generate an output that can AES-UnWrap the encrypted key? Is it possible that the test cases contains wrong data, which causes decrypting the encryption key fails with checksum error?

I might try the interop test files too, if they can be decrypted.

Upvotes: 0

Views: 44

Answers (0)

Related Questions