summary refs log tree commit diff
path: root/crypto/src/crypto/engines/Rfc5649WrapEngine.cs
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/src/crypto/engines/Rfc5649WrapEngine.cs')
-rw-r--r--crypto/src/crypto/engines/Rfc5649WrapEngine.cs271
1 files changed, 271 insertions, 0 deletions
diff --git a/crypto/src/crypto/engines/Rfc5649WrapEngine.cs b/crypto/src/crypto/engines/Rfc5649WrapEngine.cs
new file mode 100644
index 000000000..09830c7e8
--- /dev/null
+++ b/crypto/src/crypto/engines/Rfc5649WrapEngine.cs
@@ -0,0 +1,271 @@
+using System;
+
+using Org.BouncyCastle.Crypto.Parameters;
+using Org.BouncyCastle.Crypto.Utilities;
+using Org.BouncyCastle.Utilities;
+
+namespace Org.BouncyCastle.Crypto.Engines
+{
+    /// <summary>An implementation of the AES Key Wrap with Padding specification as described in RFC 5649.</summary>
+    /// <remarks>
+    /// For further details see: Housley, R. and M. Dworkin, "Advanced Encryption Standard (AES) Key Wrap with Padding Algorithm",
+    /// RFC 5649, DOI 10.17487/RFC5649, September 2009, <https://www.rfc-editor.org/info/rfc5649>, and
+    /// http://csrc.nist.gov/encryption/kms/key-wrap.pdf.
+    /// </remarks>
+    public class Rfc5649WrapEngine
+        : IWrapper
+    {
+        // The AIV as defined in the RFC
+        private static readonly byte[] DefaultIV = { 0xa6, 0x59, 0x59, 0xa6 };
+
+        private readonly IBlockCipher m_engine;
+        private readonly byte[] m_preIV = new byte[4];
+
+        private KeyParameter m_key = null;
+        private bool m_forWrapping = true;
+
+        public Rfc5649WrapEngine(IBlockCipher engine)
+        {
+            m_engine = engine;
+        }
+
+        public virtual string AlgorithmName => m_engine.AlgorithmName;
+
+        public virtual void Init(bool forWrapping, ICipherParameters parameters)
+        {
+            m_forWrapping = forWrapping;
+
+            if (parameters is ParametersWithRandom withRandom)
+            {
+                parameters = withRandom.Parameters;
+            }
+
+            if (parameters is KeyParameter keyParameter)
+            {
+                m_key = keyParameter;
+                Array.Copy(DefaultIV, 0, m_preIV, 0, 4);
+            }
+            else if (parameters is ParametersWithIV withIV)
+            {
+                byte[] iv = withIV.GetIV();
+                if (iv.Length != 4)
+                    throw new ArgumentException("IV length not equal to 4", nameof(parameters));
+
+                m_key = (KeyParameter)withIV.Parameters;
+                Array.Copy(iv, 0, m_preIV, 0, 4);
+            }
+            else
+            {
+                // TODO Throw an exception for bad parameters?
+            }
+        }
+
+        public virtual byte[] Wrap(byte[] input, int inOff, int length)
+        {
+            if (!m_forWrapping)
+                throw new InvalidOperationException("not set for wrapping");
+
+            byte[] iv = new byte[8];
+
+            // copy in the fixed portion of the AIV
+            Array.Copy(m_preIV, 0, iv, 0, 4);
+            // copy in the MLI (size of key to be wrapped) after the AIV
+            Pack.UInt32_To_BE((uint)length, iv, 4);
+
+            // get the relevant plaintext to be wrapped
+            byte[] relevantPlaintext = new byte[length];
+            Array.Copy(input, inOff, relevantPlaintext, 0, length);
+            byte[] paddedPlaintext = PadPlaintext(relevantPlaintext);
+
+            if (paddedPlaintext.Length == 8)
+            {
+                // if the padded plaintext contains exactly 8 octets,
+                // then prepend iv and encrypt using AES in ECB mode.
+
+                // prepend the IV to the plaintext
+                byte[] paddedPlainTextWithIV = new byte[paddedPlaintext.Length + iv.Length];
+                Array.Copy(iv, 0, paddedPlainTextWithIV, 0, iv.Length);
+                Array.Copy(paddedPlaintext, 0, paddedPlainTextWithIV, iv.Length, paddedPlaintext.Length);
+
+                m_engine.Init(true, m_key);
+                for (int i = 0, blockSize = m_engine.GetBlockSize(); i < paddedPlainTextWithIV.Length; i += blockSize)
+                {
+                    m_engine.ProcessBlock(paddedPlainTextWithIV, i, paddedPlainTextWithIV, i);
+                }
+
+                return paddedPlainTextWithIV;
+            }
+            else
+            {
+                // otherwise, apply the RFC 3394 wrap to
+                // the padded plaintext with the new IV
+                Rfc3394WrapEngine wrapper = new Rfc3394WrapEngine(m_engine);
+                ParametersWithIV paramsWithIV = new ParametersWithIV(m_key, iv);
+                wrapper.Init(true, paramsWithIV);
+                return wrapper.Wrap(paddedPlaintext, 0, paddedPlaintext.Length);
+            }
+        }
+
+        public virtual byte[] Unwrap(byte[] input, int inOff, int length)
+        {
+            if (m_forWrapping)
+                throw new InvalidOperationException("not set for unwrapping");
+
+            int n = length / 8;
+
+            if ((n * 8) != length)
+                throw new InvalidCipherTextException("unwrap data must be a multiple of 8 bytes");
+
+            if (n <= 1)
+                throw new InvalidCipherTextException("unwrap data must be at least 16 bytes");
+
+            byte[] relevantCiphertext = new byte[length];
+            Array.Copy(input, inOff, relevantCiphertext, 0, length);
+            byte[] decrypted = new byte[length];
+            byte[] paddedPlaintext;
+
+            byte[] extractedAIV = new byte[8];
+
+            if (n == 2)
+            {
+                // When there are exactly two 64-bit blocks of ciphertext,
+                // they are decrypted as a single block using AES in ECB.
+                m_engine.Init(false, m_key);
+                for (int i = 0, blockSize = m_engine.GetBlockSize(); i < relevantCiphertext.Length; i += blockSize)
+                {
+                    m_engine.ProcessBlock(relevantCiphertext, i, decrypted, i);
+                }
+
+                // extract the AIV
+                Array.Copy(decrypted, 0, extractedAIV, 0, 8);
+                paddedPlaintext = new byte[decrypted.Length - 8];
+                Array.Copy(decrypted, 8, paddedPlaintext, 0, paddedPlaintext.Length);
+            }
+            else
+            {
+                // Otherwise, unwrap as per RFC 3394 but don't check IV the same way
+                decrypted = Rfc3394UnwrapNoIvCheck(input, inOff, length, extractedAIV);
+                paddedPlaintext = decrypted;
+            }
+
+            // Decompose the extracted AIV to the fixed portion and the MLI
+            byte[] extractedHighOrderAIV = new byte[4];
+            Array.Copy(extractedAIV, 0, extractedHighOrderAIV, 0, 4);
+            int mli = (int)Pack.BE_To_UInt32(extractedAIV, 4);
+
+            // Even if a check fails we still continue and check everything 
+            // else in order to avoid certain timing based side-channel attacks.
+
+            // Check the fixed portion of the AIV
+            bool isValid = Arrays.FixedTimeEquals(extractedHighOrderAIV, m_preIV);
+
+            // Check the MLI against the actual length
+            int upperBound = paddedPlaintext.Length;
+            int lowerBound = upperBound - 8;
+            if (mli <= lowerBound)
+            {
+                isValid = false;
+            }
+            if (mli > upperBound)
+            {
+                isValid = false;
+            }
+
+            // Check the number of padding zeros
+            int expectedZeros = upperBound - mli;
+            if (expectedZeros >= 8 || expectedZeros < 0)
+            {
+                // We have to pick a "typical" amount of padding to avoid timing attacks.
+                isValid = false;
+                expectedZeros = 4;
+            }
+
+            byte[] zeros = new byte[expectedZeros];
+            byte[] pad = new byte[expectedZeros];
+            Array.Copy(paddedPlaintext, paddedPlaintext.Length - expectedZeros, pad, 0, expectedZeros);
+            if (!Arrays.FixedTimeEquals(pad, zeros))
+            {
+                isValid = false;
+            }
+
+            if (!isValid)
+                throw new InvalidCipherTextException("checksum failed");
+
+            // Extract the plaintext from the padded plaintext
+            byte[] plaintext = new byte[mli];
+            Array.Copy(paddedPlaintext, 0, plaintext, 0, plaintext.Length);
+
+            return plaintext;
+        }
+
+        /**
+         * Performs steps 1 and 2 of the unwrap process defined in RFC 3394.
+         * This code is duplicated from RFC3394WrapEngine because that class
+         * will throw an error during unwrap because the IV won't match up.
+         *
+         * @param in
+         * @param inOff
+         * @param inLen
+         * @return Unwrapped data.
+         */
+        private byte[] Rfc3394UnwrapNoIvCheck(byte[] input, int inOff, int inLen, byte[] extractedAIV)
+        {
+            byte[] block = new byte[inLen - 8];
+            byte[] buf = new byte[16];
+
+            Array.Copy(input, inOff, buf, 0, 8);
+            Array.Copy(input, inOff + 8, block, 0, inLen - 8);
+
+            m_engine.Init(false, m_key);
+
+            int n = inLen / 8;
+            n = n - 1;
+
+            for (int j = 5; j >= 0; j--)
+            {
+                for (int i = n; i >= 1; i--)
+                {
+                    Array.Copy(block, 8 * (i - 1), buf, 8, 8);
+
+                    uint t = (uint)(n * j + i);
+                    for (int k = 1; t != 0U; k++)
+                    {
+                        buf[8 - k] ^= (byte)t;
+                        t >>= 8;
+                    }
+
+                    m_engine.ProcessBlock(buf, 0, buf, 0);
+
+                    Array.Copy(buf, 8, block, 8 * (i - 1), 8);
+                }
+            }
+
+            Array.Copy(buf, 0, extractedAIV, 0, 8);
+
+            return block;
+        }
+
+        /**
+         * Pads the plaintext (i.e., the key to be wrapped)
+         * as per section 4.1 of RFC 5649.
+         *
+         * @param plaintext The key being wrapped.
+         * @return The padded key.
+         */
+        private static byte[] PadPlaintext(byte[] plaintext)
+        {
+            int plaintextLength = plaintext.Length;
+            int numOfZerosToAppend = (8 - (plaintextLength % 8)) % 8;
+            byte[] paddedPlaintext = new byte[plaintextLength + numOfZerosToAppend];
+            Array.Copy(plaintext, 0, paddedPlaintext, 0, plaintextLength);
+            if (numOfZerosToAppend != 0)
+            {
+                // plaintext (i.e., key to be wrapped) does not have
+                // a multiple of 8 octet blocks so it must be padded
+                byte[] zeros = new byte[numOfZerosToAppend];
+                Array.Copy(zeros, 0, paddedPlaintext, plaintextLength, numOfZerosToAppend);
+            }
+            return paddedPlaintext;
+        }
+    }
+}