diff --git a/DotBased/Utilities/Cryptography.cs b/DotBased/Utilities/Cryptography.cs new file mode 100644 index 0000000..f0e6192 --- /dev/null +++ b/DotBased/Utilities/Cryptography.cs @@ -0,0 +1,129 @@ +using System.Security.Cryptography; + +namespace DotBased.Utilities; + +public static class Cryptography +{ + /* + * https://gist.github.com/therightstuff/aa65356e95f8d0aae888e9f61aa29414 + */ + public static Result ExportPublicKeyToPem(RSACryptoServiceProvider csp) + { + var outputStream = new StringWriter(); + var parameters = csp.ExportParameters(false); + if (parameters.Exponent == null || parameters.Modulus == null) + return Result.Failed("RSAParameters are empty!"); + using (var stream = new MemoryStream()) + { + var writer = new BinaryWriter(stream); + writer.Write((byte)0x30); // SEQUENCE + using (var innerStream = new MemoryStream()) + { + var innerWriter = new BinaryWriter(innerStream); + innerWriter.Write((byte)0x30); // SEQUENCE + EncodeLength(innerWriter, 13); + innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER + byte[] rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 }; + EncodeLength(innerWriter, rsaEncryptionOid.Length); + innerWriter.Write(rsaEncryptionOid); + innerWriter.Write((byte)0x05); // NULL + EncodeLength(innerWriter, 0); + innerWriter.Write((byte)0x03); // BIT STRING + using (var bitStringStream = new MemoryStream()) + { + var bitStringWriter = new BinaryWriter(bitStringStream); + bitStringWriter.Write((byte)0x00); // # of unused bits + bitStringWriter.Write((byte)0x30); // SEQUENCE + using (var paramsStream = new MemoryStream()) + { + var paramsWriter = new BinaryWriter(paramsStream); + EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus + EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent + var paramsLength = (int)paramsStream.Length; + EncodeLength(bitStringWriter, paramsLength); + bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength); + } + + int bitStringLength = (int)bitStringStream.Length; + EncodeLength(innerWriter, bitStringLength); + innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength); + } + + int length = (int)innerStream.Length; + EncodeLength(writer, length); + writer.Write(innerStream.GetBuffer(), 0, length); + } + + char[] base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray(); + // WriteLine terminates with \r\n, we want only \n + outputStream.Write("-----BEGIN PUBLIC KEY-----\n"); + for (int i = 0; i < base64.Length; i += 64) + { + outputStream.Write(base64, i, Math.Min(64, base64.Length - i)); + outputStream.Write("\n"); + } + + outputStream.Write("-----END PUBLIC KEY-----"); + } + + return Result.Ok(outputStream.ToString()); + } + + private static void EncodeLength(BinaryWriter stream, int length) + { + switch (length) + { + case < 0: + throw new ArgumentOutOfRangeException(nameof(length), "Length must be non-negative"); + case < 0x80: + // Short form + stream.Write((byte)length); + break; + default: + { + // Long form + int temp = length; + int bytesRequired = 0; + while (temp > 0) + { + temp >>= 8; + bytesRequired++; + } + stream.Write((byte)(bytesRequired | 0x80)); + for (int i = bytesRequired - 1; i >= 0; i--) + { + stream.Write((byte)(length >> (8 * i) & 0xff)); + } + break; + } + } + } + + private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true) + { + stream.Write((byte)0x02); // INTEGER + int prefixZeros = value.TakeWhile(t => t == 0).Count(); + if (value.Length - prefixZeros == 0) + { + EncodeLength(stream, 1); + stream.Write((byte)0); + } + else + { + if (forceUnsigned && value[prefixZeros] > 0x7f) + { + // Add a prefix zero to force unsigned if the MSB is 1 + EncodeLength(stream, value.Length - prefixZeros + 1); + stream.Write((byte)0); + } + else + { + EncodeLength(stream, value.Length - prefixZeros); + } + for (int i = prefixZeros; i < value.Length; i++) + { + stream.Write(value[i]); + } + } + } +} \ No newline at end of file