1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 using System; 6 using System.Diagnostics; 7 using System.Security.Cryptography; 8 using Microsoft.Win32.SafeHandles; 9 10 namespace Internal.Cryptography 11 { 12 internal class OpenSslCipher : BasicSymmetricCipher 13 { 14 private readonly bool _encrypting; 15 private SafeEvpCipherCtxHandle _ctx; 16 OpenSslCipher(IntPtr algorithm, CipherMode cipherMode, int blockSizeInBytes, byte[] key, int effectiveKeyLength, byte[] iv, bool encrypting)17 public OpenSslCipher(IntPtr algorithm, CipherMode cipherMode, int blockSizeInBytes, byte[] key, int effectiveKeyLength, byte[] iv, bool encrypting) 18 : base(cipherMode.GetCipherIv(iv), blockSizeInBytes) 19 { 20 Debug.Assert(algorithm != IntPtr.Zero); 21 22 _encrypting = encrypting; 23 24 OpenKey(algorithm, key, effectiveKeyLength); 25 } 26 Dispose(bool disposing)27 protected override void Dispose(bool disposing) 28 { 29 if (disposing) 30 { 31 if (_ctx != null) 32 { 33 _ctx.Dispose(); 34 _ctx = null; 35 } 36 } 37 38 base.Dispose(disposing); 39 } 40 Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)41 public override unsafe int Transform(byte[] input, int inputOffset, int count, byte[] output, int outputOffset) 42 { 43 Debug.Assert(input != null); 44 Debug.Assert(inputOffset >= 0); 45 Debug.Assert(count > 0); 46 Debug.Assert((count % BlockSizeInBytes) == 0); 47 Debug.Assert(input.Length - inputOffset >= count); 48 Debug.Assert(output != null); 49 Debug.Assert(outputOffset >= 0); 50 Debug.Assert(output.Length - outputOffset >= count); 51 52 return CipherUpdate(input, inputOffset, count, output, outputOffset); 53 } 54 TransformFinal(byte[] input, int inputOffset, int count)55 public override byte[] TransformFinal(byte[] input, int inputOffset, int count) 56 { 57 Debug.Assert(input != null); 58 Debug.Assert(inputOffset >= 0); 59 Debug.Assert(count >= 0); 60 Debug.Assert((count % BlockSizeInBytes) == 0); 61 Debug.Assert(input.Length - inputOffset >= count); 62 63 byte[] output = ProcessFinalBlock(input, inputOffset, count); 64 Reset(); 65 return output; 66 } 67 ProcessFinalBlock(byte[] input, int inputOffset, int count)68 private unsafe byte[] ProcessFinalBlock(byte[] input, int inputOffset, int count) 69 { 70 byte[] output = new byte[count]; 71 int outputBytes = CipherUpdate(input, inputOffset, count, output, 0); 72 73 fixed (byte* outputStart = output) 74 { 75 byte* outputCurrent = outputStart + outputBytes; 76 77 int bytesWritten; 78 CheckBoolReturn(Interop.Crypto.EvpCipherFinalEx(_ctx, outputCurrent, out bytesWritten)); 79 outputBytes += bytesWritten; 80 } 81 82 if (outputBytes == output.Length) 83 { 84 return output; 85 } 86 87 if (outputBytes == 0) 88 { 89 return Array.Empty<byte>(); 90 } 91 92 byte[] userData = new byte[outputBytes]; 93 Buffer.BlockCopy(output, 0, userData, 0, outputBytes); 94 return userData; 95 } 96 CipherUpdate(byte[] input, int inputOffset, int count, byte[] output, int outputOffset)97 private unsafe int CipherUpdate(byte[] input, int inputOffset, int count, byte[] output, int outputOffset) 98 { 99 bool status; 100 int bytesWritten; 101 102 fixed (byte* inputStart = input) 103 fixed (byte* outputStart = output) 104 { 105 byte* inputCurrent = inputStart + inputOffset; 106 byte* outputCurrent = outputStart + outputOffset; 107 108 status = Interop.Crypto.EvpCipherUpdate( 109 _ctx, 110 outputCurrent, 111 out bytesWritten, 112 inputCurrent, 113 count); 114 } 115 116 CheckBoolReturn(status); 117 return bytesWritten; 118 } 119 OpenKey(IntPtr algorithm, byte[] key, int effectiveKeyLength)120 private void OpenKey(IntPtr algorithm, byte[] key, int effectiveKeyLength) 121 { 122 _ctx = Interop.Crypto.EvpCipherCreate( 123 algorithm, 124 key, 125 key.Length * 8, 126 effectiveKeyLength, 127 IV, 128 _encrypting ? 1 : 0); 129 130 Interop.Crypto.CheckValidOpenSslHandle(_ctx); 131 132 // OpenSSL will happily do PKCS#7 padding for us, but since we support padding modes 133 // that it doesn't (PaddingMode.Zeros) we'll just always pad the blocks ourselves. 134 CheckBoolReturn(Interop.Crypto.EvpCipherCtxSetPadding(_ctx, 0)); 135 } 136 Reset()137 private void Reset() 138 { 139 bool status = Interop.Crypto.EvpCipherReset(_ctx); 140 141 CheckBoolReturn(status); 142 } 143 CheckBoolReturn(bool returnValue)144 private static void CheckBoolReturn(bool returnValue) 145 { 146 if (!returnValue) 147 { 148 throw Interop.Crypto.CreateOpenSslCryptographicException(); 149 } 150 } 151 } 152 } 153