protected override unsafe byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            if (DepaddingRequired)
            {
                byte[] rented  = CryptoPool.Rent(inputCount + InputBlockSize);
                int    written = 0;

                fixed(byte *pRented = rented)
                {
                    try
                    {
                        written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), rented);
                        return(rented.AsSpan(0, written).ToArray());
                    }
                    finally
                    {
                        CryptoPool.Return(rented, clearSize: written);
                    }
                }
            }
            else
            {
#if NETSTANDARD || NETFRAMEWORK || NETCOREAPP3_0
                byte[] buffer = new byte[inputCount];
#else
                byte[] buffer = GC.AllocateUninitializedArray <byte>(inputCount);
#endif
                int written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), buffer);
                Debug.Assert(written == buffer.Length);
                return(buffer);
            }
        }
Пример #2
0
        public override unsafe int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)
        {
            Debug.Assert(input != null);
            Debug.Assert(inputOffset >= 0);
            Debug.Assert(count > 0);
            Debug.Assert((count % BlockSizeInBytes) == 0);
            Debug.Assert(input.Length - inputOffset >= count);
            Debug.Assert(output != null);
            Debug.Assert(outputOffset >= 0);
            Debug.Assert(output.Length - outputOffset >= count);

            // OpenSSL 1.1 does not allow partial overlap.
            if (input == output && inputOffset != outputOffset)
            {
                byte[] tmp     = CryptoPool.Rent(count);
                int    written = 0;

                try
                {
                    written = CipherUpdate(input, inputOffset, count, tmp, 0);
                    Buffer.BlockCopy(tmp, 0, output, outputOffset, written);
                    return(written);
                }
                finally
                {
                    CryptoPool.Return(tmp, written);
                }
            }

            return(CipherUpdate(input, inputOffset, count, output, outputOffset));
        }
Пример #3
0
        protected override unsafe byte[] UncheckedTransformFinalBlock(byte[] inputBuffer, int inputOffset, int inputCount)
        {
            if (SymmetricPadding.DepaddingRequired(PaddingMode))
            {
                byte[] rented  = CryptoPool.Rent(inputCount + InputBlockSize);
                int    written = 0;

                fixed(byte *pRented = rented)
                {
                    try
                    {
                        written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), rented);
                        return(rented.AsSpan(0, written).ToArray());
                    }
                    finally
                    {
                        CryptoPool.Return(rented, clearSize: written);
                    }
                }
            }
            else
            {
                byte[] buffer  = GC.AllocateUninitializedArray <byte>(inputCount);
                int    written = UncheckedTransformFinalBlock(inputBuffer.AsSpan(inputOffset, inputCount), buffer);
                Debug.Assert(written == buffer.Length);
                return(buffer);
            }
        }
Пример #4
0
        public static unsafe bool TryExportToPem <T>(
            T arg,
            string label,
            TryExportKeyAction <T> exporter,
            Span <char> destination,
            out int charsWritten)
        {
            int bufferSize = 4096;

            while (true)
            {
                byte[] buffer       = CryptoPool.Rent(bufferSize);
                int    bytesWritten = 0;
                bufferSize = buffer.Length;

                // Fixed to prevent GC moves.
                fixed(byte *bufferPtr = buffer)
                {
                    try
                    {
                        if (exporter(arg, buffer, out bytesWritten))
                        {
                            Span <byte> writtenSpan = new Span <byte>(buffer, 0, bytesWritten);
                            return(PemEncoding.TryWrite(label, writtenSpan, destination, out charsWritten));
                        }
                    }
                    finally
                    {
                        CryptoPool.Return(buffer, bytesWritten);
                    }

                    bufferSize = checked (bufferSize * 2);
                }
            }
        }
