using NSec.Cryptography; namespace DodoSSH.Crypto; /// /// Ed25519 signatures over the canonical encodings of the specification. See docs/crypto.md §7. /// /// /// /// Every signature is prefixed with a context string, so a signature produced in one role can /// never be replayed in another. Without that, a key statement signature and a grant signature /// over coincidentally-equal bytes would be interchangeable. /// /// /// Verification returns rather than throwing, including for malformed /// keys and signatures. These values arrive from an untrusted server or peer, so rejection is an /// expected outcome to be handled at the call site, not an exceptional one. /// /// public static class DshSignatures { private static SignatureAlgorithm Algorithm => SignatureAlgorithm.Ed25519; /// /// Signs a key statement's canonical encoding. /// /// The signer's Ed25519 key. /// Output of . public static byte[] SignKeyStatement(Key signingKey, ReadOnlySpan canonicalStatement) { ArgumentNullException.ThrowIfNull(signingKey); var message = BuildMessage(CryptoSpec.SigningContexts.KeyStatement, canonicalStatement); return Algorithm.Sign(signingKey, message); } /// /// Verifies a key statement's self-signature. /// /// /// The statement carries the very public key that verifies it, so this proves only that /// whoever produced the statement held the matching private key. It says nothing about who /// that is — that is the identity provider binding's job. Both checks are required; neither /// substitutes for the other. /// /// Ed25519 public key from the statement itself. /// Output of . /// The detached signature. public static bool VerifyKeyStatement( ReadOnlySpan signingPublicKey, ReadOnlySpan canonicalStatement, ReadOnlySpan signature) { if (signingPublicKey.Length != CryptoSpec.PublicKeySize || signature.Length != CryptoSpec.SignatureSize) { return false; } PublicKey publicKey; try { publicKey = PublicKey.Import(Algorithm, signingPublicKey, KeyBlobFormat.RawPublicKey); } catch (FormatException) { return false; } var message = BuildMessage(CryptoSpec.SigningContexts.KeyStatement, canonicalStatement); return Algorithm.Verify(publicKey, message, signature); } /// /// Prefixes a message with its signing context. /// /// /// Heap-allocated rather than stack-allocated on purpose: the statement contains /// caller-supplied strings of unbounded length, and a stackalloc sized from untrusted /// input is a stack overflow waiting to happen. /// private static byte[] BuildMessage(ReadOnlySpan context, ReadOnlySpan payload) { var message = new byte[context.Length + payload.Length]; context.CopyTo(message); payload.CopyTo(message.AsSpan(context.Length)); return message; } }