mirror of
https://github.com/esiur/esiur-dotnet.git
synced 2026-07-30 17:30:40 +00:00
236 lines
8.7 KiB
C#
236 lines
8.7 KiB
C#
using System;
|
|
using System.IO;
|
|
|
|
namespace Esiur.Security.Authority.Providers.Ppap;
|
|
|
|
internal static partial class PpapWire
|
|
{
|
|
internal sealed class RotationOffer
|
|
{
|
|
public PpapIdentityRole Role;
|
|
public string Identity;
|
|
public long ExpectedVersion;
|
|
public byte[] Nonce;
|
|
public byte[] EncapsulationKey;
|
|
public PpapKdfProfile KdfProfile;
|
|
}
|
|
|
|
internal sealed class RotationChallenge
|
|
{
|
|
public PpapIdentityRole Role;
|
|
public byte[] Ciphertext;
|
|
}
|
|
|
|
internal sealed class RotationProof
|
|
{
|
|
public PpapIdentityRole Role;
|
|
public byte[] Proof;
|
|
}
|
|
|
|
internal static byte[] EncodeRotationStart(PpapIdentityRole role)
|
|
=> Encode(PpapMessageType.RotationStart,
|
|
payload => payload.WriteByte(ValidateRole(role)));
|
|
|
|
internal static PpapIdentityRole DecodeRotationStart(object data)
|
|
=> DecodeRoleOnly(data, PpapMessageType.RotationStart);
|
|
|
|
internal static byte[] EncodeRotationOffer(PpapIdentityRole role, string identity,
|
|
long expectedVersion, byte[] nonce, byte[] encapsulationKey,
|
|
PpapKdfProfile kdfProfile)
|
|
{
|
|
ValidateRole(role);
|
|
if (expectedVersion < 1 || expectedVersion == long.MaxValue)
|
|
throw new InvalidDataException("Invalid rotation version.");
|
|
ValidateExact(nonce, PpapProtocol.RegistrationNonceLength, nameof(nonce));
|
|
PpapCryptography.ValidatePublicKey(encapsulationKey);
|
|
if (kdfProfile == null)
|
|
throw new ArgumentNullException(nameof(kdfProfile));
|
|
var identityBytes = PpapCryptography.EncodeUtf8(
|
|
PpapCryptography.NormalizeIdentity(identity),
|
|
PpapProtocol.MaximumIdentityBytes, nameof(identity));
|
|
var descriptor = EncodeDescriptor(new PpapRegistrationDescriptor(
|
|
PpapIdentityKind.PasswordDerived, expectedVersion + 1, nonce, kdfProfile));
|
|
try
|
|
{
|
|
return Encode(PpapMessageType.RotationOffer, payload =>
|
|
{
|
|
payload.WriteByte((byte)role);
|
|
WriteField(payload, identityBytes);
|
|
WriteInt64(payload, expectedVersion);
|
|
WriteField(payload, encapsulationKey);
|
|
WriteField(payload, descriptor);
|
|
});
|
|
}
|
|
finally
|
|
{
|
|
PpapCryptography.Clear(identityBytes);
|
|
PpapCryptography.Clear(descriptor);
|
|
}
|
|
}
|
|
|
|
internal static RotationOffer DecodeRotationOffer(object data)
|
|
{
|
|
var payload = DecodeEnvelope(RequireWireBytes(data), PpapMessageType.RotationOffer);
|
|
var offset = 0;
|
|
var role = ReadRole(payload, ref offset);
|
|
var identityBytes = ReadField(payload, ref offset,
|
|
PpapProtocol.MaximumIdentityBytes);
|
|
var expectedVersion = ReadInt64(payload, ref offset);
|
|
var key = ReadField(payload, ref offset, PpapProtocol.PublicKeyLength);
|
|
var descriptorBytes = ReadField(payload, ref offset, 128);
|
|
RequireEnd(payload, offset);
|
|
if (expectedVersion < 1 || expectedVersion == long.MaxValue)
|
|
throw new InvalidDataException("Invalid rotation version.");
|
|
PpapCryptography.ValidatePublicKey(key);
|
|
var identity = PpapCryptography.NormalizeIdentity(
|
|
PpapCryptography.DecodeUtf8(identityBytes,
|
|
PpapProtocol.MaximumIdentityBytes, "identity"));
|
|
var descriptor = DecodeDescriptor(descriptorBytes);
|
|
if (descriptor.Kind != PpapIdentityKind.PasswordDerived
|
|
|| descriptor.Version != expectedVersion + 1)
|
|
throw new InvalidDataException("Invalid rotated registration descriptor.");
|
|
PpapCryptography.Clear(identityBytes);
|
|
PpapCryptography.Clear(descriptorBytes);
|
|
return new RotationOffer
|
|
{
|
|
Role = role,
|
|
Identity = identity,
|
|
ExpectedVersion = expectedVersion,
|
|
Nonce = descriptor.Nonce,
|
|
EncapsulationKey = key,
|
|
KdfProfile = descriptor.KdfProfile,
|
|
};
|
|
}
|
|
|
|
internal static byte[] EncodeRotationChallenge(PpapIdentityRole role,
|
|
byte[] ciphertext)
|
|
{
|
|
ValidateRole(role);
|
|
ValidateExact(ciphertext, PpapProtocol.CiphertextLength, nameof(ciphertext));
|
|
return Encode(PpapMessageType.RotationChallenge, payload =>
|
|
{
|
|
payload.WriteByte((byte)role);
|
|
WriteField(payload, ciphertext);
|
|
});
|
|
}
|
|
|
|
internal static RotationChallenge DecodeRotationChallenge(object data)
|
|
{
|
|
var payload = DecodeEnvelope(RequireWireBytes(data),
|
|
PpapMessageType.RotationChallenge);
|
|
var offset = 0;
|
|
var role = ReadRole(payload, ref offset);
|
|
var ciphertext = ReadField(payload, ref offset,
|
|
PpapProtocol.CiphertextLength);
|
|
RequireEnd(payload, offset);
|
|
ValidateExact(ciphertext, PpapProtocol.CiphertextLength,
|
|
nameof(ciphertext));
|
|
return new RotationChallenge { Role = role, Ciphertext = ciphertext };
|
|
}
|
|
|
|
internal static byte[] EncodeRotationProof(PpapIdentityRole role, byte[] proof)
|
|
{
|
|
ValidateRole(role);
|
|
ValidateExact(proof, PpapProtocol.HashLength, nameof(proof));
|
|
return Encode(PpapMessageType.RotationProof, payload =>
|
|
{
|
|
payload.WriteByte((byte)role);
|
|
WriteField(payload, proof);
|
|
});
|
|
}
|
|
|
|
internal static RotationProof DecodeRotationProof(object data)
|
|
{
|
|
var payload = DecodeEnvelope(RequireWireBytes(data), PpapMessageType.RotationProof);
|
|
var offset = 0;
|
|
var role = ReadRole(payload, ref offset);
|
|
var proof = ReadField(payload, ref offset, PpapProtocol.HashLength);
|
|
RequireEnd(payload, offset);
|
|
ValidateExact(proof, PpapProtocol.HashLength, nameof(proof));
|
|
return new RotationProof { Role = role, Proof = proof };
|
|
}
|
|
|
|
internal static byte[] EncodeRotationCommit(PpapIdentityRole role, long version)
|
|
=> EncodeRoleVersion(PpapMessageType.RotationCommit, role, version);
|
|
|
|
internal static long DecodeRotationCommit(object data,
|
|
PpapIdentityRole expectedRole)
|
|
=> DecodeRoleVersion(data, PpapMessageType.RotationCommit, expectedRole);
|
|
|
|
internal static byte[] EncodeRotationCommitAck(PpapIdentityRole role, long version)
|
|
=> EncodeRoleVersion(PpapMessageType.RotationCommitAck, role, version);
|
|
|
|
internal static long DecodeRotationCommitAck(object data,
|
|
PpapIdentityRole expectedRole)
|
|
=> DecodeRoleVersion(data, PpapMessageType.RotationCommitAck, expectedRole);
|
|
|
|
internal static byte[] EncodeRotationDone()
|
|
=> Encode(PpapMessageType.RotationDone, _ => { });
|
|
|
|
internal static void DecodeRotationDone(object data)
|
|
{
|
|
var payload = DecodeEnvelope(RequireWireBytes(data), PpapMessageType.RotationDone);
|
|
RequireEnd(payload, 0);
|
|
}
|
|
|
|
internal static PpapMessageType PeekMessageType(object data)
|
|
{
|
|
var input = RequireWireBytes(data);
|
|
if (input.Length < 10)
|
|
throw new InvalidDataException("Invalid PPAP message length.");
|
|
return (PpapMessageType)input[5];
|
|
}
|
|
|
|
static byte[] EncodeRoleVersion(PpapMessageType type, PpapIdentityRole role,
|
|
long version)
|
|
{
|
|
ValidateRole(role);
|
|
if (version < 2)
|
|
throw new InvalidDataException("Invalid committed registration version.");
|
|
return Encode(type, payload =>
|
|
{
|
|
payload.WriteByte((byte)role);
|
|
WriteInt64(payload, version);
|
|
});
|
|
}
|
|
|
|
static long DecodeRoleVersion(object data, PpapMessageType type,
|
|
PpapIdentityRole expectedRole)
|
|
{
|
|
var payload = DecodeEnvelope(RequireWireBytes(data), type);
|
|
var offset = 0;
|
|
var role = ReadRole(payload, ref offset);
|
|
var version = ReadInt64(payload, ref offset);
|
|
RequireEnd(payload, offset);
|
|
if (role != expectedRole || version < 2)
|
|
throw new InvalidDataException("Unexpected rotation commit.");
|
|
return version;
|
|
}
|
|
|
|
static PpapIdentityRole DecodeRoleOnly(object data, PpapMessageType type)
|
|
{
|
|
var payload = DecodeEnvelope(RequireWireBytes(data), type);
|
|
var offset = 0;
|
|
var role = ReadRole(payload, ref offset);
|
|
RequireEnd(payload, offset);
|
|
return role;
|
|
}
|
|
|
|
static PpapIdentityRole ReadRole(byte[] payload, ref int offset)
|
|
{
|
|
if (offset >= payload.Length)
|
|
throw new InvalidDataException("Missing PPAP identity role.");
|
|
var role = (PpapIdentityRole)payload[offset++];
|
|
ValidateRole(role);
|
|
return role;
|
|
}
|
|
|
|
static byte ValidateRole(PpapIdentityRole role)
|
|
{
|
|
if (role != PpapIdentityRole.Initiator
|
|
&& role != PpapIdentityRole.Responder)
|
|
throw new InvalidDataException("Invalid PPAP identity role.");
|
|
return (byte)role;
|
|
}
|
|
}
|