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