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,235 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user