| | 1 | | namespace NLightning.Infrastructure.Transport.Encryption; |
| | 2 | |
|
| | 3 | | using Common.Utils; |
| | 4 | | using Domain.Crypto.Constants; |
| | 5 | | using Domain.Exceptions; |
| | 6 | | using Domain.Transport; |
| | 7 | | using Handshake.States; |
| | 8 | | using Protocol.Constants; |
| | 9 | |
|
| | 10 | | /// <inheritdoc/> |
| | 11 | | internal sealed class Transport : ITransport |
| | 12 | | { |
| | 13 | | private readonly bool _initiator; |
| | 14 | | private readonly CipherState _sendingKey; |
| | 15 | | private readonly CipherState _receivingKey; |
| | 16 | |
|
| | 17 | | private bool _disposed; |
| | 18 | |
|
| 32 | 19 | | public Transport(bool initiator, CipherState c1, CipherState c2) |
| | 20 | | { |
| 32 | 21 | | ArgumentNullException.ThrowIfNull(c1, nameof(c1)); |
| 32 | 22 | | ArgumentNullException.ThrowIfNull(c2, nameof(c2)); |
| | 23 | |
|
| 32 | 24 | | _initiator = initiator; |
| 32 | 25 | | _sendingKey = c1; |
| 32 | 26 | | _receivingKey = c2; |
| 32 | 27 | | } |
| | 28 | |
|
| | 29 | | /// <inheritdoc/> |
| | 30 | | /// <exception cref="ObjectDisposedException">Thrown if the current instance has already been disposed.</exception> |
| | 31 | | /// <exception cref="InvalidOperationException">Thrown if the responder has attempted to write a message to a one-wa |
| | 32 | | /// <exception cref="ArgumentException">Thrown if the encrypted payload was greater than <see cref="ProtocolConstant |
| | 33 | | public int WriteMessage(ReadOnlySpan<byte> payload, Span<byte> messageBuffer) |
| | 34 | | { |
| 4008 | 35 | | ExceptionUtils.ThrowIfDisposed(_disposed, nameof(Transport)); |
| | 36 | |
|
| | 37 | | // Serialize length into 2 bytes encoded as a big-endian integer |
| 4008 | 38 | | var l = BitConverter.GetBytes((ushort)payload.Length); |
| 4008 | 39 | | if (BitConverter.IsLittleEndian) |
| | 40 | | { |
| 4008 | 41 | | Array.Reverse(l); |
| | 42 | | } |
| | 43 | |
|
| | 44 | | // Encrypt the payload length into the message buffer |
| 4008 | 45 | | var lcLen = WriteMessagePart(l, messageBuffer); |
| | 46 | |
|
| | 47 | | // Encrypt the payload into the message buffer |
| 4008 | 48 | | var mLen = WriteMessagePart(payload, messageBuffer[lcLen..]); |
| | 49 | |
|
| 4008 | 50 | | return lcLen + mLen; |
| | 51 | | } |
| | 52 | |
|
| | 53 | | /// <inheritdoc/> |
| | 54 | | public int ReadMessageLength(ReadOnlySpan<byte> lc) |
| | 55 | | { |
| 4008 | 56 | | ExceptionUtils.ThrowIfDisposed(_disposed, nameof(Transport)); |
| | 57 | |
|
| 4008 | 58 | | if (lc.Length != ProtocolConstants.MESSAGE_HEADER_SIZE) |
| | 59 | | { |
| 0 | 60 | | throw new ArgumentException($"Lightning Message Header must be {ProtocolConstants.MESSAGE_HEADER_SIZE} bytes |
| | 61 | | } |
| | 62 | |
|
| | 63 | | // Decrypt the payload length from the message buffer |
| 4008 | 64 | | var l = new byte[2]; |
| | 65 | | // Bytes read should always be 2 |
| 4008 | 66 | | if (ReadMessagePart(lc, l) != 2) |
| | 67 | | { |
| 0 | 68 | | throw new ConnectionException("Message Length was invalid."); |
| | 69 | | } |
| | 70 | |
|
| 4008 | 71 | | if (BitConverter.IsLittleEndian) |
| | 72 | | { |
| 4008 | 73 | | Array.Reverse(l); |
| | 74 | | } |
| 4008 | 75 | | return BitConverter.ToUInt16(l, 0) + CryptoConstants.CHACHA20_POLY1305_TAG_LEN; |
| | 76 | | } |
| | 77 | |
|
| | 78 | | /// <inheritdoc/> |
| | 79 | | public int ReadMessagePayload(ReadOnlySpan<byte> message, Span<byte> payloadBuffer) |
| | 80 | | { |
| 4008 | 81 | | ExceptionUtils.ThrowIfDisposed(_disposed, nameof(Transport)); |
| | 82 | |
|
| | 83 | | // Decrypt the payload from the message buffer |
| 4008 | 84 | | return ReadMessagePart(message, payloadBuffer); |
| | 85 | | } |
| | 86 | |
|
| | 87 | | /// <summary> |
| | 88 | | /// Encrypts the <paramref name="payload"/> and writes the result into <paramref name="messageBuffer"/>. |
| | 89 | | /// </summary> |
| | 90 | | /// <param name="payload">The payload to encrypt.</param> |
| | 91 | | /// <param name="messageBuffer">The buffer for the encrypted message.</param> |
| | 92 | | /// <returns>The ciphertext size in bytes.</returns> |
| | 93 | | /// <exception cref="ObjectDisposedException"> |
| | 94 | | /// Thrown if the current instance has already been disposed. |
| | 95 | | /// </exception> |
| | 96 | | /// <exception cref="InvalidOperationException"> |
| | 97 | | /// Thrown if the responder has attempted to write a message to a one-way stream. |
| | 98 | | /// </exception> |
| | 99 | | /// <exception cref="ArgumentException"> |
| | 100 | | /// Thrown if the encrypted payload was greater than <see cref="ProtocolConstants.MAX_MESSAGE_LENGTH"/> |
| | 101 | | /// bytes in length, or if the output buffer did not have enough space to hold the ciphertext. |
| | 102 | | /// </exception> |
| | 103 | | private int WriteMessagePart(ReadOnlySpan<byte> payload, Span<byte> messageBuffer) |
| | 104 | | { |
| 8016 | 105 | | if (payload.Length + CryptoConstants.CHACHA20_POLY1305_TAG_LEN > ProtocolConstants.MAX_MESSAGE_LENGTH) |
| | 106 | | { |
| 0 | 107 | | throw new ArgumentException($"Noise message must be less than or equal to {ProtocolConstants.MAX_MESSAGE_LEN |
| | 108 | | } |
| | 109 | |
|
| 8016 | 110 | | if (payload.Length + CryptoConstants.CHACHA20_POLY1305_TAG_LEN > messageBuffer.Length) |
| | 111 | | { |
| 0 | 112 | | throw new ArgumentException("Message buffer does not have enough space to hold the ciphertext."); |
| | 113 | | } |
| | 114 | |
|
| 8016 | 115 | | var cipher = _initiator ? _sendingKey : _receivingKey; |
| 8016 | 116 | | if (!cipher.HasKeys()) |
| | 117 | | { |
| 0 | 118 | | throw new InvalidOperationException("Cipher is missing keys."); |
| | 119 | | } |
| | 120 | |
|
| 8016 | 121 | | return cipher.Encrypt(payload, messageBuffer); |
| | 122 | | } |
| | 123 | |
|
| | 124 | | /// <summary> |
| | 125 | | /// Decrypts the <paramref name="message"/> and writes the result into <paramref name="payloadBuffer"/>. |
| | 126 | | /// </summary> |
| | 127 | | /// <param name="message">The message to decrypt.</param> |
| | 128 | | /// <param name="payloadBuffer">The buffer for the decrypted payload.</param> |
| | 129 | | /// <returns>The plaintext size in bytes.</returns> |
| | 130 | | /// <exception cref="ObjectDisposedException"> |
| | 131 | | /// Thrown if the current instance has already been disposed. |
| | 132 | | /// </exception> |
| | 133 | | /// <exception cref="InvalidOperationException"> |
| | 134 | | /// Thrown if the initiator has attempted to read a message from a one-way stream. |
| | 135 | | /// </exception> |
| | 136 | | /// <exception cref="ArgumentException"> |
| | 137 | | /// Thrown if the message was greater than <see cref="ProtocolConstants.MAX_MESSAGE_LENGTH"/> |
| | 138 | | /// bytes in length, or if the output buffer did not have enough space to hold the plaintext. |
| | 139 | | /// </exception> |
| | 140 | | /// <exception cref="System.Security.Cryptography.CryptographicException"> |
| | 141 | | /// Thrown if the decryption of the message has failed. |
| | 142 | | /// </exception> |
| | 143 | | private int ReadMessagePart(ReadOnlySpan<byte> message, Span<byte> payloadBuffer) |
| | 144 | | { |
| 8016 | 145 | | switch (message.Length) |
| | 146 | | { |
| | 147 | | case > ProtocolConstants.MAX_MESSAGE_LENGTH: |
| 0 | 148 | | throw new ArgumentException($"Noise message must be less than or equal to {ProtocolConstants.MAX_MESSAGE |
| | 149 | | case < CryptoConstants.CHACHA20_POLY1305_TAG_LEN: |
| 0 | 150 | | throw new ArgumentException($"Noise message must be greater than or equal to {CryptoConstants.CHACHA20_P |
| | 151 | | } |
| | 152 | |
|
| 8016 | 153 | | if (message.Length - CryptoConstants.CHACHA20_POLY1305_TAG_LEN > payloadBuffer.Length) |
| | 154 | | { |
| 0 | 155 | | throw new ArgumentException("Payload buffer does not have enough space to hold the plaintext."); |
| | 156 | | } |
| | 157 | |
|
| 8016 | 158 | | var cipher = _initiator ? _receivingKey : _sendingKey; |
| 8016 | 159 | | if (!cipher.HasKeys()) |
| | 160 | | { |
| 0 | 161 | | throw new InvalidOperationException("Cipher is missing keys."); |
| | 162 | | } |
| | 163 | |
|
| 8016 | 164 | | return cipher.Decrypt(message, payloadBuffer); |
| | 165 | | } |
| | 166 | |
|
| | 167 | | public void Dispose() |
| | 168 | | { |
| 8 | 169 | | if (_disposed) |
| | 170 | | { |
| 0 | 171 | | return; |
| | 172 | | } |
| | 173 | |
|
| 8 | 174 | | _sendingKey.Dispose(); |
| 8 | 175 | | _receivingKey.Dispose(); |
| | 176 | |
|
| 8 | 177 | | _disposed = true; |
| 8 | 178 | | } |
| | 179 | | } |