Пример #5
0
        private int ProcessFinalBlock(ReadOnlySpan <byte> input, Span <byte> output)
        {
            // If input and output overlap but are not the same, we need to use a
            // temp buffer since openssl doesn't seem to like partial overlaps.
            if (input.Overlaps(output, out int offset) && offset != 0)
            {
                byte[] rented  = CryptoPool.Rent(input.Length);
                int    written = 0;

                try
                {
                    written = CipherUpdate(input, rented);
                    Span <byte> outputSpan = rented.AsSpan(written);
                    CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
                    written += finalWritten;
                    rented.AsSpan(0, written).CopyTo(output);
                    return(written);
                }
                finally
                {
                    CryptoPool.Return(rented, clearSize: written);
                }
            }
            else
            {
                int         written    = CipherUpdate(input, output);
                Span <byte> outputSpan = output.Slice(written);
                CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
                written += finalWritten;
                return(written);
            }
        }
Пример #6
0
        public override unsafe int Transform(ReadOnlySpan <byte> input, Span <byte> output)
        {
            Debug.Assert(input.Length > 0);
            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);

            // OpenSSL 1.1 does not allow partial overlap.
            if (input.Overlaps(output, out int overlapOffset) && overlapOffset != 0)
            {
                byte[]      tmp     = CryptoPool.Rent(input.Length);
                Span <byte> tmpSpan = tmp;
                int         written = 0;

                try
                {
                    written = CipherUpdate(input, tmpSpan);
                    tmpSpan.Slice(0, written).CopyTo(output);
                    return(written);
                }
                finally
                {
                    CryptoPool.Return(tmp, written);
                }
            }

            return(CipherUpdate(input, output));
        }
Пример #7
0
        public override unsafe bool TransformOneShot(ReadOnlySpan <byte> input, Span <byte> output, out int bytesWritten)
        {
            if (input.Length % PaddingSizeBytes != 0)
            {
                throw new CryptographicException(SR.Cryptography_PartialBlock);
            }

            // If there is no padding that needs to be removed, and the output buffer is large enough to hold
            // the resulting plaintext, we can decrypt directly in to the output buffer.
            // We do not do this for modes that require padding removal.
            //
            // This is not done for padded ciphertexts because we don't know if the padding is valid
            // until it's been decrypted. We don't want to decrypt in to a user-supplied buffer and then throw
            // a padding exception after we've already filled the user buffer with plaintext. We should only
            // release the plaintext to the caller once we know the padding is valid.
            if (!SymmetricPadding.DepaddingRequired(PaddingMode))
            {
                if (output.Length >= input.Length)
                {
                    bytesWritten = BasicSymmetricCipher.TransformFinal(input, output);
                    return(true);
                }

                // If no padding is going to be removed, we know the buffer is too small and we can bail out.
                bytesWritten = 0;
                return(false);
            }

            byte[]      rentedBuffer    = CryptoPool.Rent(input.Length);
            Span <byte> buffer          = rentedBuffer.AsSpan(0, input.Length);
            Span <byte> decryptedBuffer = default;

            fixed(byte *pBuffer = buffer)
            {
                try
                {
                    int transformWritten = BasicSymmetricCipher.TransformFinal(input, buffer);
                    decryptedBuffer = buffer.Slice(0, transformWritten);

                    // validates padding
                    int unpaddedLength = SymmetricPadding.GetPaddingLength(decryptedBuffer, PaddingMode, InputBlockSize);

                    if (unpaddedLength > output.Length)
                    {
                        bytesWritten = 0;
                        return(false);
                    }

                    decryptedBuffer.Slice(0, unpaddedLength).CopyTo(output);
                    bytesWritten = unpaddedLength;
                    return(true);
                }
                finally
                {
                    CryptographicOperations.ZeroMemory(decryptedBuffer);
                    CryptoPool.Return(rentedBuffer, clearSize: 0); // ZeroMemory clears the part of the buffer that was written to.
                }
            }
        }
