ssh: use local CA, add mutual container/host auth

See loop/server/local_ssh.md for a detailed description of how sketch uses
now uses a local CA to sign each container certificate instead of adding
a new entry to known_hosts for each container.

This also adds another layer of security by having the container's ssh
server verify that incoming ssh connections have valid host certificates,
whereas prior to this change the authentication was only one-way (verifying
that the sketch container you think you're ssh'ing into really is the one
you think you're ssh'ing into).

This is somewhat inspired by https://github.com/FiloSottile/mkcert - which
plays a similar role as ssh_theater.go local for ssh connections, but mkcert
uses a local CA to address local development use cases for TLS/https rather
than for ssh.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: sc7b3928295277d5dk
diff --git a/loop/server/loophttp.go b/loop/server/loophttp.go
index a89557d..1cd486a 100644
--- a/loop/server/loophttp.go
+++ b/loop/server/loophttp.go
@@ -80,14 +80,16 @@
 }
 
 type InitRequest struct {
-	HostAddr          string `json:"host_addr"`
-	OutsideHTTP       string `json:"outside_http"`
-	GitRemoteAddr     string `json:"git_remote_addr"`
-	Commit            string `json:"commit"`
-	SSHAuthorizedKeys []byte `json:"ssh_authorized_keys"`
-	SSHServerIdentity []byte `json:"ssh_server_identity"`
-	SSHAvailable      bool   `json:"ssh_available"`
-	SSHError          string `json:"ssh_error,omitempty"`
+	HostAddr           string `json:"host_addr"`
+	OutsideHTTP        string `json:"outside_http"`
+	GitRemoteAddr      string `json:"git_remote_addr"`
+	Commit             string `json:"commit"`
+	SSHAuthorizedKeys  []byte `json:"ssh_authorized_keys"`
+	SSHServerIdentity  []byte `json:"ssh_server_identity"`
+	SSHContainerCAKey  []byte `json:"ssh_container_ca_key"`
+	SSHHostCertificate []byte `json:"ssh_host_certificate"`
+	SSHAvailable       bool   `json:"ssh_available"`
+	SSHError           string `json:"ssh_error,omitempty"`
 }
 
 // Server serves sketch HTTP. Server implements http.Handler.
@@ -190,7 +192,7 @@
 		if len(m.SSHAuthorizedKeys) > 0 && len(m.SSHServerIdentity) > 0 {
 			go func() {
 				ctx := context.Background()
-				if err := s.ServeSSH(ctx, m.SSHServerIdentity, m.SSHAuthorizedKeys); err != nil {
+				if err := s.ServeSSH(ctx, m.SSHServerIdentity, m.SSHAuthorizedKeys, m.SSHContainerCAKey, m.SSHHostCertificate); err != nil {
 					slog.ErrorContext(r.Context(), "/init ServeSSH", slog.String("err", err.Error()))
 					// Update SSH error if server fails to start
 					s.sshAvailable = false