mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-31 01:40:42 +00:00
PPAP integration
This commit is contained in:
@@ -0,0 +1,626 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Esiur.Security.Authority.Providers.Ppap;
|
||||
|
||||
/// <summary>
|
||||
/// Stable message codes for the ppap-mlkem768-v1 authentication and rotation protocols.
|
||||
/// Authentication payloads are canonical byte arrays, not runtime-specific object graphs.
|
||||
/// </summary>
|
||||
public enum PpapMessageType : byte
|
||||
{
|
||||
ClientHello = 1,
|
||||
ServerHello = 2,
|
||||
InitiatorProof = 3,
|
||||
ResponderProof = 4,
|
||||
InitiatorFinished = 5,
|
||||
|
||||
RotationStart = 16,
|
||||
RotationOffer = 17,
|
||||
RotationChallenge = 18,
|
||||
RotationProof = 19,
|
||||
RotationCommit = 20,
|
||||
RotationCommitAck = 21,
|
||||
RotationDone = 22,
|
||||
}
|
||||
|
||||
public enum PpapIdentityRole : byte
|
||||
{
|
||||
Initiator = 1,
|
||||
Responder = 2,
|
||||
}
|
||||
|
||||
internal sealed class PpapRegistrationDescriptor
|
||||
{
|
||||
public PpapIdentityKind Kind { get; }
|
||||
public long Version { get; }
|
||||
public byte[] Nonce { get; }
|
||||
public PpapKdfProfile KdfProfile { get; }
|
||||
|
||||
public PpapRegistrationDescriptor(PpapIdentityKind kind, long version,
|
||||
byte[] nonce, PpapKdfProfile kdfProfile)
|
||||
{
|
||||
if (version < 1)
|
||||
throw new InvalidDataException("Invalid registration version.");
|
||||
Kind = kind;
|
||||
Version = version;
|
||||
KdfProfile = kdfProfile;
|
||||
|
||||
if (kind == PpapIdentityKind.PasswordDerived)
|
||||
{
|
||||
if (nonce == null || nonce.Length != PpapProtocol.RegistrationNonceLength
|
||||
|| kdfProfile == null)
|
||||
throw new InvalidDataException("Invalid password registration descriptor.");
|
||||
Nonce = (byte[])nonce.Clone();
|
||||
}
|
||||
else if (kind == PpapIdentityKind.StaticMlKem)
|
||||
{
|
||||
if ((nonce != null && nonce.Length != 0) || kdfProfile != null)
|
||||
throw new InvalidDataException("Invalid static-key registration descriptor.");
|
||||
Nonce = Array.Empty<byte>();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidDataException("Unknown PPAP identity kind.");
|
||||
}
|
||||
}
|
||||
|
||||
public static PpapRegistrationDescriptor FromRecord(PpapRegistrationRecord record)
|
||||
{
|
||||
if (record == null)
|
||||
throw new ArgumentNullException(nameof(record));
|
||||
return new PpapRegistrationDescriptor(record.Kind, record.Version,
|
||||
record.NonceBytes, record.KdfProfile);
|
||||
}
|
||||
}
|
||||
|
||||
internal static partial class PpapWire
|
||||
{
|
||||
static readonly byte[] Magic = { (byte)'P', (byte)'P', (byte)'A', (byte)'P' };
|
||||
|
||||
internal sealed class ClientHello
|
||||
{
|
||||
public AuthenticationMode Mode;
|
||||
public string Domain;
|
||||
public byte[] EphemeralKey;
|
||||
public byte[] InitiatorMask;
|
||||
}
|
||||
|
||||
internal sealed class ServerHello
|
||||
{
|
||||
public byte[] EphemeralCiphertext;
|
||||
public byte[] ResponderMask;
|
||||
public byte[] MaskedResponderIdentity;
|
||||
}
|
||||
|
||||
internal sealed class InitiatorProof
|
||||
{
|
||||
public byte[] MaskedInitiatorIdentity;
|
||||
public byte[] ResponderCiphertext;
|
||||
public PpapRegistrationDescriptor ResponderRegistration;
|
||||
}
|
||||
|
||||
internal sealed class ResponderProof
|
||||
{
|
||||
public byte[] InitiatorCiphertext;
|
||||
public PpapRegistrationDescriptor InitiatorRegistration;
|
||||
public byte[] Finished;
|
||||
public byte[] TranscriptCore;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeClientHello(AuthenticationMode mode, string domain,
|
||||
byte[] ephemeralKey, byte[] initiatorMask)
|
||||
{
|
||||
ValidateMode(mode);
|
||||
PpapCryptography.ValidatePublicKey(ephemeralKey);
|
||||
ValidateExact(initiatorMask, PpapProtocol.IdentityMaskLength, nameof(initiatorMask));
|
||||
var domainBytes = PpapCryptography.EncodeUtf8(
|
||||
PpapCryptography.NormalizeDomain(domain), PpapProtocol.MaximumDomainBytes,
|
||||
nameof(domain));
|
||||
try
|
||||
{
|
||||
return Encode(PpapMessageType.ClientHello, payload =>
|
||||
{
|
||||
payload.WriteByte((byte)mode);
|
||||
WriteField(payload, domainBytes);
|
||||
WriteField(payload, ephemeralKey);
|
||||
WriteField(payload, initiatorMask);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
PpapCryptography.Clear(domainBytes);
|
||||
}
|
||||
}
|
||||
|
||||
internal static ClientHello DecodeClientHello(object data)
|
||||
{
|
||||
var input = RequireWireBytes(data);
|
||||
var payload = DecodeEnvelope(input, PpapMessageType.ClientHello);
|
||||
var offset = 0;
|
||||
if (payload.Length == 0)
|
||||
throw new InvalidDataException("Missing authentication mode.");
|
||||
var mode = (AuthenticationMode)payload[offset++];
|
||||
ValidateMode(mode);
|
||||
var domainBytes = ReadField(payload, ref offset, PpapProtocol.MaximumDomainBytes);
|
||||
var key = ReadField(payload, ref offset, PpapProtocol.PublicKeyLength);
|
||||
var mask = ReadField(payload, ref offset, PpapProtocol.IdentityMaskLength);
|
||||
RequireEnd(payload, offset);
|
||||
PpapCryptography.ValidatePublicKey(key);
|
||||
ValidateExact(mask, PpapProtocol.IdentityMaskLength, nameof(mask));
|
||||
var domain = PpapCryptography.NormalizeDomain(PpapCryptography.DecodeUtf8(
|
||||
domainBytes, PpapProtocol.MaximumDomainBytes, "domain"));
|
||||
PpapCryptography.Clear(domainBytes);
|
||||
return new ClientHello
|
||||
{
|
||||
Mode = mode,
|
||||
Domain = domain,
|
||||
EphemeralKey = key,
|
||||
InitiatorMask = mask,
|
||||
};
|
||||
}
|
||||
|
||||
internal static byte[] EncodeServerHello(byte[] ephemeralCiphertext,
|
||||
byte[] responderMask, byte[] maskedResponderIdentity)
|
||||
{
|
||||
ValidateExact(ephemeralCiphertext, PpapProtocol.CiphertextLength,
|
||||
nameof(ephemeralCiphertext));
|
||||
ValidateExact(responderMask, PpapProtocol.IdentityMaskLength,
|
||||
nameof(responderMask));
|
||||
ValidateOptional(maskedResponderIdentity, PpapProtocol.HashLength,
|
||||
nameof(maskedResponderIdentity));
|
||||
return Encode(PpapMessageType.ServerHello, payload =>
|
||||
{
|
||||
WriteField(payload, ephemeralCiphertext);
|
||||
WriteField(payload, responderMask);
|
||||
WriteField(payload, maskedResponderIdentity);
|
||||
});
|
||||
}
|
||||
|
||||
internal static ServerHello DecodeServerHello(object data, bool responderAuthenticated)
|
||||
{
|
||||
var payload = DecodeEnvelope(RequireWireBytes(data), PpapMessageType.ServerHello);
|
||||
var offset = 0;
|
||||
var ciphertext = ReadField(payload, ref offset, PpapProtocol.CiphertextLength);
|
||||
var mask = ReadField(payload, ref offset, PpapProtocol.IdentityMaskLength);
|
||||
var maskedIdentity = ReadField(payload, ref offset, PpapProtocol.HashLength);
|
||||
RequireEnd(payload, offset);
|
||||
ValidateExact(ciphertext, PpapProtocol.CiphertextLength, nameof(ciphertext));
|
||||
ValidateExact(mask, PpapProtocol.IdentityMaskLength, nameof(mask));
|
||||
ValidatePresence(maskedIdentity, responderAuthenticated, PpapProtocol.HashLength,
|
||||
nameof(maskedIdentity));
|
||||
return new ServerHello
|
||||
{
|
||||
EphemeralCiphertext = ciphertext,
|
||||
ResponderMask = mask,
|
||||
MaskedResponderIdentity = maskedIdentity,
|
||||
};
|
||||
}
|
||||
|
||||
internal static byte[] EncodeInitiatorProof(byte[] maskedInitiatorIdentity,
|
||||
byte[] responderCiphertext, PpapRegistrationDescriptor responderRegistration,
|
||||
string domain, byte[] ephemeralSecret)
|
||||
{
|
||||
ValidateOptional(maskedInitiatorIdentity, PpapProtocol.HashLength,
|
||||
nameof(maskedInitiatorIdentity));
|
||||
ValidateOptional(responderCiphertext, PpapProtocol.CiphertextLength,
|
||||
nameof(responderCiphertext));
|
||||
var descriptor = ProtectDescriptor(responderRegistration,
|
||||
PpapIdentityRole.Responder, domain, ephemeralSecret);
|
||||
try
|
||||
{
|
||||
return Encode(PpapMessageType.InitiatorProof, payload =>
|
||||
{
|
||||
WriteField(payload, maskedInitiatorIdentity);
|
||||
WriteField(payload, responderCiphertext);
|
||||
WriteField(payload, descriptor);
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
PpapCryptography.Clear(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
internal static InitiatorProof DecodeInitiatorProof(object data,
|
||||
bool initiatorAuthenticated, bool responderAuthenticated,
|
||||
string domain, byte[] ephemeralSecret)
|
||||
{
|
||||
var payload = DecodeEnvelope(RequireWireBytes(data), PpapMessageType.InitiatorProof);
|
||||
var offset = 0;
|
||||
var maskedIdentity = ReadField(payload, ref offset, PpapProtocol.HashLength);
|
||||
var ciphertext = ReadField(payload, ref offset, PpapProtocol.CiphertextLength);
|
||||
var descriptorBytes = ReadField(payload, ref offset,
|
||||
PpapProtocol.ProtectedDescriptorLength);
|
||||
RequireEnd(payload, offset);
|
||||
ValidatePresence(maskedIdentity, initiatorAuthenticated, PpapProtocol.HashLength,
|
||||
nameof(maskedIdentity));
|
||||
ValidatePresence(ciphertext, responderAuthenticated, PpapProtocol.CiphertextLength,
|
||||
nameof(ciphertext));
|
||||
ValidatePresence(descriptorBytes, responderAuthenticated,
|
||||
PpapProtocol.ProtectedDescriptorLength, nameof(descriptorBytes));
|
||||
try
|
||||
{
|
||||
var descriptor = responderAuthenticated
|
||||
? UnprotectDescriptor(descriptorBytes, PpapIdentityRole.Responder,
|
||||
domain, ephemeralSecret)
|
||||
: null;
|
||||
return new InitiatorProof
|
||||
{
|
||||
MaskedInitiatorIdentity = maskedIdentity,
|
||||
ResponderCiphertext = ciphertext,
|
||||
ResponderRegistration = descriptor,
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
PpapCryptography.Clear(descriptorBytes);
|
||||
}
|
||||
}
|
||||
|
||||
internal static byte[] EncodeResponderProofCore(byte[] initiatorCiphertext,
|
||||
PpapRegistrationDescriptor initiatorRegistration, string domain,
|
||||
byte[] ephemeralSecret, out byte[] protectedDescriptor)
|
||||
{
|
||||
ValidateOptional(initiatorCiphertext, PpapProtocol.CiphertextLength,
|
||||
nameof(initiatorCiphertext));
|
||||
protectedDescriptor = ProtectDescriptor(initiatorRegistration,
|
||||
PpapIdentityRole.Initiator, domain, ephemeralSecret);
|
||||
try
|
||||
{
|
||||
return EncodeResponderProofCoreProtected(initiatorCiphertext,
|
||||
protectedDescriptor);
|
||||
}
|
||||
catch
|
||||
{
|
||||
PpapCryptography.Clear(protectedDescriptor);
|
||||
protectedDescriptor = null;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
internal static byte[] EncodeResponderProof(byte[] initiatorCiphertext,
|
||||
byte[] protectedInitiatorDescriptor, byte[] finished)
|
||||
{
|
||||
ValidateOptional(initiatorCiphertext, PpapProtocol.CiphertextLength,
|
||||
nameof(initiatorCiphertext));
|
||||
ValidateOptional(protectedInitiatorDescriptor,
|
||||
PpapProtocol.ProtectedDescriptorLength,
|
||||
nameof(protectedInitiatorDescriptor));
|
||||
ValidateExact(finished, PpapProtocol.FinishedTagLength, nameof(finished));
|
||||
return Encode(PpapMessageType.ResponderProof, payload =>
|
||||
{
|
||||
WriteField(payload, initiatorCiphertext);
|
||||
WriteField(payload, protectedInitiatorDescriptor);
|
||||
WriteField(payload, finished);
|
||||
});
|
||||
}
|
||||
|
||||
internal static ResponderProof DecodeResponderProof(object data,
|
||||
bool initiatorAuthenticated, string domain, byte[] ephemeralSecret)
|
||||
{
|
||||
var payload = DecodeEnvelope(RequireWireBytes(data), PpapMessageType.ResponderProof);
|
||||
var offset = 0;
|
||||
var ciphertext = ReadField(payload, ref offset, PpapProtocol.CiphertextLength);
|
||||
var descriptorBytes = ReadField(payload, ref offset,
|
||||
PpapProtocol.ProtectedDescriptorLength);
|
||||
var finished = ReadField(payload, ref offset, PpapProtocol.FinishedTagLength);
|
||||
RequireEnd(payload, offset);
|
||||
ValidatePresence(ciphertext, initiatorAuthenticated, PpapProtocol.CiphertextLength,
|
||||
nameof(ciphertext));
|
||||
ValidatePresence(descriptorBytes, initiatorAuthenticated,
|
||||
PpapProtocol.ProtectedDescriptorLength, nameof(descriptorBytes));
|
||||
ValidateExact(finished, PpapProtocol.FinishedTagLength, nameof(finished));
|
||||
try
|
||||
{
|
||||
var descriptor = initiatorAuthenticated
|
||||
? UnprotectDescriptor(descriptorBytes, PpapIdentityRole.Initiator,
|
||||
domain, ephemeralSecret)
|
||||
: null;
|
||||
var core = EncodeResponderProofCoreProtected(ciphertext,
|
||||
descriptorBytes);
|
||||
return new ResponderProof
|
||||
{
|
||||
InitiatorCiphertext = ciphertext,
|
||||
InitiatorRegistration = descriptor,
|
||||
Finished = finished,
|
||||
TranscriptCore = core,
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
PpapCryptography.Clear(descriptorBytes);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] EncodeResponderProofCoreProtected(byte[] initiatorCiphertext,
|
||||
byte[] protectedInitiatorDescriptor)
|
||||
{
|
||||
ValidateOptional(initiatorCiphertext, PpapProtocol.CiphertextLength,
|
||||
nameof(initiatorCiphertext));
|
||||
ValidateOptional(protectedInitiatorDescriptor,
|
||||
PpapProtocol.ProtectedDescriptorLength,
|
||||
nameof(protectedInitiatorDescriptor));
|
||||
return Encode(PpapMessageType.ResponderProof, payload =>
|
||||
{
|
||||
WriteField(payload, initiatorCiphertext);
|
||||
WriteField(payload, protectedInitiatorDescriptor);
|
||||
});
|
||||
}
|
||||
|
||||
internal static byte[] EncodeInitiatorFinished(byte[] finished)
|
||||
{
|
||||
ValidateExact(finished, PpapProtocol.FinishedTagLength, nameof(finished));
|
||||
return Encode(PpapMessageType.InitiatorFinished,
|
||||
payload => WriteField(payload, finished));
|
||||
}
|
||||
|
||||
internal static byte[] DecodeInitiatorFinished(object data)
|
||||
{
|
||||
var payload = DecodeEnvelope(RequireWireBytes(data), PpapMessageType.InitiatorFinished);
|
||||
var offset = 0;
|
||||
var finished = ReadField(payload, ref offset, PpapProtocol.FinishedTagLength);
|
||||
RequireEnd(payload, offset);
|
||||
ValidateExact(finished, PpapProtocol.FinishedTagLength, nameof(finished));
|
||||
return finished;
|
||||
}
|
||||
|
||||
internal static byte[] EncodeAuthenticationContext(AuthenticationMode mode,
|
||||
string domain, string initiatorIdentity, PpapIdentityKind? initiatorKind,
|
||||
string responderIdentity, PpapIdentityKind? responderKind)
|
||||
{
|
||||
ValidateMode(mode);
|
||||
var needsInitiator = mode == AuthenticationMode.InitializerIdentity
|
||||
|| mode == AuthenticationMode.DualIdentity;
|
||||
var needsResponder = mode == AuthenticationMode.ResponderIdentity
|
||||
|| mode == AuthenticationMode.DualIdentity;
|
||||
if (needsInitiator != initiatorKind.HasValue
|
||||
|| needsInitiator != (initiatorIdentity != null)
|
||||
|| needsResponder != responderKind.HasValue
|
||||
|| needsResponder != (responderIdentity != null))
|
||||
throw new InvalidDataException("The PPAP authentication context is incomplete.");
|
||||
if (initiatorKind.HasValue
|
||||
&& !Enum.IsDefined(typeof(PpapIdentityKind), initiatorKind.Value))
|
||||
throw new InvalidDataException("Invalid initiator identity kind.");
|
||||
if (responderKind.HasValue
|
||||
&& !Enum.IsDefined(typeof(PpapIdentityKind), responderKind.Value))
|
||||
throw new InvalidDataException("Invalid responder identity kind.");
|
||||
|
||||
var protocol = Encoding.ASCII.GetBytes(PpapProtocol.Name);
|
||||
var modeBytes = new[] { (byte)mode };
|
||||
var domainBytes = PpapCryptography.EncodeUtf8(
|
||||
PpapCryptography.NormalizeDomain(domain), PpapProtocol.MaximumDomainBytes,
|
||||
nameof(domain));
|
||||
var initiatorBytes = PpapCryptography.EncodeUtf8(
|
||||
initiatorIdentity == null ? string.Empty
|
||||
: PpapCryptography.NormalizeIdentity(initiatorIdentity),
|
||||
PpapProtocol.MaximumIdentityBytes, nameof(initiatorIdentity));
|
||||
var responderBytes = PpapCryptography.EncodeUtf8(
|
||||
responderIdentity == null ? string.Empty
|
||||
: PpapCryptography.NormalizeIdentity(responderIdentity),
|
||||
PpapProtocol.MaximumIdentityBytes, nameof(responderIdentity));
|
||||
var kinds = new[]
|
||||
{
|
||||
initiatorKind.HasValue ? (byte)initiatorKind.Value : (byte)0,
|
||||
responderKind.HasValue ? (byte)responderKind.Value : (byte)0,
|
||||
};
|
||||
try
|
||||
{
|
||||
return PpapCryptography.EncodeFrames(protocol, modeBytes, domainBytes,
|
||||
initiatorBytes, responderBytes, kinds);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PpapCryptography.Clear(protocol);
|
||||
PpapCryptography.Clear(domainBytes);
|
||||
PpapCryptography.Clear(initiatorBytes);
|
||||
PpapCryptography.Clear(responderBytes);
|
||||
PpapCryptography.Clear(kinds);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] ProtectDescriptor(PpapRegistrationDescriptor descriptor,
|
||||
PpapIdentityRole role, string domain, byte[] ephemeralSecret)
|
||||
{
|
||||
var plaintext = EncodeDescriptor(descriptor);
|
||||
if (plaintext.Length == 0)
|
||||
return plaintext;
|
||||
try
|
||||
{
|
||||
return PpapCryptography.ProtectRegistrationDescriptor(domain, role,
|
||||
ephemeralSecret, plaintext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PpapCryptography.Clear(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
static PpapRegistrationDescriptor UnprotectDescriptor(byte[] descriptor,
|
||||
PpapIdentityRole role, string domain, byte[] ephemeralSecret)
|
||||
{
|
||||
var plaintext = PpapCryptography.UnprotectRegistrationDescriptor(domain,
|
||||
role, ephemeralSecret, descriptor);
|
||||
try
|
||||
{
|
||||
return DecodeDescriptor(plaintext);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PpapCryptography.Clear(plaintext);
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] EncodeDescriptor(PpapRegistrationDescriptor descriptor)
|
||||
{
|
||||
if (descriptor == null)
|
||||
return Array.Empty<byte>();
|
||||
using (var output = new MemoryStream())
|
||||
{
|
||||
output.WriteByte((byte)descriptor.Kind);
|
||||
WriteInt64(output, descriptor.Version);
|
||||
WriteField(output, descriptor.Nonce);
|
||||
if (descriptor.Kind == PpapIdentityKind.PasswordDerived)
|
||||
{
|
||||
output.WriteByte(1);
|
||||
PpapCryptography.WriteInt32(output, descriptor.KdfProfile.Version);
|
||||
PpapCryptography.WriteInt32(output, descriptor.KdfProfile.MemoryKiB);
|
||||
PpapCryptography.WriteInt32(output, descriptor.KdfProfile.Iterations);
|
||||
PpapCryptography.WriteInt32(output, descriptor.KdfProfile.Parallelism);
|
||||
}
|
||||
else
|
||||
{
|
||||
output.WriteByte(0);
|
||||
}
|
||||
return output.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
static PpapRegistrationDescriptor DecodeDescriptor(byte[] input)
|
||||
{
|
||||
if (input == null || input.Length < 10 || input.Length > 128)
|
||||
throw new InvalidDataException("Invalid registration descriptor.");
|
||||
var offset = 0;
|
||||
var kind = (PpapIdentityKind)input[offset++];
|
||||
var version = ReadInt64(input, ref offset);
|
||||
var nonce = ReadField(input, ref offset, PpapProtocol.RegistrationNonceLength);
|
||||
if (offset >= input.Length)
|
||||
throw new InvalidDataException("Truncated registration descriptor.");
|
||||
var hasProfile = input[offset++];
|
||||
PpapKdfProfile profile = null;
|
||||
if (hasProfile == 1)
|
||||
{
|
||||
var profileVersion = PpapCryptography.ReadInt32(input, ref offset);
|
||||
var memory = PpapCryptography.ReadInt32(input, ref offset);
|
||||
var iterations = PpapCryptography.ReadInt32(input, ref offset);
|
||||
var parallelism = PpapCryptography.ReadInt32(input, ref offset);
|
||||
profile = new PpapKdfProfile(profileVersion, memory, iterations, parallelism);
|
||||
}
|
||||
else if (hasProfile != 0)
|
||||
{
|
||||
throw new InvalidDataException("Invalid KDF profile marker.");
|
||||
}
|
||||
RequireEnd(input, offset);
|
||||
return new PpapRegistrationDescriptor(kind, version, nonce, profile);
|
||||
}
|
||||
|
||||
static byte[] Encode(PpapMessageType type, Action<MemoryStream> writePayload)
|
||||
{
|
||||
using (var payload = new MemoryStream())
|
||||
{
|
||||
writePayload(payload);
|
||||
if (payload.Length > PpapProtocol.MaximumWireMessageBytes - 10)
|
||||
throw new InvalidDataException("PPAP payload is too large.");
|
||||
using (var message = new MemoryStream())
|
||||
{
|
||||
message.Write(Magic, 0, Magic.Length);
|
||||
message.WriteByte(PpapProtocol.WireVersion);
|
||||
message.WriteByte((byte)type);
|
||||
PpapCryptography.WriteInt32(message, (int)payload.Length);
|
||||
payload.Position = 0;
|
||||
payload.CopyTo(message);
|
||||
return message.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] DecodeEnvelope(byte[] input, PpapMessageType expectedType)
|
||||
{
|
||||
if (input.Length < 10 || input.Length > PpapProtocol.MaximumWireMessageBytes)
|
||||
throw new InvalidDataException("Invalid PPAP message length.");
|
||||
for (var i = 0; i < Magic.Length; i++)
|
||||
if (input[i] != Magic[i])
|
||||
throw new InvalidDataException("Invalid PPAP message magic.");
|
||||
if (input[4] != PpapProtocol.WireVersion)
|
||||
throw new InvalidDataException("Unsupported PPAP wire version.");
|
||||
if (input[5] != (byte)expectedType)
|
||||
throw new InvalidDataException("Unexpected PPAP message type.");
|
||||
var offset = 6;
|
||||
var length = PpapCryptography.ReadInt32(input, ref offset);
|
||||
if (length != input.Length - offset)
|
||||
throw new InvalidDataException("Invalid PPAP payload length.");
|
||||
var payload = new byte[length];
|
||||
Buffer.BlockCopy(input, offset, payload, 0, length);
|
||||
return payload;
|
||||
}
|
||||
|
||||
static byte[] RequireWireBytes(object data)
|
||||
{
|
||||
if (!(data is byte[] input))
|
||||
throw new InvalidDataException("PPAP authentication data must be a byte array.");
|
||||
return input;
|
||||
}
|
||||
|
||||
static void WriteField(Stream output, byte[] value)
|
||||
{
|
||||
var field = value ?? Array.Empty<byte>();
|
||||
PpapCryptography.WriteInt32(output, field.Length);
|
||||
output.Write(field, 0, field.Length);
|
||||
}
|
||||
|
||||
static byte[] ReadField(byte[] input, ref int offset, int maximumLength)
|
||||
{
|
||||
var length = PpapCryptography.ReadInt32(input, ref offset);
|
||||
if (length > maximumLength || input.Length - offset < length)
|
||||
throw new InvalidDataException("Invalid PPAP field length.");
|
||||
var field = new byte[length];
|
||||
Buffer.BlockCopy(input, offset, field, 0, length);
|
||||
offset += length;
|
||||
return field;
|
||||
}
|
||||
|
||||
static void WriteInt64(Stream output, long value)
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
for (var shift = 56; shift >= 0; shift -= 8)
|
||||
output.WriteByte((byte)(value >> shift));
|
||||
}
|
||||
|
||||
static long ReadInt64(byte[] input, ref int offset)
|
||||
{
|
||||
if (input.Length - offset < 8)
|
||||
throw new InvalidDataException("Truncated PPAP integer.");
|
||||
long value = 0;
|
||||
for (var i = 0; i < 8; i++)
|
||||
value = (value << 8) | input[offset++];
|
||||
if (value < 0)
|
||||
throw new InvalidDataException("Negative PPAP integer.");
|
||||
return value;
|
||||
}
|
||||
|
||||
static void ValidateMode(AuthenticationMode mode)
|
||||
{
|
||||
if (mode != AuthenticationMode.InitializerIdentity
|
||||
&& mode != AuthenticationMode.ResponderIdentity
|
||||
&& mode != AuthenticationMode.DualIdentity)
|
||||
throw new InvalidDataException("Unsupported PPAP authentication mode.");
|
||||
}
|
||||
|
||||
static void ValidateExact(byte[] value, int length, string name)
|
||||
{
|
||||
if (value == null || value.Length != length)
|
||||
throw new InvalidDataException($"{name} has an invalid length.");
|
||||
}
|
||||
|
||||
static void ValidateOptional(byte[] value, int length, string name)
|
||||
{
|
||||
if (value != null && value.Length != 0 && value.Length != length)
|
||||
throw new InvalidDataException($"{name} has an invalid length.");
|
||||
}
|
||||
|
||||
static void ValidatePresence(byte[] value, bool required, int length, string name)
|
||||
{
|
||||
if (required)
|
||||
ValidateExact(value, length, name);
|
||||
else if (value == null || value.Length != 0)
|
||||
throw new InvalidDataException($"{name} must be absent.");
|
||||
}
|
||||
|
||||
static void RequireEnd(byte[] input, int offset)
|
||||
{
|
||||
if (offset != input.Length)
|
||||
throw new InvalidDataException("Trailing PPAP message data.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user