Пример #8
0
        public static byte[] DecodeOctetString(ReadOnlyMemory <byte> encodedOctets)
        {
            // Read using BER because the CMS specification says the encoding is BER.
            AsnReader reader = new AsnReader(encodedOctets, AsnEncodingRules.BER);

            const int   ArbitraryStackLimit = 256;
            Span <byte> tmp = stackalloc byte[ArbitraryStackLimit];
            // Use stackalloc 0 so data can later hold a slice of tmp.
            ReadOnlySpan <byte> data = stackalloc byte[0];

            byte[] poolBytes = null;

            try
            {
                if (!reader.TryReadPrimitiveOctetStringBytes(out var contents))
                {
                    if (reader.TryCopyOctetStringBytes(tmp, out int bytesWritten))
                    {
                        data = tmp.Slice(0, bytesWritten);
                    }
                    else
                    {
                        poolBytes = CryptoPool.Rent(reader.PeekContentBytes().Length);

                        if (!reader.TryCopyOctetStringBytes(poolBytes, out bytesWritten))
                        {
                            Debug.Fail("TryCopyOctetStringBytes failed with a provably-large-enough buffer");
                            throw new CryptographicException();
                        }

                        data = new ReadOnlySpan <byte>(poolBytes, 0, bytesWritten);
                    }
                }
                else
                {
                    data = contents.Span;
                }

                reader.ThrowIfNotEmpty();

                return(data.ToArray());
            }
            finally
            {
                if (poolBytes != null)
                {
                    CryptoPool.Return(poolBytes, data.Length);
                }
            }
        }
        public override int Transform(ReadOnlySpan <byte> input, Span <byte> output)
        {
            Debug.Assert(input.Length > 0);
            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);

            int numBytesWritten = 0;

            // BCryptEncrypt and BCryptDecrypt can do in place encryption, but if the buffers overlap
            // the offset must be zero. In that case, we need to copy to a temporary location.
            if (input.Overlaps(output, out int offset) && offset != 0)
            {
                byte[] rented = CryptoPool.Rent(output.Length);

                try
                {
                    numBytesWritten = BCryptTransform(input, rented);
                    rented.AsSpan(0, numBytesWritten).CopyTo(output);
                }
                finally
                {
                    CryptoPool.Return(rented, clearSize: numBytesWritten);
                }
            }
            else
            {
                numBytesWritten = BCryptTransform(input, output);
            }

            if (numBytesWritten != input.Length)
            {
                // CNG gives us no way to tell BCryptDecrypt() that we're decrypting the final block, nor is it performing any
                // padding /depadding for us. So there's no excuse for a provider to hold back output for "future calls." Though
                // this isn't technically our problem to detect, we might as well detect it now for easier diagnosis.
                throw new CryptographicException(SR.Cryptography_UnexpectedTransformTruncation);
            }

            return(numBytesWritten);

            int BCryptTransform(ReadOnlySpan <byte> input, Span <byte> output)
            {
                return(_encrypting ?
                       Interop.BCrypt.BCryptEncrypt(_hKey, input, _currentIv, output) :
                       Interop.BCrypt.BCryptDecrypt(_hKey, input, _currentIv, output));
            }
        }
Пример #10
0
        public int TransformFinal(ReadOnlySpan <byte> input, Span <byte> output)
        {
#if DEBUG
            if (_isFinalized)
            {
                Debug.Fail("Cipher was reused without being reset.");
                throw new CryptographicException();
            }

            _isFinalized = true;
#endif

            // If input and output overlap but are not the same, we need to use a
            // temp buffer since openssl doesn't seem to like partial overlaps.
            if (input.Overlaps(output, out int offset) && offset != 0)
            {
                byte[] rented  = CryptoPool.Rent(input.Length);
                int    written = 0;

                try
                {
                    written = CipherUpdate(input, rented);
                    Span <byte> outputSpan = rented.AsSpan(written);
                    CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
                    written += finalWritten;
                    rented.AsSpan(0, written).CopyTo(output);
                    return(written);
                }
                finally
                {
                    CryptoPool.Return(rented, clearSize: written);
                }
            }
            else
            {
                int         written    = CipherUpdate(input, output);
                Span <byte> outputSpan = output.Slice(written);
                CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputSpan, out int finalWritten));
                written += finalWritten;
                return(written);
            }
        }
