Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Omika.Core.Security;
|
||||
|
||||
namespace Omika.Tests.Security;
|
||||
|
||||
/// <summary>
|
||||
/// Tests du déchiffreur du mot de passe SMTP standard (<c>SMTP.sMdpStandard</c>),
|
||||
/// algorithme SHA-256 + AES-256-CBC-PKCS7 + IV aléatoire préfixé + Base64.
|
||||
///
|
||||
/// Les tests de roundtrip utilisent une clé de test explicite (indépendante des
|
||||
/// constantes de production) via le constructeur à 3 paramètres. Ils prouvent la
|
||||
/// cohérence C# du format mais NE remplacent PAS un vecteur réellement généré par
|
||||
/// WinDev (voir note TEST 8).
|
||||
/// </summary>
|
||||
public class SmtpPasswordStandardDecryptorTests
|
||||
{
|
||||
private const string Key1 = "K1";
|
||||
private const string Key2 = "K2";
|
||||
private const string Key3 = "K3";
|
||||
|
||||
private static SmtpPasswordStandardDecryptor Decryptor(
|
||||
string k1 = Key1, string k2 = Key2, string k3 = Key3)
|
||||
=> new(k1, k2, k3);
|
||||
|
||||
// ===== TEST 1 : vide → erreur métier (contrat : SmtpStandardPasswordError.Missing) =====
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void Decrypt_Vide_LeveMissing(string? valeur)
|
||||
{
|
||||
SmtpStandardPasswordException ex = Assert.Throws<SmtpStandardPasswordException>(
|
||||
() => Decryptor().Decrypt(valeur!));
|
||||
|
||||
Assert.Equal(SmtpStandardPasswordError.Missing, ex.Error);
|
||||
Assert.Equal("SMTP_STANDARD_PASSWORD_MISSING", ex.LogCode);
|
||||
}
|
||||
|
||||
// ===== TEST 2 : Base64 invalide → erreur contrôlée =====
|
||||
|
||||
[Theory]
|
||||
[InlineData("%%%%")]
|
||||
[InlineData("!!!!")]
|
||||
public void Decrypt_Base64Invalide_LeveInvalidBase64(string valeur)
|
||||
{
|
||||
SmtpStandardPasswordException ex = Assert.Throws<SmtpStandardPasswordException>(
|
||||
() => Decryptor().Decrypt(valeur));
|
||||
|
||||
Assert.Equal(SmtpStandardPasswordError.InvalidBase64, ex.Error);
|
||||
Assert.Equal("SMTP_STANDARD_PASSWORD_INVALID_BASE64", ex.LogCode);
|
||||
}
|
||||
|
||||
// ===== TEST 3 : payload < 17 octets → erreur contrôlée =====
|
||||
|
||||
[Fact]
|
||||
public void Decrypt_PayloadTropCourt_LeveInvalidPayload()
|
||||
{
|
||||
string base64 = Convert.ToBase64String(new byte[16]);
|
||||
|
||||
SmtpStandardPasswordException ex = Assert.Throws<SmtpStandardPasswordException>(
|
||||
() => Decryptor().Decrypt(base64));
|
||||
|
||||
Assert.Equal(SmtpStandardPasswordError.InvalidPayload, ex.Error);
|
||||
Assert.Equal("SMTP_STANDARD_PASSWORD_INVALID_PAYLOAD", ex.LogCode);
|
||||
}
|
||||
|
||||
// ===== TEST 4 : ciphertext non multiple de 16 → erreur contrôlée =====
|
||||
|
||||
[Fact]
|
||||
public void Decrypt_CiphertextNonMultipleDe16_LeveInvalidPayload()
|
||||
{
|
||||
// IV (16 octets) + 15 octets de ciphertext = 31 octets.
|
||||
string base64 = Convert.ToBase64String(new byte[31]);
|
||||
|
||||
SmtpStandardPasswordException ex = Assert.Throws<SmtpStandardPasswordException>(
|
||||
() => Decryptor().Decrypt(base64));
|
||||
|
||||
Assert.Equal(SmtpStandardPasswordError.InvalidPayload, ex.Error);
|
||||
}
|
||||
|
||||
// ===== TEST 5 : clé SHA-256 = 32 octets =====
|
||||
|
||||
[Fact]
|
||||
public void DeriveAesKey_Retourne32Octets()
|
||||
{
|
||||
byte[] key = SmtpPasswordStandardDecryptor.DeriveAesKey(Key1, Key2, Key3);
|
||||
|
||||
Assert.Equal(32, key.Length);
|
||||
}
|
||||
|
||||
// ===== TEST 6 : roundtrip UTF-8 avec accents =====
|
||||
|
||||
[Fact]
|
||||
public void Decrypt_RoundtripAccents_RetourneClair()
|
||||
{
|
||||
const string clair = "MotDePasse-éà-2026!";
|
||||
|
||||
string chiffre = EncryptStandard(clair, Key1, Key2, Key3);
|
||||
|
||||
Assert.Equal(clair, Decryptor().Decrypt(chiffre));
|
||||
}
|
||||
|
||||
// ===== TEST 7 : mauvaise clé → decrypt fail =====
|
||||
|
||||
[Fact]
|
||||
public void Decrypt_MauvaiseCle_LeveDecryptFailed()
|
||||
{
|
||||
string chiffre = EncryptStandard("secret", Key1, Key2, Key3);
|
||||
|
||||
SmtpStandardPasswordException ex = Assert.Throws<SmtpStandardPasswordException>(
|
||||
() => Decryptor("X1", "X2", "X3").Decrypt(chiffre));
|
||||
|
||||
Assert.Equal(SmtpStandardPasswordError.DecryptFailed, ex.Error);
|
||||
Assert.Equal("SMTP_STANDARD_PASSWORD_DECRYPT_FAILED", ex.LogCode);
|
||||
}
|
||||
|
||||
// ===== TEST 8 : vecteur mot de passe de test (format WinDev) =====
|
||||
// NOTE HONNÊTETÉ : ce vecteur est généré par l'implémentation C# du même
|
||||
// algorithme (roundtrip), PAS par WinDev. La compatibilité WinDev→C# réelle
|
||||
// reste PENDING tant qu'aucune valeur sMdpStandard migrée n'est fournie.
|
||||
|
||||
[Fact]
|
||||
public void Decrypt_VecteurMotDePasseTest_RetourneAttendu()
|
||||
{
|
||||
const string attendu = "Omika-Test-SMTP-2026!";
|
||||
|
||||
string chiffre = EncryptStandard(attendu, Key1, Key2, Key3);
|
||||
|
||||
Assert.Equal(attendu, Decryptor().Decrypt(chiffre));
|
||||
}
|
||||
|
||||
// ===== VECTEUR RÉEL WINDEV (source de vérité) =====
|
||||
// Valeur réellement produite par WinDev via CrypteMdpSMTPStandard pour le
|
||||
// plaintext volontairement non sensible "Omika-Test-SMTP-2026!". Ce test
|
||||
// appelle le decryptor DE PRODUCTION (clé = SmtpStandardCryptoConstants,
|
||||
// ordre cMdp1 + cMdp3 + cMdp2) sans AUCUN chiffrement C# : la donnée d'entrée
|
||||
// est exclusivement la valeur retournée par WinDev.
|
||||
|
||||
[Fact]
|
||||
public void WinDevStandardVector_ShouldDecryptCorrectly()
|
||||
{
|
||||
const string vecteurWinDev =
|
||||
"BjzyEIJ1aTVO2/ss4/Y2UOrgzt153SBaeIrpaqmJMXSn1C40A/c0FNOBrU8kkYOE";
|
||||
const string attendu = "Omika-Test-SMTP-2026!";
|
||||
|
||||
var decryptor = new SmtpPasswordStandardDecryptor(); // clé de production
|
||||
|
||||
string resultat = decryptor.Decrypt(vecteurWinDev);
|
||||
|
||||
Assert.Equal(attendu, resultat);
|
||||
}
|
||||
|
||||
// ===== Helper : chiffrement C# équivalent à CrypteMdpSMTPStandard =====
|
||||
|
||||
private static string EncryptStandard(string plaintext, string cMdp1, string cMdp2, string cMdp3)
|
||||
{
|
||||
byte[] key = SmtpPasswordStandardDecryptor.DeriveAesKey(cMdp1, cMdp2, cMdp3);
|
||||
using var aes = Aes.Create();
|
||||
aes.KeySize = 256;
|
||||
aes.BlockSize = 128;
|
||||
aes.Mode = CipherMode.CBC;
|
||||
aes.Padding = PaddingMode.PKCS7;
|
||||
aes.Key = key;
|
||||
aes.GenerateIV(); // IV aléatoire, inclus en tête du payload (comportement WinDev).
|
||||
|
||||
using ICryptoTransform encryptor = aes.CreateEncryptor();
|
||||
byte[] clear = Encoding.UTF8.GetBytes(plaintext);
|
||||
byte[] ciphertext = encryptor.TransformFinalBlock(clear, 0, clear.Length);
|
||||
|
||||
byte[] payload = new byte[aes.IV.Length + ciphertext.Length];
|
||||
Buffer.BlockCopy(aes.IV, 0, payload, 0, aes.IV.Length);
|
||||
Buffer.BlockCopy(ciphertext, 0, payload, aes.IV.Length, ciphertext.Length);
|
||||
return Convert.ToBase64String(payload);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user