using NSec.Cryptography; namespace DodoSSH.Crypto.Tests; /// Key statement signing and verification. See docs/crypto.md ยง7. public sealed class DshSignaturesTests { [Fact] public void AFreshSignature_Verifies() { using var key = CreateSigningKey(); var statement = Canonical(key); var signature = DshSignatures.SignKeyStatement(key, statement); signature.Length.ShouldBe(CryptoSpec.SignatureSize); DshSignatures.VerifyKeyStatement(PublicKeyBytes(key), statement, signature).ShouldBeTrue(); } [Fact] public void ATamperedStatement_DoesNotVerify() { using var key = CreateSigningKey(); var statement = Canonical(key); var signature = DshSignatures.SignKeyStatement(key, statement); var tampered = statement.ToArray(); tampered[^1] ^= 0x01; DshSignatures.VerifyKeyStatement(PublicKeyBytes(key), tampered, signature).ShouldBeFalse(); } [Fact] public void AnotherKeysSignature_DoesNotVerify() { using var key = CreateSigningKey(); using var other = CreateSigningKey(); var statement = Canonical(key); var signature = DshSignatures.SignKeyStatement(other, statement); DshSignatures.VerifyKeyStatement(PublicKeyBytes(key), statement, signature).ShouldBeFalse(); } [Fact] public void ASignatureMissingTheSigningContext_DoesNotVerify() { // Domain separation. A signature over the bare canonical encoding must not be accepted as a // key statement signature, or the same bytes could be replayed into another role. using var key = CreateSigningKey(); var statement = Canonical(key); var contextless = SignatureAlgorithm.Ed25519.Sign(key, statement); DshSignatures.VerifyKeyStatement(PublicKeyBytes(key), statement, contextless).ShouldBeFalse(); } [Theory] [InlineData(0)] [InlineData(31)] [InlineData(33)] public void AMalformedPublicKey_ReturnsFalseRatherThanThrowing(int length) { // These values arrive from an untrusted server, so rejection has to be an ordinary outcome. using var key = CreateSigningKey(); var statement = Canonical(key); var signature = DshSignatures.SignKeyStatement(key, statement); DshSignatures.VerifyKeyStatement(new byte[length], statement, signature).ShouldBeFalse(); } [Theory] [InlineData(0)] [InlineData(63)] [InlineData(65)] public void AMalformedSignature_ReturnsFalseRatherThanThrowing(int length) { using var key = CreateSigningKey(); DshSignatures.VerifyKeyStatement(PublicKeyBytes(key), Canonical(key), new byte[length]) .ShouldBeFalse(); } private static Key CreateSigningKey() => Key.Create( SignatureAlgorithm.Ed25519, new KeyCreationParameters { ExportPolicy = KeyExportPolicies.AllowPlaintextExport }); private static byte[] PublicKeyBytes(Key key) => key.PublicKey.Export(KeyBlobFormat.RawPublicKey); private static byte[] Canonical(Key signingKey) => KeyStatementCodec.Encode(new KeyStatementFields( Version: 1, Issuer: "https://idp.example/realms/dodossh", Subject: "alice-subject", Email: "alice@example.com", EncryptionPublicKey: TestKeys.Encryption, SigningPublicKey: PublicKeyBytes(signingKey), KeyGeneration: 1, CreatedAt: DateTimeOffset.FromUnixTimeMilliseconds(1_750_000_000_123), DeviceName: "alice-laptop")); }