Пример #11
0
        public int TransformFinal(ReadOnlySpan <byte> input, Span <byte> output)
        {
#if DEBUG
            if (_isFinalized)
            {
                Debug.Fail("Cipher was reused without being reset.");
                throw new CryptographicException();
            }

            _isFinalized = true;
#endif

            Debug.Assert((input.Length % PaddingSizeInBytes) == 0);
            Debug.Assert(input.Length <= output.Length);

            int written = 0;

            if (input.Overlaps(output, out int offset) && offset != 0)
            {
                byte[] rented = CryptoPool.Rent(output.Length);

                try
                {
                    written = ProcessFinalBlock(input, rented);
                    rented.AsSpan(0, written).CopyTo(output);
                }
                finally
                {
                    CryptoPool.Return(rented, clearSize: written);
                }
            }
            else
            {
                written = ProcessFinalBlock(input, output);
            }

            return(written);
        }
Пример #12
0
        public static unsafe bool TryExportToEncryptedPem <T>(
            T arg,
            ReadOnlySpan <char> password,
            PbeParameters pbeParameters,
            TryExportEncryptedKeyAction <T> exporter,
            Span <char> destination,
            out int charsWritten)
        {
            int bufferSize = 4096;

            while (true)
            {
                byte[] buffer       = CryptoPool.Rent(bufferSize);
                int    bytesWritten = 0;
                bufferSize = buffer.Length;

                // Fixed to prevent GC moves.
                fixed(byte *bufferPtr = buffer)
                {
                    try
                    {
                        if (exporter(arg, password, pbeParameters, buffer, out bytesWritten))
                        {
                            Span <byte> writtenSpan = new Span <byte>(buffer, 0, bytesWritten);
                            return(PemEncoding.TryWrite(PemLabels.EncryptedPkcs8PrivateKey, writtenSpan, destination, out charsWritten));
                        }
                    }
                    finally
                    {
                        CryptoPool.Return(buffer, bytesWritten);
                    }

                    bufferSize = checked (bufferSize * 2);
                }
            }
        }
Пример #13
0
        protected override unsafe int UncheckedTransformFinalBlock(ReadOnlySpan <byte> inputBuffer, Span <byte> outputBuffer)
        {
            // We can't complete decryption on a partial block
            if (inputBuffer.Length % PaddingSizeBytes != 0)
            {
                throw new CryptographicException(SR.Cryptography_PartialBlock);
            }

            //
            // If we have postponed cipher bits from the prior round, copy that into the decryption buffer followed by the input data.
            // Otherwise the decryption buffer is just the input data.
            //

            ReadOnlySpan <byte> inputCiphertext;
            Span <byte>         ciphertext;

            byte[]? rentedCiphertext = null;
            int rentedCiphertextSize = 0;

            try
            {
                if (_heldoverCipher == null)
                {
                    rentedCiphertextSize = inputBuffer.Length;
                    rentedCiphertext     = CryptoPool.Rent(inputBuffer.Length);
                    ciphertext           = rentedCiphertext.AsSpan(0, inputBuffer.Length);
                    inputCiphertext      = inputBuffer;
                }
                else
                {
                    rentedCiphertextSize = _heldoverCipher.Length + inputBuffer.Length;
                    rentedCiphertext     = CryptoPool.Rent(rentedCiphertextSize);
                    ciphertext           = rentedCiphertext.AsSpan(0, rentedCiphertextSize);
                    _heldoverCipher.AsSpan().CopyTo(ciphertext);
                    inputBuffer.CopyTo(ciphertext.Slice(_heldoverCipher.Length));

                    // Decrypt in-place
                    inputCiphertext = ciphertext;
                }

                int unpaddedLength = 0;

                fixed(byte *pCiphertext = ciphertext)
                {
                    // Decrypt the data, then strip the padding to get the final decrypted data. Note that even if the cipherText length is 0, we must
                    // invoke TransformFinal() so that the cipher object knows to reset for the next cipher operation.
                    int         decryptWritten = BasicSymmetricCipher.TransformFinal(inputCiphertext, ciphertext);
                    Span <byte> decryptedBytes = ciphertext.Slice(0, decryptWritten);

                    if (decryptedBytes.Length > 0)
                    {
                        unpaddedLength = SymmetricPadding.GetPaddingLength(decryptedBytes, PaddingMode, InputBlockSize);
                        decryptedBytes.Slice(0, unpaddedLength).CopyTo(outputBuffer);
                    }
                }

                Reset();
                return(unpaddedLength);
            }
            finally
            {
                if (rentedCiphertext != null)
                {
                    CryptoPool.Return(rentedCiphertext, clearSize: rentedCiphertextSize);
                }
            }
        }
