Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Omika.Core.Contracts.Mobile.Enrollment;
|
||||
using Omika.Core.Services.Mobile;
|
||||
|
||||
namespace Omika.Tests.Mobile.Local;
|
||||
|
||||
/// <summary>
|
||||
/// Tests du codec de QR d'inscription (signature RSA-SHA256 + décodage/vérification
|
||||
/// par kid). Utilise une paire de clés éphémère : aucun secret serveur embarqué.
|
||||
/// </summary>
|
||||
public class EnrollmentQrCodecTests
|
||||
{
|
||||
private const string Kid = "test-kid-1";
|
||||
|
||||
private readonly string _publicKey;
|
||||
private readonly string _privateKey;
|
||||
private readonly IReadOnlyDictionary<string, string> _registry;
|
||||
|
||||
public EnrollmentQrCodecTests()
|
||||
{
|
||||
using var rsa = RSA.Create(2048);
|
||||
_publicKey = rsa.ToXmlString(false);
|
||||
_privateKey = rsa.ToXmlString(true);
|
||||
_registry = new Dictionary<string, string> { [Kid] = _publicKey };
|
||||
}
|
||||
|
||||
private string Sign(string code, string numero, string url)
|
||||
=> EnrollmentQrCodec.Sign(
|
||||
EnrollmentQrCodec.BuildPayloadJson(Kid, code, numero, url), _privateKey);
|
||||
|
||||
/// <summary>Signe un payload JSON arbitraire (pour forger des cas invalides).</summary>
|
||||
private string SignJson(string json)
|
||||
=> EnrollmentQrCodec.Sign(json, _privateKey);
|
||||
|
||||
private string Envelope(string payloadB64Url, string signatureB64Url)
|
||||
=> EnrollmentQrCodec.Prefix + payloadB64Url + "." + signatureB64Url;
|
||||
|
||||
private static string B64Url(string raw)
|
||||
=> Convert.ToBase64String(Encoding.UTF8.GetBytes(raw)).TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
||||
|
||||
// ===== Cas acceptés =====
|
||||
|
||||
[Fact]
|
||||
public void QrSigne_Accepte_CodeClient()
|
||||
{
|
||||
EnrollmentQrPayload? p = EnrollmentQrCodec.Decode(Sign("BATIK.01", "LIC-001", "https://licence.example.com"), _registry);
|
||||
Assert.NotNull(p);
|
||||
Assert.Equal("BATIK.01", p.CodeClient);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QrSigne_Accepte_NumeroLicence()
|
||||
{
|
||||
EnrollmentQrPayload? p = EnrollmentQrCodec.Decode(Sign("BATIK.01", "LIC-001", "https://licence.example.com"), _registry);
|
||||
Assert.NotNull(p);
|
||||
Assert.Equal("LIC-001", p.NumeroLicence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void QrSigne_Accepte_UrlLicence()
|
||||
{
|
||||
EnrollmentQrPayload? p = EnrollmentQrCodec.Decode(Sign("BATIK.01", "LIC-001", "https://licence.example.com"), _registry);
|
||||
Assert.NotNull(p);
|
||||
Assert.Equal("https://licence.example.com", p.UrlLicence);
|
||||
}
|
||||
|
||||
// ===== Cas refusés =====
|
||||
|
||||
[Fact]
|
||||
public void PayloadModifie_Refuse()
|
||||
{
|
||||
string qr = Sign("BATIK.01", "LIC-001", "https://licence.example.com");
|
||||
// Altération du contenu signé.
|
||||
Assert.Null(EnrollmentQrCodec.Decode(qr + "x", _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SignatureModifiee_Refuse()
|
||||
{
|
||||
string qr = Sign("BATIK.01", "LIC-001", "https://licence.example.com");
|
||||
int dot = qr.LastIndexOf('.');
|
||||
string tampered = qr[..(dot + 1)] + "AAAA"; // signature corrompue
|
||||
Assert.Null(EnrollmentQrCodec.Decode(tampered, _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MauvaiseCle_Refuse()
|
||||
{
|
||||
using var other = RSA.Create(2048);
|
||||
string qr = EnrollmentQrCodec.Sign(
|
||||
EnrollmentQrCodec.BuildPayloadJson(Kid, "BATIK.01", "LIC-001", "https://licence.example.com"),
|
||||
other.ToXmlString(true));
|
||||
Assert.Null(EnrollmentQrCodec.Decode(qr, _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Base64Invalide_Refuse()
|
||||
{
|
||||
Assert.Null(EnrollmentQrCodec.Decode(Envelope("!!!", "!!!"), _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void JsonInvalide_Refuse()
|
||||
{
|
||||
string qr = SignJson("{not-json");
|
||||
Assert.Null(EnrollmentQrCodec.Decode(qr, _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VersionInconnue_Refuse()
|
||||
{
|
||||
string json = JsonSerializer.Serialize(new
|
||||
{
|
||||
version = 99,
|
||||
keyId = Kid,
|
||||
codeClient = "BATIK.01",
|
||||
numeroLicence = "LIC-001",
|
||||
urlLicence = "https://licence.example.com"
|
||||
});
|
||||
Assert.Null(EnrollmentQrCodec.Decode(SignJson(json), _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CodeClientVide_Refuse()
|
||||
{
|
||||
string json = JsonSerializer.Serialize(new
|
||||
{
|
||||
version = 1,
|
||||
keyId = Kid,
|
||||
codeClient = "",
|
||||
numeroLicence = "LIC-001",
|
||||
urlLicence = "https://licence.example.com"
|
||||
});
|
||||
Assert.Null(EnrollmentQrCodec.Decode(SignJson(json), _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumeroLicenceVide_Refuse()
|
||||
{
|
||||
string json = JsonSerializer.Serialize(new
|
||||
{
|
||||
version = 1,
|
||||
keyId = Kid,
|
||||
codeClient = "BATIK.01",
|
||||
numeroLicence = "",
|
||||
urlLicence = "https://licence.example.com"
|
||||
});
|
||||
Assert.Null(EnrollmentQrCodec.Decode(SignJson(json), _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UrlLicenceVide_Refuse()
|
||||
{
|
||||
string json = JsonSerializer.Serialize(new
|
||||
{
|
||||
version = 1,
|
||||
keyId = Kid,
|
||||
codeClient = "BATIK.01",
|
||||
numeroLicence = "LIC-001",
|
||||
urlLicence = ""
|
||||
});
|
||||
Assert.Null(EnrollmentQrCodec.Decode(SignJson(json), _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void KidInconnu_Refuse()
|
||||
{
|
||||
using var other = RSA.Create(2048);
|
||||
string qr = EnrollmentQrCodec.Sign(
|
||||
EnrollmentQrCodec.BuildPayloadJson("autre-kid", "BATIK.01", "LIC-001", "https://licence.example.com"),
|
||||
other.ToXmlString(true));
|
||||
Assert.Null(EnrollmentQrCodec.Decode(qr, _registry));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RotationKid_DeuxCles_VerifieAvecBonneCle()
|
||||
{
|
||||
// Simule une rotation : le registre contient l'ancienne ET la nouvelle clé.
|
||||
using var oldRsa = RSA.Create(2048);
|
||||
using var newRsa = RSA.Create(2048);
|
||||
var registry = new Dictionary<string, string>
|
||||
{
|
||||
["kid-old"] = oldRsa.ToXmlString(false),
|
||||
["kid-new"] = newRsa.ToXmlString(false)
|
||||
};
|
||||
|
||||
string qrNew = EnrollmentQrCodec.Sign(
|
||||
EnrollmentQrCodec.BuildPayloadJson("kid-new", "BATIK.01", "LIC-001", "https://licence.example.com"),
|
||||
newRsa.ToXmlString(true));
|
||||
|
||||
EnrollmentQrPayload? p = EnrollmentQrCodec.Decode(qrNew, registry);
|
||||
Assert.NotNull(p);
|
||||
Assert.Equal("BATIK.01", p.CodeClient);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user