Пример #14
0
        public static unsafe bool OneShotDecrypt(
            ILiteSymmetricCipher cipher,
            PaddingMode paddingMode,
            ReadOnlySpan <byte> input,
            Span <byte> output,
            out int bytesWritten)
        {
            if (input.Length % cipher.PaddingSizeInBytes != 0)
            {
                throw new CryptographicException(SR.Cryptography_PartialBlock);
            }

            // If there is no padding that needs to be removed, and the output buffer is large enough to hold
            // the resulting plaintext, we can decrypt directly in to the output buffer.
            // We do not do this for modes that require padding removal.
            //
            // This is not done for padded ciphertexts because we don't know if the padding is valid
            // until it's been decrypted. We don't want to decrypt in to a user-supplied buffer and then throw
            // a padding exception after we've already filled the user buffer with plaintext. We should only
            // release the plaintext to the caller once we know the padding is valid.
            if (!SymmetricPadding.DepaddingRequired(paddingMode))
            {
                if (output.Length >= input.Length)
                {
                    bytesWritten = cipher.TransformFinal(input, output);
                    return(true);
                }

                // If no padding is going to be removed, we know the buffer is too small and we can bail out.
                bytesWritten = 0;
                return(false);
            }

            byte[]      rentedBuffer    = CryptoPool.Rent(input.Length);
            Span <byte> buffer          = rentedBuffer.AsSpan(0, input.Length);
            Span <byte> decryptedBuffer = default;

            fixed(byte *pBuffer = buffer)
            {
                try
                {
                    int transformWritten = cipher.TransformFinal(input, buffer);
                    decryptedBuffer = buffer.Slice(0, transformWritten);

                    // This intentionally passes in BlockSizeInBytes instead of PaddingSizeInBytes. This is so that
                    // "extra padded" CFB data can still be decrypted. The .NET Framework always padded CFB8 to the
                    // block size, not the feedback size. We want the one-shot to be able to continue to decrypt
                    // those ciphertexts, so for CFB8 we are more lenient on the number of allowed padding bytes.
                    int unpaddedLength = SymmetricPadding.GetPaddingLength(decryptedBuffer, paddingMode, cipher.BlockSizeInBytes); // validates padding

                    if (unpaddedLength > output.Length)
                    {
                        bytesWritten = 0;
                        return(false);
                    }

                    decryptedBuffer.Slice(0, unpaddedLength).CopyTo(output);
                    bytesWritten = unpaddedLength;
                    return(true);
                }
                finally
                {
                    CryptographicOperations.ZeroMemory(decryptedBuffer);
                    CryptoPool.Return(rentedBuffer, clearSize: 0); // ZeroMemory clears the part of the buffer that was written to.
                }
            }
        }