From b96b6e7f8f75aad4eb933b9bef57a7d4b3ad31c6 Mon Sep 17 00:00:00 2001 From: maze Date: Fri, 10 Oct 2025 10:05:13 +0200 Subject: [PATCH] Initial import --- .vscode/settings.json | 6 + auth/auth.go | 96 +++++ auth/file.go | 104 ++++++ auth/file_test.go | 134 +++++++ auth/handler.go | 329 ++++++++++++++++ auth/principal.go | 148 ++++++++ auth/system_linux.go | 105 ++++++ auth/testdata/passwd | 3 + auth/testdata/pubkey | 5 + cmd/conduit/config.go | 187 ++++++++++ cmd/conduit/main.go | 47 +++ conduit.hcl | 62 +++ core/core.go | 7 + go.mod | 78 ++++ go.sum | 166 +++++++++ internal/netutil/conn.go | 37 ++ internal/stringutil/iter.go | 23 ++ logger/log.go | 231 ++++++++++++ policy/input/net.go | 42 +++ policy/input/ssh.go | 98 +++++ policy/policy.go | 157 ++++++++ provider/okta/go.mod | 29 ++ provider/okta/go.sum | 58 +++ provider/okta/provider.go | 60 +++ provider/provider.go | 29 ++ recorder/asciicast.go | 132 +++++++ recorder/recorder.go | 68 ++++ recorder/text.go | 15 + recorder/ttyrec.go | 72 ++++ ssh/channel.go | 52 +++ ssh/client.go | 28 ++ ssh/compat.go | 46 +++ ssh/context.go | 204 ++++++++++ ssh/handler.go | 352 ++++++++++++++++++ ssh/handler_tunnel.go | 47 +++ ssh/keys.go | 37 ++ ssh/server.go | 115 ++++++ ssh/sshutil/key.go | 62 +++ ssh/sshutil/request.go | 47 +++ storage/codec/binary.go | 248 ++++++++++++ storage/codec/codec.go | 66 ++++ storage/codec/default.go | 77 ++++ storage/io.go | 225 +++++++++++ storage/io_test.go | 124 ++++++ storage/kv.go | 61 +++ storage/testdata/kv | 4 + testdata/conduit.ed25519 | 7 + testdata/conduit.ed25519.pub | 1 + testdata/conduit.rsa | 27 ++ testdata/conduit.rsa.pub | 1 + testdata/example.ed25519 | 7 + testdata/example.ed25519-cert.pub | 1 + testdata/example.ed25519.pub | 1 + testdata/keys/test_id_ed25519_a | 7 + testdata/keys/test_id_ed25519_a.pub | 1 + testdata/keys/test_id_ed25519_b | 7 + testdata/keys/test_id_ed25519_b.pub | 1 + testdata/keys/test_id_ed25519_c | 7 + testdata/keys/test_id_ed25519_c.pub | 1 + testdata/keys/test_id_ed25519_d | 7 + testdata/keys/test_id_ed25519_d.pub | 1 + testdata/keys/test_id_ed25519_e | 7 + testdata/keys/test_id_ed25519_e.pub | 1 + testdata/keys/test_id_ed25519_f | 7 + testdata/keys/test_id_ed25519_f.pub | 1 + testdata/keys/test_id_ed25519_g | 7 + testdata/keys/test_id_ed25519_g.pub | 1 + testdata/keys/test_id_ed25519_h | 7 + testdata/keys/test_id_ed25519_h.pub | 1 + testdata/keys/test_id_ed25519_i | 7 + testdata/keys/test_id_ed25519_i.pub | 1 + testdata/keys/test_id_ed25519_j | 7 + testdata/keys/test_id_ed25519_j.pub | 1 + testdata/keys/test_id_ed25519_k | 7 + testdata/keys/test_id_ed25519_k.pub | 1 + testdata/keys/test_id_ed25519_l | 7 + testdata/keys/test_id_ed25519_l.pub | 1 + testdata/keys/test_id_ed25519_m | 7 + testdata/keys/test_id_ed25519_m.pub | 1 + testdata/keys/test_id_ed25519_n | 7 + testdata/keys/test_id_ed25519_n.pub | 1 + testdata/keys/test_id_ed25519_o | 7 + testdata/keys/test_id_ed25519_o.pub | 1 + testdata/keys/test_id_ed25519_p | 7 + testdata/keys/test_id_ed25519_p.pub | 1 + testdata/keys/test_id_ed25519_q | 7 + testdata/keys/test_id_ed25519_q.pub | 1 + testdata/keys/test_id_ed25519_r | 7 + testdata/keys/test_id_ed25519_r.pub | 1 + testdata/keys/test_id_ed25519_s | 7 + testdata/keys/test_id_ed25519_s.pub | 1 + testdata/keys/test_id_ed25519_t | 7 + testdata/keys/test_id_ed25519_t.pub | 1 + testdata/keys/test_id_ed25519_u | 7 + testdata/keys/test_id_ed25519_u.pub | 1 + testdata/keys/test_id_ed25519_v | 7 + testdata/keys/test_id_ed25519_v.pub | 1 + testdata/keys/test_id_ed25519_w | 7 + testdata/keys/test_id_ed25519_w.pub | 1 + testdata/keys/test_id_ed25519_x | 7 + testdata/keys/test_id_ed25519_x.pub | 1 + testdata/keys/test_id_ed25519_y | 7 + testdata/keys/test_id_ed25519_y.pub | 1 + testdata/keys/test_id_ed25519_z | 7 + testdata/keys/test_id_ed25519_z.pub | 1 + testdata/keys/test_id_rsa_a | 27 ++ testdata/keys/test_id_rsa_a.pub | 1 + testdata/keys/test_id_rsa_b | 27 ++ testdata/keys/test_id_rsa_b.pub | 1 + testdata/keys/test_id_rsa_c | 27 ++ testdata/keys/test_id_rsa_c.pub | 1 + testdata/keys/test_id_rsa_d | 27 ++ testdata/keys/test_id_rsa_d.pub | 1 + testdata/keys/test_id_rsa_e | 27 ++ testdata/keys/test_id_rsa_e.pub | 1 + testdata/keys/test_id_rsa_f | 27 ++ testdata/keys/test_id_rsa_f.pub | 1 + testdata/keys/test_id_rsa_g | 27 ++ testdata/keys/test_id_rsa_g.pub | 1 + testdata/keys/test_id_rsa_h | 27 ++ testdata/keys/test_id_rsa_h.pub | 1 + testdata/keys/test_id_rsa_i | 27 ++ testdata/keys/test_id_rsa_i.pub | 1 + testdata/keys/test_id_rsa_j | 27 ++ testdata/keys/test_id_rsa_j.pub | 1 + testdata/keys/test_id_rsa_k | 27 ++ testdata/keys/test_id_rsa_k.pub | 1 + testdata/keys/test_id_rsa_l | 27 ++ testdata/keys/test_id_rsa_l.pub | 1 + testdata/keys/test_id_rsa_m | 27 ++ testdata/keys/test_id_rsa_m.pub | 1 + testdata/keys/test_id_rsa_n | 27 ++ testdata/keys/test_id_rsa_n.pub | 1 + testdata/keys/test_id_rsa_o | 27 ++ testdata/keys/test_id_rsa_o.pub | 1 + testdata/keys/test_id_rsa_p | 27 ++ testdata/keys/test_id_rsa_p.pub | 1 + testdata/keys/test_id_rsa_q | 27 ++ testdata/keys/test_id_rsa_q.pub | 1 + testdata/keys/test_id_rsa_r | 27 ++ testdata/keys/test_id_rsa_r.pub | 1 + testdata/keys/test_id_rsa_s | 27 ++ testdata/keys/test_id_rsa_s.pub | 1 + testdata/keys/test_id_rsa_t | 27 ++ testdata/keys/test_id_rsa_t.pub | 1 + testdata/keys/test_id_rsa_u | 27 ++ testdata/keys/test_id_rsa_u.pub | 1 + testdata/keys/test_id_rsa_v | 27 ++ testdata/keys/test_id_rsa_v.pub | 1 + testdata/keys/test_id_rsa_w | 27 ++ testdata/keys/test_id_rsa_w.pub | 1 + testdata/keys/test_id_rsa_x | 27 ++ testdata/keys/test_id_rsa_x.pub | 1 + testdata/keys/test_id_rsa_y | 27 ++ testdata/keys/test_id_rsa_y.pub | 1 + testdata/keys/test_id_rsa_z | 27 ++ testdata/keys/test_id_rsa_z.pub | 1 + testdata/policy/auth.rego | 62 +++ testdata/policy/conduit/auth/mfa.rego | 76 ++++ .../policy/conduit/auth/user_certificate.rego | 11 + testdata/policy/conduit/session/env.rego | 11 + testdata/skip-mfa.ed25519 | 7 + testdata/skip-mfa.ed25519-cert.pub | 1 + testdata/skip-mfa.ed25519.pub | 1 + 164 files changed, 5473 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 auth/auth.go create mode 100644 auth/file.go create mode 100644 auth/file_test.go create mode 100644 auth/handler.go create mode 100644 auth/principal.go create mode 100644 auth/system_linux.go create mode 100644 auth/testdata/passwd create mode 100644 auth/testdata/pubkey create mode 100644 cmd/conduit/config.go create mode 100644 cmd/conduit/main.go create mode 100644 conduit.hcl create mode 100644 core/core.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/netutil/conn.go create mode 100644 internal/stringutil/iter.go create mode 100644 logger/log.go create mode 100644 policy/input/net.go create mode 100644 policy/input/ssh.go create mode 100644 policy/policy.go create mode 100644 provider/okta/go.mod create mode 100644 provider/okta/go.sum create mode 100644 provider/okta/provider.go create mode 100644 provider/provider.go create mode 100644 recorder/asciicast.go create mode 100644 recorder/recorder.go create mode 100644 recorder/text.go create mode 100644 recorder/ttyrec.go create mode 100644 ssh/channel.go create mode 100644 ssh/client.go create mode 100644 ssh/compat.go create mode 100644 ssh/context.go create mode 100644 ssh/handler.go create mode 100644 ssh/handler_tunnel.go create mode 100644 ssh/keys.go create mode 100644 ssh/server.go create mode 100644 ssh/sshutil/key.go create mode 100644 ssh/sshutil/request.go create mode 100644 storage/codec/binary.go create mode 100644 storage/codec/codec.go create mode 100644 storage/codec/default.go create mode 100644 storage/io.go create mode 100644 storage/io_test.go create mode 100644 storage/kv.go create mode 100644 storage/testdata/kv create mode 100644 testdata/conduit.ed25519 create mode 100644 testdata/conduit.ed25519.pub create mode 100644 testdata/conduit.rsa create mode 100644 testdata/conduit.rsa.pub create mode 100644 testdata/example.ed25519 create mode 100644 testdata/example.ed25519-cert.pub create mode 100644 testdata/example.ed25519.pub create mode 100644 testdata/keys/test_id_ed25519_a create mode 100644 testdata/keys/test_id_ed25519_a.pub create mode 100644 testdata/keys/test_id_ed25519_b create mode 100644 testdata/keys/test_id_ed25519_b.pub create mode 100644 testdata/keys/test_id_ed25519_c create mode 100644 testdata/keys/test_id_ed25519_c.pub create mode 100644 testdata/keys/test_id_ed25519_d create mode 100644 testdata/keys/test_id_ed25519_d.pub create mode 100644 testdata/keys/test_id_ed25519_e create mode 100644 testdata/keys/test_id_ed25519_e.pub create mode 100644 testdata/keys/test_id_ed25519_f create mode 100644 testdata/keys/test_id_ed25519_f.pub create mode 100644 testdata/keys/test_id_ed25519_g create mode 100644 testdata/keys/test_id_ed25519_g.pub create mode 100644 testdata/keys/test_id_ed25519_h create mode 100644 testdata/keys/test_id_ed25519_h.pub create mode 100644 testdata/keys/test_id_ed25519_i create mode 100644 testdata/keys/test_id_ed25519_i.pub create mode 100644 testdata/keys/test_id_ed25519_j create mode 100644 testdata/keys/test_id_ed25519_j.pub create mode 100644 testdata/keys/test_id_ed25519_k create mode 100644 testdata/keys/test_id_ed25519_k.pub create mode 100644 testdata/keys/test_id_ed25519_l create mode 100644 testdata/keys/test_id_ed25519_l.pub create mode 100644 testdata/keys/test_id_ed25519_m create mode 100644 testdata/keys/test_id_ed25519_m.pub create mode 100644 testdata/keys/test_id_ed25519_n create mode 100644 testdata/keys/test_id_ed25519_n.pub create mode 100644 testdata/keys/test_id_ed25519_o create mode 100644 testdata/keys/test_id_ed25519_o.pub create mode 100644 testdata/keys/test_id_ed25519_p create mode 100644 testdata/keys/test_id_ed25519_p.pub create mode 100644 testdata/keys/test_id_ed25519_q create mode 100644 testdata/keys/test_id_ed25519_q.pub create mode 100644 testdata/keys/test_id_ed25519_r create mode 100644 testdata/keys/test_id_ed25519_r.pub create mode 100644 testdata/keys/test_id_ed25519_s create mode 100644 testdata/keys/test_id_ed25519_s.pub create mode 100644 testdata/keys/test_id_ed25519_t create mode 100644 testdata/keys/test_id_ed25519_t.pub create mode 100644 testdata/keys/test_id_ed25519_u create mode 100644 testdata/keys/test_id_ed25519_u.pub create mode 100644 testdata/keys/test_id_ed25519_v create mode 100644 testdata/keys/test_id_ed25519_v.pub create mode 100644 testdata/keys/test_id_ed25519_w create mode 100644 testdata/keys/test_id_ed25519_w.pub create mode 100644 testdata/keys/test_id_ed25519_x create mode 100644 testdata/keys/test_id_ed25519_x.pub create mode 100644 testdata/keys/test_id_ed25519_y create mode 100644 testdata/keys/test_id_ed25519_y.pub create mode 100644 testdata/keys/test_id_ed25519_z create mode 100644 testdata/keys/test_id_ed25519_z.pub create mode 100644 testdata/keys/test_id_rsa_a create mode 100644 testdata/keys/test_id_rsa_a.pub create mode 100644 testdata/keys/test_id_rsa_b create mode 100644 testdata/keys/test_id_rsa_b.pub create mode 100644 testdata/keys/test_id_rsa_c create mode 100644 testdata/keys/test_id_rsa_c.pub create mode 100644 testdata/keys/test_id_rsa_d create mode 100644 testdata/keys/test_id_rsa_d.pub create mode 100644 testdata/keys/test_id_rsa_e create mode 100644 testdata/keys/test_id_rsa_e.pub create mode 100644 testdata/keys/test_id_rsa_f create mode 100644 testdata/keys/test_id_rsa_f.pub create mode 100644 testdata/keys/test_id_rsa_g create mode 100644 testdata/keys/test_id_rsa_g.pub create mode 100644 testdata/keys/test_id_rsa_h create mode 100644 testdata/keys/test_id_rsa_h.pub create mode 100644 testdata/keys/test_id_rsa_i create mode 100644 testdata/keys/test_id_rsa_i.pub create mode 100644 testdata/keys/test_id_rsa_j create mode 100644 testdata/keys/test_id_rsa_j.pub create mode 100644 testdata/keys/test_id_rsa_k create mode 100644 testdata/keys/test_id_rsa_k.pub create mode 100644 testdata/keys/test_id_rsa_l create mode 100644 testdata/keys/test_id_rsa_l.pub create mode 100644 testdata/keys/test_id_rsa_m create mode 100644 testdata/keys/test_id_rsa_m.pub create mode 100644 testdata/keys/test_id_rsa_n create mode 100644 testdata/keys/test_id_rsa_n.pub create mode 100644 testdata/keys/test_id_rsa_o create mode 100644 testdata/keys/test_id_rsa_o.pub create mode 100644 testdata/keys/test_id_rsa_p create mode 100644 testdata/keys/test_id_rsa_p.pub create mode 100644 testdata/keys/test_id_rsa_q create mode 100644 testdata/keys/test_id_rsa_q.pub create mode 100644 testdata/keys/test_id_rsa_r create mode 100644 testdata/keys/test_id_rsa_r.pub create mode 100644 testdata/keys/test_id_rsa_s create mode 100644 testdata/keys/test_id_rsa_s.pub create mode 100644 testdata/keys/test_id_rsa_t create mode 100644 testdata/keys/test_id_rsa_t.pub create mode 100644 testdata/keys/test_id_rsa_u create mode 100644 testdata/keys/test_id_rsa_u.pub create mode 100644 testdata/keys/test_id_rsa_v create mode 100644 testdata/keys/test_id_rsa_v.pub create mode 100644 testdata/keys/test_id_rsa_w create mode 100644 testdata/keys/test_id_rsa_w.pub create mode 100644 testdata/keys/test_id_rsa_x create mode 100644 testdata/keys/test_id_rsa_x.pub create mode 100644 testdata/keys/test_id_rsa_y create mode 100644 testdata/keys/test_id_rsa_y.pub create mode 100644 testdata/keys/test_id_rsa_z create mode 100644 testdata/keys/test_id_rsa_z.pub create mode 100644 testdata/policy/auth.rego create mode 100644 testdata/policy/conduit/auth/mfa.rego create mode 100644 testdata/policy/conduit/auth/user_certificate.rego create mode 100644 testdata/policy/conduit/session/env.rego create mode 100644 testdata/skip-mfa.ed25519 create mode 100644 testdata/skip-mfa.ed25519-cert.pub create mode 100644 testdata/skip-mfa.ed25519.pub diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..cd525ed --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "gopls": { + "formatting.local": "git.maze.io/maze/conduit" + }, + "CodeGPT.apiKey": "Ollama" +} \ No newline at end of file diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..3b2120b --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,96 @@ +package auth + +import ( + "bytes" + "errors" + "strings" + "time" + + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/logger" +) + +var ErrUnauthorized = errors.New("unauthorized") + +// Provider implements zero or more of the [Password], [PublicKey] interfaces. +type Provider any + +// Password authenticator. +type Password interface { + VerifyPassword(meta ssh.ConnMetadata, password string) (Principal, error) +} + +// Token authenticator. +type Token interface { + Instruction() string + + Prompt() string + + // Multiline accepts multiline input and wait for the user to supply + // an empty line. + Multiline() bool + + VerifyToken(meta ssh.ConnMetadata, token string) (Principal, error) +} + +// PublicKey authenticator. +type PublicKey interface { + VerifyPublicKey(meta ssh.ConnMetadata, publicKey ssh.PublicKey) (Principal, error) +} + +type CertificateAuthority struct { + checker *ssh.CertChecker + keys []ssh.PublicKey + blob [][]byte + user bool + host bool +} + +func NewUserCertificateAuthority(keys ...ssh.PublicKey) *CertificateAuthority { + var blob [][]byte + for _, key := range keys { + blob = append(blob, key.Marshal()) + } + auth := &CertificateAuthority{ + checker: &ssh.CertChecker{ + Clock: time.Now, + }, + keys: keys, + blob: blob, + user: true, + } + auth.checker.IsUserAuthority = auth.isUserAuthority + return auth +} + +func (auth *CertificateAuthority) isUserAuthority(key ssh.PublicKey) bool { + if !auth.user { + return false + } + blob := key.Marshal() + for _, other := range auth.blob { + if bytes.Equal(blob, other) { + return true + } + } + return false +} + +func (auth *CertificateAuthority) VerifyPublicKey(meta ssh.ConnMetadata, publicKey ssh.PublicKey) (Principal, error) { + log := logger.StandardLog.Values(logger.Values{ + "client": meta.RemoteAddr().String(), + "key": strings.TrimSpace(string(ssh.MarshalAuthorizedKey(publicKey))), + "user": meta.User(), + "version": string(meta.ClientVersion()), + }) + log.Debug("Verifying user certificate") + + _, err := auth.checker.Authenticate(meta, publicKey) + if err != nil { + return nil, err + } + + cert := publicKey.(*ssh.Certificate) + return UserCertificatePrincipal{cert}, nil +} diff --git a/auth/file.go b/auth/file.go new file mode 100644 index 0000000..3e86360 --- /dev/null +++ b/auth/file.go @@ -0,0 +1,104 @@ +package auth + +import ( + "bufio" + "bytes" + "fmt" + "os" + "strings" + + "github.com/go-crypt/crypt" + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/logger" + "git.maze.io/maze/conduit/ssh/sshutil" +) + +type passwordFile map[string]string + +func PasswordFile(name string) (Password, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + s := bufio.NewScanner(f) + p := make(passwordFile) + for s.Scan() { + line := s.Text() + if len(line) == 0 || line[0] == '#' || strings.HasPrefix(line, "//") { + continue + } + if i := strings.IndexByte(line, ':'); i > 0 { + p[line[:i]] = line[i+1:] + } + } + return p, s.Err() +} + +func (auth passwordFile) VerifyPassword(meta ssh.ConnMetadata, password string) (Principal, error) { + encodedDigest := auth[meta.User()] + if ok, err := crypt.CheckPasswordWithPlainText(password, encodedDigest); err != nil { + return nil, err + } else if !ok { + return nil, ErrUnauthorized + } + return MakePasswordPrincipal(meta), nil +} + +type publibKeyFile map[string][]ssh.PublicKey + +func PublicKeyFile(name string) (PublicKey, error) { + log := logger.StandardLog.Value("path", name) + log.Trace("Parsing public keys file") + + f, err := os.Open(name) + if err != nil { + return nil, err + } + + var ( + s = bufio.NewScanner(f) + p = make(publibKeyFile) + lineno int + ) + for s.Scan() { + line := s.Text() + lineno++ + if len(line) == 0 || line[0] == '#' || strings.HasPrefix(line, "//") { + continue + } + if i := strings.IndexByte(line, ':'); i > 0 { + //k, err := ssh.ParsePublicKey([]byte(line[i+1:])) + k, _, _, _, err := ssh.ParseAuthorizedKey([]byte(line[i+1:])) + if err != nil { + return nil, fmt.Errorf("auth: invalid public key in %s:%d: %w", name, lineno, err) + } + log.Values(logger.Values{ + "user": line[:i], + "key": k.Type(), + }).Trace("Parsed authorized public key") + p[line[:i]] = append(p[line[:i]], k) + } + } + return p, s.Err() +} + +func (auth publibKeyFile) VerifyPublicKey(meta ssh.ConnMetadata, key ssh.PublicKey) (Principal, error) { + log := logger.StandardLog.Values(logger.Values{ + "client": meta.RemoteAddr().String(), + "key_type": sshutil.KeyType(key), + "key_bits": sshutil.KeyBits(key), + "user": meta.User(), + "version": string(meta.ClientVersion()), + }) + log.Debug("Verifying user public key") + + blob := key.Marshal() + keys := auth[meta.User()] + for _, other := range keys { + if bytes.Equal(other.Marshal(), blob) { + return MakePublicKeyPrincipal(meta, key), nil + } + } + return nil, ErrUnauthorized +} diff --git a/auth/file_test.go b/auth/file_test.go new file mode 100644 index 0000000..008c58e --- /dev/null +++ b/auth/file_test.go @@ -0,0 +1,134 @@ +package auth + +import ( + "net" + "path/filepath" + "testing" + + "golang.org/x/crypto/ssh" +) + +var testAddr = &net.TCPAddr{ + IP: net.ParseIP("127.1.2.3"), + Port: 22, +} + +type testConnMetadata struct { + user string + sessionID []byte + clientVersion string + serverVersion string + laddr, raddr net.Addr +} + +func (t testConnMetadata) User() string { return t.user } +func (t testConnMetadata) SessionID() []byte { return t.sessionID } +func (t testConnMetadata) ClientVersion() []byte { return []byte(t.clientVersion) } +func (t testConnMetadata) ServerVersion() []byte { return []byte(t.serverVersion) } +func (t testConnMetadata) RemoteAddr() net.Addr { return t.raddr } +func (t testConnMetadata) LocalAddr() net.Addr { return t.laddr } + +var _ ssh.ConnMetadata = (*testConnMetadata)(nil) + +func TestPasswordFile(t *testing.T) { + a, err := PasswordFile(filepath.Join("testdata", "passwd")) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + Username string + Password string + }{ + {"example", "example"}, + {"bcrypt", "example"}, + } + for _, test := range tests { + t.Run(test.Username, func(it *testing.T) { + p, err := a.VerifyPassword(testConnMetadata{user: test.Username}, test.Password) + if err != nil { + it.Error(err) + } else { + it.Logf("%s: %s (%T)", p.Type(), p.Identity(), p) + } + }) + } +} + +func TestPublicKeyFile(t *testing.T) { + a, err := PublicKeyFile(filepath.Join("testdata", "pubkey")) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + Name string + Username string + PublicKey string + }{ + {"single/ed25519", "test_a", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFo1lt6lEk+1VUrMbhlaVpkI0p1TFUGujHaKKn7+VoGb"}, + {"dual/ed25519", "test_b", "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICA9dQjNeX3eBvkOXJN+nJm1C2W9UtRiLbK9O87Mjkir"}, + {"dual/rsa", "test_b", "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFq82Pfsg7KjTU5LN4jikxITDQhCWB3TFxQdXTgYtKt40+gv88hZkemM1MYTzR30bUX/zcRsioUSwr3u7/2La7ti+BoilsHjrEx4w+nxNGCCe8D3M6K5Xi8MPL2AqbXFqkPSEpX+psrs+qILfNhs1lWAsN7GLP0cTIxPynFNECwJnUlleN0hsn8N8bQCoUInZQGmHwIHq62H+3IPbv7Vko3J0Zrqqo4OqfeV5BA0By7ZP+2Jd9ZsLJ2efaiALcs6oTk0v95wVQ36wp605x9ePYg6zHzIZDfpA400RqeuiZF5jpiG7q3eb0+CysfMbU0BpfeHmCq15PFYqre8HKAJZ3"}, + } + for _, test := range tests { + t.Run(test.Username, func(it *testing.T) { + k, _, _, _, err := ssh.ParseAuthorizedKey([]byte(test.PublicKey)) + if err != nil { + it.Fatal(err) + } + + p, err := a.VerifyPublicKey(testConnMetadata{ + user: test.Username, + laddr: testAddr, + raddr: testAddr, + }, k) + if err != nil { + it.Error(err) + } else { + it.Logf("%s: %s (%T)", p.Type(), p.Identity(), p) + } + }) + } +} + +func BenchmarkPasswordFileHits(b *testing.B) { + a, err := PasswordFile(filepath.Join("testdata", "passwd")) + if err != nil { + b.Fatal(err) + } + + c := testConnMetadata{user: "example"} + + b.ResetTimer() + for b.Loop() { + a.VerifyPassword(c, "example") + } +} + +func BenchmarkPasswordFileMissPassword(b *testing.B) { + a, err := PasswordFile(filepath.Join("testdata", "passwd")) + if err != nil { + b.Fatal(err) + } + + c := testConnMetadata{user: "example"} + + b.ResetTimer() + for b.Loop() { + a.VerifyPassword(c, "invalid") + } +} + +func BenchmarkPasswordFileMissPrincipal(b *testing.B) { + a, err := PasswordFile(filepath.Join("testdata", "passwd")) + if err != nil { + b.Fatal(err) + } + + c := testConnMetadata{user: "invalid"} + + b.ResetTimer() + for b.Loop() { + a.VerifyPassword(c, "example") + } +} diff --git a/auth/handler.go b/auth/handler.go new file mode 100644 index 0000000..6401901 --- /dev/null +++ b/auth/handler.go @@ -0,0 +1,329 @@ +package auth + +import ( + "bytes" + "errors" + "net" + "strings" + "time" + + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/logger" + "git.maze.io/maze/conduit/policy" + "git.maze.io/maze/conduit/policy/input" +) + +// None accepts no authentication, this is only useful for debugging/testing. +type None struct{} + +func (None) HandleAccept(conn net.Conn, serverConfig *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, <-chan *ssh.Request, error) { + config := new(ssh.ServerConfig) + if serverConfig != nil { + *config = *serverConfig + } + config.NoClientAuth = true + return ssh.NewServerConn(conn, config) +} + +// UserCertificate offers user certificate based authentication. +type UserCertificate struct { + // Loader is the policy loader. + Loader policy.Loader + + // CA is our trusted user certificate authority public keys. + CA []ssh.PublicKey + + // VerifyCallback can optionally be used to perform additional checks on the certificate. + VerifyCallback func(*ssh.Certificate) bool +} + +func (auth UserCertificate) HandleAccept(conn net.Conn, serverConfig *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, <-chan *ssh.Request, error) { + log := logger.StandardLog.Value("client", conn.RemoteAddr().String()) + + policy, err := auth.Loader.LoadPolicy("conduit/auth/user_certificate") + if err != nil { + return nil, nil, nil, err + } + _ = policy + + checker := &ssh.CertChecker{ + Clock: time.Now, + } + checker.IsUserAuthority = func(key ssh.PublicKey) bool { + blob := key.Marshal() + for _, ca := range auth.CA { + if bytes.Equal(ca.Marshal(), blob) { + return true + } + } + return false + } + + config := new(ssh.ServerConfig) + if serverConfig != nil { + *config = *serverConfig + } + config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + if _, err := checker.Authenticate(conn, key); err != nil { + log.Err(err).Debug("Certificate check failed") + return nil, err + } + + cert := key.(*ssh.Certificate) + principal := UserCertificatePrincipal{Certificate: cert} + var ( + query = "data." + policy.Package() + input = struct { + Conn *input.ConnMetadata `json:"conn"` + Principal Principal `json:"principal"` + }{input.NewConnMetadata(conn), principal} + result struct { + Permit bool `mapstructure:"permit"` + } + ) + if err = policy.Query(query, input, &result); err != nil { + log.Err(err).Warn("Policy query returned error") + return nil, err + } + if !result.Permit { + log.Debug("Policy rejected certificate") + return nil, ErrUnauthorized + } + return &cert.Permissions, nil + } + + return ssh.NewServerConn(conn, config) +} + +// MultiFactor offers policy-based MFA (multi factor authentication). +type MultiFactor struct { + // Loader for the policy rules. + Loader policy.Loader + + // PublicKey enables public key authentication. + PublicKey PublicKey + + // UserCA enables user certificate based authentication. + UserCA []ssh.PublicKey + + // Password enables password authentication. + Password Password + + // Token enabled token authentication. + Token Token +} + +func (mfa *MultiFactor) HandleAccept(conn net.Conn, serverConfig *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, <-chan *ssh.Request, error) { + log := logger.StandardLog.Value("client", conn.RemoteAddr().String()) + + defer func() { + if r := recover(); r != nil { + if err, ok := r.(error); ok { + log.Err(err).Error("Recovered from panic in accept handler! This is a bug!") + } + } + }() + + rules, err := mfa.Loader.LoadPolicy("conduit/auth/mfa") + if err != nil { + return nil, nil, nil, err + } + + config := new(ssh.ServerConfig) + if serverConfig != nil { + *config = *serverConfig + } + + var ( + meta ssh.ConnMetadata + principals []Principal + tokenAuth = mfa.Token + passwordAuth = mfa.Password + publicKeyAuth = mfa.PublicKey + certificateAuth []ssh.PublicKey + ) + + checkPolicy := func() (perms *ssh.Permissions, err error) { + var ( + query = "data." + rules.Package() + input = struct { + Conn *input.ConnMetadata `json:"conn"` + Principals []Principal `json:"principals"` + }{input.NewConnMetadata(meta), principals} + result struct { + Permit bool `mapstructure:"permit"` + PermitPassword bool `mapstructure:"permit_password"` + PermitToken bool `mapstructure:"permit_token"` + PermitCertificate bool `mapstructure:"permit_certificate"` + PermitPublicKey bool `mapstructure:"permit_publickey"` + } + ) + if err = rules.Query(query, input, &result); err != nil { + log.Err(err).Warn("Policy query returned error") + return + } + + if !result.PermitPassword && passwordAuth != nil { + log.Debug("Policy disabled password authentication") + passwordAuth = nil + } + if passwordAuth == nil && config.PasswordCallback != nil { + // Disable password authentication. + log.Debug("Policy disabled password callback") + config.PasswordCallback = nil + } + + if !result.PermitToken && tokenAuth != nil { + log.Debug("Policy disabled token authentication") + tokenAuth = nil + } + if tokenAuth == nil && config.KeyboardInteractiveCallback != nil { + // Disable token authentication. + log.Debug("Policy disabled token authentication") + config.KeyboardInteractiveCallback = nil + } + + if !result.PermitCertificate && len(certificateAuth) != 0 { + log.Debug("Policy disabled certificate authentication") + certificateAuth = nil + } + if !result.PermitPublicKey && publicKeyAuth != nil { + log.Debug("Policy disabled public key authentication") + publicKeyAuth = nil + } + if publicKeyAuth == nil && len(certificateAuth) == 0 && config.PublicKeyCallback != nil { + // Disable pubkey authentication. + log.Debug("Policy disabled public key callback") + config.PublicKeyCallback = nil + } + + if !result.Permit { + log.Debug("Policy rejected principals; not authorized") + return nil, ErrUnauthorized + } + + if len(principals) == 0 { + log.Debug("No valid principals; not authorized") + return nil, ErrUnauthorized + } + + attr := principals[0].Attributes() + return &ssh.Permissions{ + CriticalOptions: attr.Options, + Extensions: attr.Extensions, + }, nil + } + + if mfa.PublicKey != nil || len(mfa.UserCA) > 0 { + log = log.Values(logger.Values{ + "permit_pubkey": mfa.PublicKey != nil, + "permit_certificate": len(mfa.UserCA) > 0, + }) + log.Trace("MFA enabling public key authentication") + + var checker *ssh.CertChecker + if len(mfa.UserCA) > 0 { + checker = &ssh.CertChecker{ + Clock: time.Now, + } + checker.IsUserAuthority = func(key ssh.PublicKey) bool { + blob := key.Marshal() + for _, ca := range mfa.UserCA { + if bytes.Equal(ca.Marshal(), blob) { + return true + } + } + return false + } + } + + config.PublicKeyCallback = func(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { + var ( + principal Principal + errs []error + ) + + if checker != nil { + if _, err := checker.Authenticate(conn, key); err != nil { + log.Err(err).Debug("Certificate check failed") + errs = append(errs, err) + } else { + principal = UserCertificatePrincipal{Certificate: key.(*ssh.Certificate)} + } + } + if principal == nil && mfa.PublicKey != nil { + if _, err := mfa.PublicKey.VerifyPublicKey(conn, key); err != nil { + log.Err(err).Debug("Public key check failed") + errs = append(errs, err) + } else { + principal = PublicKeyPrincipal{User: conn.User(), PublicKey: key} + } + } + + if principal == nil { + if len(errs) > 0 { + return nil, errors.Join(errs...) + } + return nil, ErrUnauthorized + } + + meta = conn + principals = append(principals, principal) + return checkPolicy() + } + } + + if mfa.Password != nil { + log.Trace("MFA enabling password authentication") + + config.PasswordCallback = func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { + if _, err := mfa.Password.VerifyPassword(conn, string(password)); err != nil { + return nil, err + } + + meta = conn + principals = append(principals, PasswordPrincipal{User: conn.User()}) + return checkPolicy() + } + } + + if mfa.Token != nil { + log.Trace("MFA enabling token authentication") + + config.KeyboardInteractiveCallback = func(conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) { + var ( + multiline = mfa.Token.Multiline() + token string + ) + + for { + answers, err := challenge("", mfa.Token.Instruction(), []string{"Token: "}, []bool{false}) + if err != nil { + return nil, err + } else if len(answers) != 1 { + return nil, ErrUnauthorized + } + if multiline { + if answers[0] == "" { + break + } + token += strings.TrimSpace(answers[0]) + } else { + token = answers[0] + break + } + } + + if _, err := mfa.Token.VerifyToken(conn, token); err != nil { + return nil, err + } + + meta = conn + principals = append(principals, TokenPrincipal{User: conn.User()}) + return checkPolicy() + } + } + + return ssh.NewServerConn(conn, config) +} diff --git a/auth/principal.go b/auth/principal.go new file mode 100644 index 0000000..1127dd9 --- /dev/null +++ b/auth/principal.go @@ -0,0 +1,148 @@ +package auth + +import ( + "encoding/json" + + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/policy/input" +) + +const ( + TypeCertificate = "certificate" + TypeToken = "token" + TypePassword = "password" + TypePublicKey = "publickey" +) + +type Attributes struct { + Groups []string `json:"groups"` + Principals []string `json:"principals"` + Options map[string]string `json:"options"` + Extensions map[string]string `json:"extensions"` + Source any `json:"source"` +} + +func MakeAttributes() Attributes { + return Attributes{ + Groups: make([]string, 0), + Principals: make([]string, 0), + Options: make(map[string]string), + Extensions: make(map[string]string), + } +} + +type Principal interface { + Type() string // "user", "host", "key", etc. + + // Identity is the user identity. + Identity() string + + // Attributes for the principal. + Attributes() Attributes +} + +type PasswordPrincipal struct { + User string + Attr Attributes +} + +func MakePasswordPrincipal(meta ssh.ConnMetadata) PasswordPrincipal { + attr := MakeAttributes() + attr.Source = meta + return PasswordPrincipal{ + User: meta.User(), + Attr: attr, + } +} + +func (p PasswordPrincipal) Type() string { return TypePassword } +func (p PasswordPrincipal) Identity() string { return p.User } +func (p PasswordPrincipal) Attributes() Attributes { return p.Attr } +func (p PasswordPrincipal) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Kind string `json:"type"` + User string `json:"identity"` + Attr Attributes `json:"attr"` + }{ + p.Type(), + p.Identity(), + p.Attributes(), + }) +} + +type TokenPrincipal struct { + User string `json:"identity"` + Attr Attributes `json:"attr"` +} + +func (p TokenPrincipal) Type() string { return TypeToken } +func (p TokenPrincipal) Identity() string { return p.User } +func (p TokenPrincipal) Attributes() Attributes { return p.Attr } +func (p TokenPrincipal) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Kind string `json:"type"` + User string `json:"identity"` + Attr Attributes `json:"attr"` + }{ + p.Type(), + p.Identity(), + p.Attributes(), + }) +} + +type PublicKeyPrincipal struct { + User string + ssh.PublicKey +} + +func MakePublicKeyPrincipal(meta ssh.ConnMetadata, key ssh.PublicKey) PublicKeyPrincipal { + attr := MakeAttributes() + attr.Source = meta + return PublicKeyPrincipal{ + User: meta.User(), + PublicKey: key, + } +} + +func (p PublicKeyPrincipal) Type() string { return TypePublicKey } +func (p PublicKeyPrincipal) Identity() string { return p.User } +func (p PublicKeyPrincipal) Attributes() Attributes { return MakeAttributes() } +func (p PublicKeyPrincipal) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Kind string `json:"type"` + User string `json:"identity"` + Attr Attributes `json:"attr"` + }{ + p.Type(), + p.Identity(), + p.Attributes(), + }) +} + +type UserCertificatePrincipal struct { + *ssh.Certificate +} + +func (cert UserCertificatePrincipal) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Kind string `json:"type"` + User string `json:"identity"` + Attr Attributes `json:"attr"` + }{ + cert.Type(), + cert.Identity(), + cert.Attributes(), + }) +} + +func (cert UserCertificatePrincipal) Type() string { return TypeCertificate } +func (cert UserCertificatePrincipal) Identity() string { return cert.KeyId } +func (cert UserCertificatePrincipal) Attributes() Attributes { + return Attributes{ + Principals: cert.ValidPrincipals, + Options: cert.CriticalOptions, + Extensions: cert.Extensions, + Source: input.NewCertificate(cert.Certificate), + } +} diff --git a/auth/system_linux.go b/auth/system_linux.go new file mode 100644 index 0000000..af69236 --- /dev/null +++ b/auth/system_linux.go @@ -0,0 +1,105 @@ +//go:build linux + +package auth + +import ( + "bytes" + "errors" + "os" + "os/user" + "path/filepath" + + "github.com/msteinert/pam" + "golang.org/x/crypto/ssh" +) + +type system struct{} + +type systemPrincipal struct { + *user.User + attributes Attributes +} + +func newSystemPrincipal(name string) (*systemPrincipal, error) { + u, err := user.Lookup(name) + if err != nil { + return nil, err + } + + a := Attributes{ + Custom: make(map[string]any), + } + if gids, err := u.GroupIds(); err == nil { + a.Custom["groups"] = gids + for _, gid := range gids { + if g, err := user.LookupGroupId(gid); err == nil { + a.Groups = append(a.Groups, g.Name) + } + } + } + + return &systemPrincipal{ + User: u, + attributes: a, + }, nil +} + +func (u systemPrincipal) Type() string { + return "user" +} + +func (u systemPrincipal) Identity() string { + return u.Name +} + +func (u systemPrincipal) Attributes() Attributes { + return u.attributes +} + +func SystemPassword() Password { + return system{} +} + +func (system) VerifyPassword(username, password string) (Principal, error) { + t, err := pam.StartFunc("sshd", username, func(s pam.Style, msg string) (string, error) { + switch s { + case pam.PromptEchoOff: + return password, nil + case pam.PromptEchoOn: + return username, nil + default: + return "", errors.New("unrecognized message style") + } + }) + if err != nil { + return nil, err + } + if err = t.Authenticate(0); err != nil { + return nil, err + } + + return newSystemPrincipal(username) +} + +func (system) VerifyPublicKey(username string, key ssh.PublicKey) (Principal, error) { + p, err := newSystemPrincipal(username) + if err != nil { + return nil, err + } + + rest, err := os.ReadFile(filepath.Join(p.HomeDir, ".ssh", "authorized_keys")) + if err != nil { + return nil, err + } + + for len(rest) > 0 { + var out ssh.PublicKey + if out, _, _, rest, err = ssh.ParseAuthorizedKey(rest); err != nil { + return nil, err + } + if bytes.Equal(out.Marshal(), key.Marshal()) { + return p, nil + } + } + return nil, ErrAuthorized +} diff --git a/auth/testdata/passwd b/auth/testdata/passwd new file mode 100644 index 0000000..a5edf69 --- /dev/null +++ b/auth/testdata/passwd @@ -0,0 +1,3 @@ +# This line is ignored +example:$argon2id$v=19$m=2097152,t=1,p=4$BjVeoTI4ntTQc0WkFQdLWg$OAUnkkyx5STI0Ixl+OSpv4JnI6J1TYWKuCuvIbUGHTY +bcrypt:$2y$10$jeTxJGC9SZ1KZgbBZoZeseq0H8Gi1yqxvGX43YnwodTYeUxvl6TPK diff --git a/auth/testdata/pubkey b/auth/testdata/pubkey new file mode 100644 index 0000000..feb05c3 --- /dev/null +++ b/auth/testdata/pubkey @@ -0,0 +1,5 @@ +maze:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIjJv5YZX22u40Wr+DRHH6jnCjxqk1u7rvNc6ALsCncK +maze:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCce9LTIeQ4FvyHUMzOK0NhZsVtuWIdUDOdl3j+5rtvrdsVzlLp2eqaSQEwZwPl7qek0M7+5i8DoSzZjg9MrsUsCiSXqRY9/G3M/KwL+MaL0116R7uwkQX+ndKvUvnqpjKKOJ/PsBFmZlXKYPDy6SFJURpmRATiafenEkg5D64fxBFw1k66ZXcQ81aYAGjpV8nqE18DcfVfj6czadpQ5Ycf/D4InUTPKTuFa2lMdVqrZ1+S6DDGIQdG9HEI7IpzFfYvGEFQc2x4BaeHroNym/k9PtIH+4debEDkrZ7Aaq/ofXTWoWLR4KoZoHcyUSGlPT+M9/aICgqaza/VZfPgobbiXRQTwNfNe4lUcbAhracX4RQDJbyPlHMtGAuDrIi6WLyBKZp4ehZPIT00YbPMP5BRTcPPwUneYVL2D1vbgWLO+0GsYfdr7fsm5TPd6fkajNj08ZSOWYRdJuoZJkplqM1GETlXkv5ictZ9k2Nm6K0qxBKIxaaBMm46jvcTM9K2YRK7QdvLTrXQzUYvR6HqcHUN3rAMT0LuyKY69FzJXuKaO4HYHQB0MaXJW/CVIo1xR92sqHUmcKn5nwyccT4hUscPAx/lNE7AZ8qcTqUqYRACF9dJhKOZD2dyO8dBHgnCqkg5JfifmbdCSEc2nGrGPx3rHRj/JXNqI404T27yB26mQw== +test_a:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFo1lt6lEk+1VUrMbhlaVpkI0p1TFUGujHaKKn7+VoGb +test_b:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICA9dQjNeX3eBvkOXJN+nJm1C2W9UtRiLbK9O87Mjkir +test_b:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFq82Pfsg7KjTU5LN4jikxITDQhCWB3TFxQdXTgYtKt40+gv88hZkemM1MYTzR30bUX/zcRsioUSwr3u7/2La7ti+BoilsHjrEx4w+nxNGCCe8D3M6K5Xi8MPL2AqbXFqkPSEpX+psrs+qILfNhs1lWAsN7GLP0cTIxPynFNECwJnUlleN0hsn8N8bQCoUInZQGmHwIHq62H+3IPbv7Vko3J0Zrqqo4OqfeV5BA0By7ZP+2Jd9ZsLJ2efaiALcs6oTk0v95wVQ36wp605x9ePYg6zHzIZDfpA400RqeuiZF5jpiG7q3eb0+CysfMbU0BpfeHmCq15PFYqre8HKAJZ3 diff --git a/cmd/conduit/config.go b/cmd/conduit/config.go new file mode 100644 index 0000000..f900823 --- /dev/null +++ b/cmd/conduit/config.go @@ -0,0 +1,187 @@ +package main + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/gohcl" + "github.com/hashicorp/hcl/v2/hclsimple" + + "git.maze.io/maze/conduit/auth" + "git.maze.io/maze/conduit/policy" + "git.maze.io/maze/conduit/provider" + "git.maze.io/maze/conduit/ssh" +) + +type Config struct { + Listen []*ListenConfig `hcl:"listen,block"` + Provider []*ProviderConfig `hcl:"provider,block"` +} + +type ListenConfig struct { + Addr string `hcl:"addr,label"` + Keys []string `hcl:"keys"` + MaxConnections int `hcl:"max_connections,optional"` + DebugSession bool `hcl:"debug_session,optional"` + AllowTunnel bool `hcl:"allow_tunnel,optional"` + Auth *AuthConfig `hcl:"auth,block"` +} + +func (c ListenConfig) Server() (*ssh.Server, error) { + var keys []ssh.Signer + + if len(c.Keys) == 0 { + return nil, fmt.Errorf("listen %q has no keys configured", c.Addr) + } + for _, name := range c.Keys { + k, err := ssh.LoadPrivateKey(name) + if err != nil { + return nil, err + } + keys = append(keys, k) + } + + var ( + server = ssh.NewServer(keys) + err error + ) + + if c.Auth == nil { + return nil, fmt.Errorf("listen %q has no auth configured", c.Addr) + } + if server.AcceptHandler, err = c.Auth.AcceptHandler(); err != nil { + return nil, fmt.Errorf("listen %q: %w", c.Addr, err) + } + + if c.MaxConnections > 0 { + server.ConnectHandler = append(server.ConnectHandler, ssh.MaxConnections(c.MaxConnections)) + } + if c.DebugSession { + server.ChannelHandler[ssh.ChannelTypeSession] = ssh.DebugSession() + } + if c.AllowTunnel { + //server.ChannelHandler[ssh.ChannelTypeDirectTCPIP] = ssh.ForwardTunnel(nil) + server.PortForwardHandler = ssh.PortForwardDialer(nil) + } + + return server, nil +} + +type AuthConfig struct { + Type string `hcl:"type,label"` + Body hcl.Body `hcl:",remain"` +} + +func (c AuthConfig) AcceptHandler() (ssh.AcceptHandler, error) { + switch c.Type { + /* + case "password_file": + var config struct { + Path string `hcl:"path"` + } + if diag := gohcl.DecodeBody(c.Body, nil, &config); diag.HasErrors() { + return nil, diag + } + return auth.PasswordFile(config.Path) + + case "pubkey_file": + var config struct { + Path string `hcl:"path"` + } + if diag := gohcl.DecodeBody(c.Body, nil, &config); diag.HasErrors() { + return nil, diag + } + return auth.PublicKeyFile(config.Path) + + case "user_ca": + var config struct { + Keys []string `hcl:"keys"` + } + if diag := gohcl.DecodeBody(c.Body, nil, &config); diag.HasErrors() { + return nil, diag + } + + keys := make([]ssh.PublicKey, 0, len(config.Keys)) + for _, s := range config.Keys { + k, _, err := ssh.ParseAuthorizedKey([]byte(s)) + if err != nil { + return nil, err + } + keys = append(keys, k) + } + + return auth.NewUserCertificateAuthority(keys...), nil + */ + + case "mfa": + var config struct { + Policy string `hcl:"policy"` + Token string `hcl:"token,optional"` + PasswordFile string `hcl:"password_file,optional"` + UserCA []string `hcl:"user_ca,optional"` + } + if diag := gohcl.DecodeBody(c.Body, nil, &config); diag.HasErrors() { + return nil, diag + } + + userCAKeys := make([]ssh.PublicKey, len(config.UserCA)) + for i, key := range config.UserCA { + k, _, err := ssh.ParseAuthorizedKey([]byte(key)) + if err != nil { + return nil, err + } + userCAKeys[i] = k + } + + var passwordAuth auth.Password + if config.PasswordFile != "" { + var err error + if passwordAuth, err = auth.PasswordFile(config.PasswordFile); err != nil { + return nil, err + } + } + + var tokenAuth auth.Password + if config.Token != "" { + var err error + if tokenAuth, err = auth.PasswordFile(config.Token); err != nil { + return nil, err + } + } + _ = tokenAuth + + return &auth.MultiFactor{ + Loader: policy.FileSystemLoader{Root: config.Policy}, + UserCA: userCAKeys, + //Token: tokenAuth, + Password: passwordAuth, + }, nil + + default: + return nil, fmt.Errorf("auth: unsupported type %q", c.Type) + } +} + +type ProviderConfig struct { + Name string `hcl:"name,label"` + Body hcl.Body `hcl:",remain"` +} + +func (c ProviderConfig) Init() error { + return provider.Init(c.Name, c.Body) +} + +func Load(name string) (*Config, error) { + var config = new(Config) + if err := hclsimple.DecodeFile(name, nil, config); err != nil { + return nil, err + } + + for _, providerConfig := range config.Provider { + if err := providerConfig.Init(); err != nil { + return nil, err + } + } + + return config, nil +} diff --git a/cmd/conduit/main.go b/cmd/conduit/main.go new file mode 100644 index 0000000..5ef7b82 --- /dev/null +++ b/cmd/conduit/main.go @@ -0,0 +1,47 @@ +package main + +import ( + "flag" + "net" + "sync" + + "git.maze.io/maze/conduit/logger" + "git.maze.io/maze/conduit/ssh" + + _ "git.maze.io/maze/conduit/provider/okta" // Okta support +) + +func main() { + configFlag := flag.String("config", "conduit.hcl", "configuration file path") + flag.Parse() + + logger.SetLevel(logger.TraceLevel) + + config, err := Load(*configFlag) + if err != nil { + logger.Err(err).Fatal("Error loading configuration") + } + + servers := make([]*ssh.Server, len(config.Listen)) + for i, listenConfig := range config.Listen { + if servers[i], err = listenConfig.Server(); err != nil { + logger.Err(err).Fatal("Error configuring listener") + } + } + + var wg sync.WaitGroup + for i, server := range servers { + wg.Go(func() { + l, err := net.Listen("tcp", config.Listen[i].Addr) + if err != nil { + logger.StandardLog.Err(err).Fatal("Can't bind to listening address") + } + + logger.StandardLog.Value("server", l.Addr().String()).Info("Server starting") + if err = server.Serve(l); err != nil { + logger.StandardLog.Err(err).Error("Error serving connections") + } + }) + } + wg.Wait() +} diff --git a/conduit.hcl b/conduit.hcl new file mode 100644 index 0000000..7a655b5 --- /dev/null +++ b/conduit.hcl @@ -0,0 +1,62 @@ +listen ":2222" { + keys = [ + "testdata/conduit.rsa", + "testdata/conduit.ed25519", + ] + debug_session = true + allow_tunnel = true + + auth "mfa" { + policy = "testdata/policy" + token = "auth/testdata/passwd" + user_ca = [ + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCufO7OUqeLZkUX7qGatOk79nZTQGqCbHxTp6z8Nb+52HJiRDXgLfY/3zLX/3kOdjVrQujwEEfbD6IVOjF3gnDkgYvjnJROXEiv3k2UApYzrbJebFohcFPrrk3WqbzOeMQXciEuSNyJV33FMYBnZ8Y+Yrf5a4x5R0pxbmdCmxSOhihOIZYlKNjPq3UVfXwth/NW4KiHUkmuH6d4x4D3OMJ+xKeK9Eu05szBWwRHY3vplf0SYiwDd3xPlFalPG2UzA3j/+kdtQf0qNJGyWpjRjHv9BvJMP/G+Y2CckvygetYBfcvX9JGb/p8G1JyU55ODD5maxDrCSFm8aqDbvCLmeAb", + ] + } +} + +provider "auth0" { + client { + url = "https://dev-maze.eu.auth0.com" + client_id = "DANKWsl8WRGY2pXQoB7CCwSkfoB8ouda" + client_secret = "VSPT2o10WQoJZSO3Y18tmWpS6MG0uDNIytn1-cDwPhp7AiMo8gVsLIBUOdF-ilCP" + } +} + +provider "okta" { + client { + org_url = "https://integrator-8134036.okta.com/" + client_id = "0oaw2uk8mnkKEJJ7s697" + token = "00ebjg06VGdf9Y_AdUf494gf5pcBlURl8of02irbRw" + private_key = < ./provider/okta + +require ( + git.maze.io/maze/conduit/provider/okta v0.0.0-00010101000000-000000000000 + github.com/go-crypt/crypt v0.4.6 + github.com/go-viper/mapstructure/v2 v2.4.0 + github.com/hashicorp/hcl/v2 v2.24.0 + github.com/msteinert/pam v1.2.0 + github.com/open-policy-agent/opa v1.9.0 + github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af + golang.org/x/crypto v0.42.0 +) + +require ( + github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 // indirect + github.com/agext/levenshtein v1.2.1 // indirect + github.com/agnivade/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect + github.com/go-crypt/x v0.4.8 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-yaml/yaml v2.1.0+incompatible // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/kelseyhightower/envconfig v1.3.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.4 // indirect + github.com/lestrrat-go/dsig v1.0.0 // indirect + github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc/v3 v3.0.1 // indirect + github.com/lestrrat-go/jwx/v3 v3.0.11 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect + github.com/lestrrat-go/option/v2 v2.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/okta/okta-sdk-golang v1.1.0 // indirect + github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect + github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 // indirect + github.com/segmentio/asm v1.2.0 // indirect + github.com/square/go-jose v2.4.1+incompatible // indirect + github.com/tchap/go-patricia/v2 v2.3.3 // indirect + github.com/valyala/fastjson v1.6.4 // indirect + github.com/vektah/gqlparser/v2 v2.5.30 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/yashtewari/glob-intersection v0.2.0 // indirect + github.com/zclconf/go-cty v1.16.3 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/square/go-jose.v2 v2.4.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..56fe107 --- /dev/null +++ b/go.sum @@ -0,0 +1,166 @@ +github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5 h1:IEjq88XO4PuBDcvmjQJcQGg+w+UaafSy8G5Kcb5tBhI= +github.com/GehirnInc/crypt v0.0.0-20230320061759-8cc1b52080c5/go.mod h1:exZ0C/1emQJAw5tHOaUDyY1ycttqBAPcxuzf7QbY6ec= +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= +github.com/go-crypt/crypt v0.4.6 h1:pC2CdIsCAjhvse6Q9oXZH97cV2iGqFOxE8HQCjY1cNg= +github.com/go-crypt/crypt v0.4.6/go.mod h1:Ts3T2ORhE0nxel6/2mlQNZTZn7dcxRdtnSJjoDqUkbs= +github.com/go-crypt/x v0.4.8 h1:Cob6IxrSfWTc+MG8CBbNHBM4UqrBgEZDoK5t/SG4oZ4= +github.com/go-crypt/x v0.4.8/go.mod h1:ozw9N4MYuLKhR5x2REs1e4T/nrEAkbuVkcsh/HbYksg= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM= +github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= +github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= +github.com/lestrrat-go/dsig v1.0.0 h1:OE09s2r9Z81kxzJYRn07TFM9XA4akrUdoMwr0L8xj38= +github.com/lestrrat-go/dsig v1.0.0/go.mod h1:dEgoOYYEJvW6XGbLasr8TFcAxoWrKlbQvmJgCR0qkDo= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= +github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc/v3 v3.0.1 h1:3n7Es68YYGZb2Jf+k//llA4FTZMl3yCwIjFIk4ubevI= +github.com/lestrrat-go/httprc/v3 v3.0.1/go.mod h1:2uAvmbXE4Xq8kAUjVrZOq1tZVYYYs5iP62Cmtru00xk= +github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= +github.com/lestrrat-go/jwx/v3 v3.0.11 h1:yEeUGNUuNjcez/Voxvr7XPTYNraSQTENJgtVTfwvG/w= +github.com/lestrrat-go/jwx/v3 v3.0.11/go.mod h1:XSOAh2SiXm0QgRe3DulLZLyt+wUuEdFo81zuKTLcvgQ= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= +github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= +github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE= +github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/okta/okta-sdk-golang v1.1.0 h1:sr/KYSMRhs4F2NWEbqWXqN4y4cKKcfzrtOiBqR/J6mI= +github.com/okta/okta-sdk-golang v1.1.0/go.mod h1:KEjmr3Zo+wP3gVa3XhwIvENBfh7L/iRUeIl6ruQYOK0= +github.com/open-policy-agent/opa v1.9.0 h1:QWFNwbcc29IRy0xwD3hRrMc/RtSersLY1Z6TaID3vgI= +github.com/open-policy-agent/opa v1.9.0/go.mod h1:72+lKmTda0O48m1VKAxxYl7MjP/EWFZu9fxHQK2xihs= +github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 h1:pSCLCl6joCFRnjpeojzOpEYs4q7Vditq8fySFG5ap3Y= +github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 h1:bsUq1dX0N8AOIL7EB/X911+m4EHsnWEHeJ0c+3TTBrg= +github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af h1:Sp5TG9f7K39yfB+If0vjp97vuT74F72r8hfRpP8jLU0= +github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/square/go-jose v2.4.1+incompatible h1:KFYc54wTtgnd3x4B/Y7Zr1s/QaEx2BNzRsB3Hae5LHo= +github.com/square/go-jose v2.4.1+incompatible/go.mod h1:7MxpAF/1WTVUu8Am+T5kNy+t0902CaLWM4Z745MkOa8= +github.com/square/go-jose/v3 v3.0.0-20200225220504-708a9fe87ddc/go.mod h1:JbpHhNyeVc538vtj/ECJ3gPYm1VEitNjsLhm4eJQQbg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tchap/go-patricia/v2 v2.3.3 h1:xfNEsODumaEcCcY3gI0hYPZ/PcpVv5ju6RMAhgwZDDc= +github.com/tchap/go-patricia/v2 v2.3.3/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/valyala/fastjson v1.6.4 h1:uAUNq9Z6ymTgGhcm0UynUAB6tlbakBrz6CQFax3BXVQ= +github.com/valyala/fastjson v1.6.4/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= +github.com/vektah/gqlparser/v2 v2.5.30 h1:EqLwGAFLIzt1wpx1IPpY67DwUujF1OfzgEyDsLrN6kE= +github.com/vektah/gqlparser/v2 v2.5.30/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= +github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= +github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk= +github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/netutil/conn.go b/internal/netutil/conn.go new file mode 100644 index 0000000..04ffd12 --- /dev/null +++ b/internal/netutil/conn.go @@ -0,0 +1,37 @@ +package netutil + +import ( + "errors" + "io" + "net" + "syscall" + + "git.maze.io/maze/conduit/logger" +) + +type ConnCloser struct { + net.Conn + Closer func() error +} + +func (c *ConnCloser) Close() error { + if c.Closer == nil { + return c.Conn.Close() + } + return c.Closer() +} + +// IsClosing checks if the error is because the connection was closed. +func IsClosing(err error) bool { + if errors.Is(err, io.EOF) || errors.Is(err, io.ErrClosedPipe) || errors.Is(err, syscall.ECONNRESET) { + return true + } + if err, ok := err.(net.Error); ok && err.Timeout() { + return true + } + if err, ok := err.(*net.OpError); ok && err.Op == "close" { + return true + } + logger.Debugf("not a closing error %T: %#+v", err, err) + return false +} diff --git a/internal/stringutil/iter.go b/internal/stringutil/iter.go new file mode 100644 index 0000000..3c1538c --- /dev/null +++ b/internal/stringutil/iter.go @@ -0,0 +1,23 @@ +package stringutil + +import "sort" + +func MapKeys(m map[string]string) <-chan string { + ch := make(chan string) + if m == nil { + close(ch) + } else { + go func(ch chan<- string) { + defer close(ch) + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + ch <- k + } + }(ch) + } + return ch +} diff --git a/logger/log.go b/logger/log.go new file mode 100644 index 0000000..6483d47 --- /dev/null +++ b/logger/log.go @@ -0,0 +1,231 @@ +package logger + +import "github.com/sirupsen/logrus" + +type Level int + +const ( + TraceLevel Level = -1 + iota + DebugLevel + InfoLevel + WarnLevel + ErrorLevel + PanicLevel + FatalLevel +) + +// Logger is a generic logging interface, similar to logrus's [logrus.ValueLogger]. +// It is used in Styx for logging, so that users can plug in their own logging implementations. +type Logger interface { + SetLevel(Level) + GetLevel() Level + Trace(...any) + Tracef(string, ...any) + Debug(...any) + Debugf(string, ...any) + Info(...any) + Infof(string, ...any) + Warn(...any) + Warnf(string, ...any) + Error(...any) + Errorf(string, ...any) + Panic(...any) + Panicf(string, ...any) + Fatal(...any) + Fatalf(string, ...any) +} + +type Structured interface { + Logger + + // Err adds an error to the log entry and returns the new logger. + Err(error) Structured + + // Value returns a new logger with the specified Value added to the log entry. + Value(string, any) Structured + + // Values returns a new logger with the specified Values added to the log entry. + Values(Values) Structured +} + +type Values map[string]any + +// Alias. +type V = Values + +// StandardLog is the logger used by the package-level exported functions. +var StandardLog = NewStandardLogger() + +// SetLogger sets the logger used by the package-level exported functions. +func SetLogger(logger Structured) { + StandardLog = logger +} + +// Get returns the logger used by the package-level exported functions. +func Get() Structured { + return StandardLog +} + +type standardLogger struct { + *logrus.Logger +} + +// NewStandardLogger returns a new Structured logger that wraps the standard logrus logger. +func NewStandardLogger() Structured { + return standardLogger{logrus.StandardLogger()} +} + +type standardLoggerEntry struct { + standardLogger + *logrus.Entry +} + +func SetLevel(level Level) { + StandardLog.SetLevel(level) +} + +func (l standardLogger) SetLevel(level Level) { + switch level { + case TraceLevel: + l.Logger.SetLevel(logrus.TraceLevel) + case DebugLevel: + l.Logger.SetLevel(logrus.DebugLevel) + case InfoLevel: + l.Logger.SetLevel(logrus.InfoLevel) + case WarnLevel: + l.Logger.SetLevel(logrus.WarnLevel) + case ErrorLevel: + l.Logger.SetLevel(logrus.ErrorLevel) + case PanicLevel: + l.Logger.SetLevel(logrus.PanicLevel) + case FatalLevel: + l.Logger.SetLevel(logrus.FatalLevel) + } +} + +func GetLevel() Level { + return StandardLog.GetLevel() +} + +func (l standardLogger) GetLevel() Level { + switch l.Logger.GetLevel() { + case logrus.TraceLevel: + return TraceLevel + case logrus.DebugLevel: + return DebugLevel + case logrus.InfoLevel: + return InfoLevel + case logrus.WarnLevel: + return WarnLevel + case logrus.ErrorLevel: + return ErrorLevel + case logrus.PanicLevel: + return PanicLevel + case logrus.FatalLevel: + return FatalLevel + default: + return InfoLevel + } +} + +func Err(err error) Structured { + return StandardLog.Err(err) +} + +func (l standardLogger) Err(err error) Structured { + return standardLoggerEntry{l, l.Logger.WithError(err)} +} + +func Value(key string, value any) Structured { + return StandardLog.Value(key, value) +} + +func (l standardLogger) Value(key string, value any) Structured { + return standardLoggerEntry{l, l.Logger.WithField(key, value)} +} + +func (l standardLogger) Values(Values Values) Structured { + return standardLoggerEntry{l, l.Logger.WithFields(logrus.Fields(Values))} +} + +func (l standardLoggerEntry) Err(err error) Structured { + return standardLoggerEntry{l.standardLogger, l.Entry.WithError(err)} +} + +func (l standardLoggerEntry) Value(key string, value any) Structured { + return standardLoggerEntry{l.standardLogger, l.Entry.WithField(key, value)} +} + +func (l standardLoggerEntry) Values(Values Values) Structured { + return standardLoggerEntry{l.standardLogger, l.Entry.WithFields(logrus.Fields(Values))} +} + +// Trace logs a message at level Trace on the standard logger. +func Trace(args ...any) { + StandardLog.Trace(args...) +} + +// Tracef logs a message at level Trace on the standard logger. +func Tracef(format string, args ...any) { + StandardLog.Tracef(format, args...) +} + +// Debug logs a message at level Debug on the standard logger. +func Debug(args ...any) { + StandardLog.Debug(args...) +} + +// Debugf logs a message at level Debug on the standard logger. +func Debugf(format string, args ...any) { + StandardLog.Debugf(format, args...) +} + +// Info logs a message at level Info on the standard logger. +func Info(args ...any) { + StandardLog.Info(args...) +} + +// Infof logs a message at level Info on the standard logger. +func Infof(format string, args ...any) { + StandardLog.Infof(format, args...) +} + +// Warn logs a message at level Warn on the standard logger. +func Warn(args ...any) { + StandardLog.Warn(args...) +} + +// Warnf logs a message at level Warn on the standard logger. +func Warnf(format string, args ...any) { + StandardLog.Warnf(format, args...) +} + +// Error logs a message at level Error on the standard logger. +func Error(args ...any) { + StandardLog.Error(args...) +} + +// Errorf logs a message at level Error on the standard logger. +func Errorf(format string, args ...any) { + StandardLog.Errorf(format, args...) +} + +// Panic logs a message at level Panic on the standard logger. +func Panic(args ...any) { + StandardLog.Panic(args...) +} + +// Panicf logs a message at level Panic on the standard logger. +func Panicf(format string, args ...any) { + StandardLog.Panicf(format, args...) +} + +// Fatal logs a message at level Fatal on the standard logger then the process will exit. +func Fatal(args ...any) { + StandardLog.Fatal(args...) +} + +// Fatalf logs a message at level Fatal on the standard logger then the process will exit. +func Fatalf(format string, args ...any) { + StandardLog.Fatalf(format, args...) +} diff --git a/policy/input/net.go b/policy/input/net.go new file mode 100644 index 0000000..32082ea --- /dev/null +++ b/policy/input/net.go @@ -0,0 +1,42 @@ +package input + +import ( + "net" +) + +// Addr represents a [net.Addr]. +type Addr struct { + Network string `json:"network"` // Type of address. + IP string `json:"ip"` // IP address. + Port int `json:"port,omitempty"` // Port (if any). +} + +func NewAddr(addr net.Addr) *Addr { + if addr == nil { + return nil + } + switch addr := addr.(type) { + case *net.IPAddr: + return &Addr{ + Network: addr.Network(), + IP: addr.IP.String(), + } + case *net.TCPAddr: + return &Addr{ + Network: addr.Network(), + IP: addr.IP.String(), + Port: addr.Port, + } + case *net.UDPAddr: + return &Addr{ + Network: addr.Network(), + IP: addr.IP.String(), + Port: addr.Port, + } + default: + return &Addr{ + Network: addr.Network(), + IP: addr.String(), + } + } +} diff --git a/policy/input/ssh.go b/policy/input/ssh.go new file mode 100644 index 0000000..5d23986 --- /dev/null +++ b/policy/input/ssh.go @@ -0,0 +1,98 @@ +package input + +import ( + "time" + + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/ssh/sshutil" +) + +// Certificate represents a [ssh.Certificate]. +type Certificate struct { + Nonce []byte `json:"nonce"` + Key *PublicKey `json:"key"` + Serial uint64 `json:"serial"` + CertType uint32 `json:"type"` + KeyId string `json:"key_id"` + ValidPrincipals []string `json:"valid_principals"` + ValidAfter *time.Time `json:"valid_after"` + ValidBefore *time.Time `json:"valid_before"` + SignatureKey *PublicKey `json:"signature_key"` + Signature []byte `json:"signature"` + SignatureFormat string `json:"signature_format"` +} + +// NewCertificate converts an [ssh.Certificate] to [Certificate] input. +func NewCertificate(cert *ssh.Certificate) *Certificate { + if cert == nil { + return nil + } + c := &Certificate{ + Nonce: cert.Nonce, + Key: NewPublicKey(cert.Key), + Serial: cert.Serial, + CertType: cert.CertType, + KeyId: cert.KeyId, + ValidPrincipals: cert.ValidPrincipals, + SignatureKey: NewPublicKey(cert.SignatureKey), + } + if cert.ValidAfter > 0 { + t := time.Unix(int64(cert.ValidAfter), 0) + c.ValidAfter = &t + } + if cert.ValidBefore > 0 { + t := time.Unix(int64(cert.ValidBefore), 0) + c.ValidBefore = &t + } + + if cert.Signature != nil { + c.Signature = cert.Signature.Blob + c.SignatureFormat = cert.Signature.Format + } + return c +} + +// ConnMetadata is a Rego input that represents a [ssh.ConnMetadata]. +type ConnMetadata struct { + User string `json:"user"` // User is the user ID for this connection. + SessionID []byte `json:"session_id"` // SessionID is the session hash, also denoted by H. + ClientVersion string `json:"client_version"` // ClientVersion is the client's version. + ServerVersion string `json:"server_version"` // ServerVersion is the server's version + RemoteAddr *Addr `json:"remote_addr"` // RemoteAddr is the remote address for this connection. + LocalAddr *Addr `json:"local_addr"` // LocalAddr is the local address for this connection. +} + +// NewConnMetadata converts an [ssh.ConnMetadata] to [ConnMetadata] input. +func NewConnMetadata(meta ssh.ConnMetadata) *ConnMetadata { + if meta == nil { + return nil + } + return &ConnMetadata{ + User: meta.User(), + SessionID: meta.SessionID(), + ClientVersion: string(meta.ClientVersion()), + ServerVersion: string(meta.ServerVersion()), + RemoteAddr: NewAddr(meta.RemoteAddr()), + LocalAddr: NewAddr(meta.LocalAddr()), + } +} + +// PublicKey represents a [ssh.PublicKey]. +type PublicKey struct { + Type string `json:"type"` + Bits int `json:"bits"` + Fingerprint string `json:"fingerprint"` +} + +// NewPublicKey converts an [ssh.PublicKey] to [PublicKey] input. +func NewPublicKey(key ssh.PublicKey) *PublicKey { + if key == nil { + return nil + } + return &PublicKey{ + Type: sshutil.KeyType(key), + Bits: sshutil.KeyBits(key), + Fingerprint: ssh.FingerprintSHA256(key), + } +} diff --git a/policy/policy.go b/policy/policy.go new file mode 100644 index 0000000..f156928 --- /dev/null +++ b/policy/policy.go @@ -0,0 +1,157 @@ +package policy + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "os" + "path/filepath" + "strings" + + "github.com/go-viper/mapstructure/v2" + "github.com/open-policy-agent/opa/v1/rego" + + "git.maze.io/maze/conduit/logger" +) + +type Policy interface { + // Package name. + Package() string + + // Query is the policy query. + Query(query string, input, result any) error + + // Verify the policy definition. + Verify() error +} + +type Loader interface { + LoadPolicy(name string) (Policy, error) +} + +type FileSystemLoader struct { + Root string +} + +func (l FileSystemLoader) LoadPolicy(name string) (Policy, error) { + path := name + if !strings.ContainsRune(filepath.Base(name), '.') || filepath.Ext(path) == "" { + path += ".rego" + } + + log := logger.StandardLog.Value("policy", path) + if !filepath.IsAbs(path) { + var err error + if path, err = filepath.Abs(filepath.Join(l.Root, path)); err != nil { + log.Err(err).Warn("Error resolving absolute policy path") + return nil, err + } + } + + b, err := os.ReadFile(path) + if err != nil { + log.Err(err).Value("path", path).Warn("Error reading policy file") + return nil, err + } + pkg, err := decodePackage(b) + if err != nil { + log.Err(err).Warn("Error decoding policy package") + return nil, err + } + + return &policyFile{ + path: []string{path}, + pkg: pkg, + }, nil +} + +func decodePackage(b []byte) (name string, err error) { + s := bufio.NewScanner(bytes.NewReader(b)) + for s.Scan() { + line := s.Text() + if strings.HasPrefix(line, "package ") { + return strings.TrimSpace(line[len("package "):]), nil + } + } + if err = s.Err(); err != nil { + return + } + return "", errors.New("policy: no package name found") +} + +type policyFile struct { + path []string + pkg string +} + +func (p policyFile) Package() string { + return p.pkg +} + +func (p policyFile) Query(query string, input, value any) error { + log := logger.StandardLog.Values(logger.Values{ + "policy": p.path[0], + "package": p.pkg, + "query": query, + }) + log.Trace("Policy query evaluating") + + options := []func(*rego.Rego){ + rego.Dump(os.Stderr), + rego.Query(query), + rego.Load(p.path, nil), + rego.Strict(true), + } + + if input != nil { + debug := json.NewEncoder(os.Stdout) + debug.SetIndent("", " ") + debug.Encode(input) + options = append(options, rego.Input(input)) + } + + ctx := context.TODO() + q, err := rego.New(options...).PrepareForEval(ctx) + if err != nil { + log.Err(err).Warn("Policy query prepare failed") + return err + } + results, err := q.Eval(ctx) + if err != nil { + log.Err(err).Warn("Policy evaluation failed") + return err + } + + log = log.Value("results", len(results)) + if value == nil { + log.Debug("Policy query results processing ended, nil return value") + return nil + } + + log.Debug("Policy query results processing") + mapped := make(map[string]any) + for _, result := range results { + for _, expr := range result.Expressions { + if value, ok := expr.Value.(map[string]any); ok { + for k, v := range value { + mapped[k] = v + } + } else { + log.Debugf("Unhandled expression value %T", expr) + } + } + } + log.Value("mapped", mapped).Debug("Policy query results done") + + return mapstructure.Decode(mapped, value) +} + +func (p policyFile) Verify() error { + _, err := rego.New( + rego.Strict(true), + rego.Load(p.path, nil), + ).PrepareForEval(context.TODO()) + return err +} diff --git a/provider/okta/go.mod b/provider/okta/go.mod new file mode 100644 index 0000000..b1725d8 --- /dev/null +++ b/provider/okta/go.mod @@ -0,0 +1,29 @@ +module git.maze.io/maze/conduit/provider/okta + +go 1.25.0 + +replace git.maze.io/maze/conduit => ../.. + +require ( + git.maze.io/maze/conduit v0.0.0-00010101000000-000000000000 + github.com/hashicorp/hcl/v2 v2.24.0 + github.com/okta/okta-sdk-golang v1.1.0 +) + +require ( + github.com/agext/levenshtein v1.2.1 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/go-yaml/yaml v2.1.0+incompatible // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/kelseyhightower/envconfig v1.3.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 // indirect + github.com/square/go-jose v2.4.1+incompatible // indirect + github.com/zclconf/go-cty v1.16.3 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect + gopkg.in/square/go-jose.v2 v2.4.1 // indirect +) diff --git a/provider/okta/go.sum b/provider/okta/go.sum new file mode 100644 index 0000000..1a1964d --- /dev/null +++ b/provider/okta/go.sum @@ -0,0 +1,58 @@ +github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= +github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o= +github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/jarcoal/httpmock v1.0.4/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= +github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM= +github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lestrrat-go/jwx v0.9.0/go.mod h1:iEoxlYfZjvoGpuWwxUz+eR5e6KTJGsaRcy/YNA/UnBk= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/okta/okta-sdk-golang v1.1.0 h1:sr/KYSMRhs4F2NWEbqWXqN4y4cKKcfzrtOiBqR/J6mI= +github.com/okta/okta-sdk-golang v1.1.0/go.mod h1:KEjmr3Zo+wP3gVa3XhwIvENBfh7L/iRUeIl6ruQYOK0= +github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627 h1:pSCLCl6joCFRnjpeojzOpEYs4q7Vditq8fySFG5ap3Y= +github.com/patrickmn/go-cache v0.0.0-20180815053127-5633e0862627/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/square/go-jose v2.4.1+incompatible h1:KFYc54wTtgnd3x4B/Y7Zr1s/QaEx2BNzRsB3Hae5LHo= +github.com/square/go-jose v2.4.1+incompatible/go.mod h1:7MxpAF/1WTVUu8Am+T5kNy+t0902CaLWM4Z745MkOa8= +github.com/square/go-jose/v3 v3.0.0-20200225220504-708a9fe87ddc/go.mod h1:JbpHhNyeVc538vtj/ECJ3gPYm1VEitNjsLhm4eJQQbg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/zclconf/go-cty v1.16.3 h1:osr++gw2T61A8KVYHoQiFbFd1Lh3JOCXc/jFLJXKTxk= +github.com/zclconf/go-cty v1.16.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/provider/okta/provider.go b/provider/okta/provider.go new file mode 100644 index 0000000..eb0dbb2 --- /dev/null +++ b/provider/okta/provider.go @@ -0,0 +1,60 @@ +package okta + +import ( + "context" + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/gohcl" + "github.com/okta/okta-sdk-golang/okta" + + "git.maze.io/maze/conduit/provider" +) + +func init() { + provider.Register(&provider.Config{ + Name: "okta", + Init: setup, + }) +} + +var ( + configuration []okta.ConfigSetter + client *okta.Client +) + +func setup(body hcl.Body) (err error) { + var config struct { + Client struct { + OrgURL string `hcl:"org_url"` + Token string `hcl:"token,optional"` + ClientID string `hcl:"client_id"` + PrivateKey string `hcl:"private_key,optional"` + JWT string `hcl:"jwt,optional"` + } `hcl:"client,block"` + } + if diag := gohcl.DecodeBody(body, nil, &config); diag.HasErrors() { + return diag + } + + configuration = []okta.ConfigSetter{ + okta.WithOrgUrl(config.Client.OrgURL), + okta.WithClientId(config.Client.ClientID), + } + if config.Client.Token != "" { + configuration = append(configuration, okta.WithToken(config.Client.Token)) + } + if config.Client.PrivateKey != "" { + configuration = append(configuration, okta.WithPrivateKey(config.Client.PrivateKey)) + } + + if client, err = okta.NewClient(context.TODO(), configuration...); err != nil { + return fmt.Errorf("okta: %w", err) + } + + if _, _, err = client.User.ListUsers(nil); err != nil { + return fmt.Errorf("okta: %w", err) + } + + return +} diff --git a/provider/provider.go b/provider/provider.go new file mode 100644 index 0000000..4f19e05 --- /dev/null +++ b/provider/provider.go @@ -0,0 +1,29 @@ +package provider + +import ( + "fmt" + + "github.com/hashicorp/hcl/v2" +) + +type Config struct { + // Name is a unique identifier for the provider. + Name string + + // Init is called once to initialize the provider from the matching configuration block. + Init func(hcl.Body) error +} + +var providerConfigs = make(map[string]*Config) + +func Init(name string, body hcl.Body) error { + p, ok := providerConfigs[name] + if ok { + return p.Init(body) + } + return fmt.Errorf("provider: no %q provider available", name) +} + +func Register(provider *Config) { + providerConfigs[provider.Name] = provider +} diff --git a/recorder/asciicast.go b/recorder/asciicast.go new file mode 100644 index 0000000..f591fc1 --- /dev/null +++ b/recorder/asciicast.go @@ -0,0 +1,132 @@ +package recorder + +import ( + "encoding/json" + "fmt" + "io" + "strconv" + "sync" + "time" +) + +type asciiCastRecorder struct { + wc io.WriteCloser + mu sync.Mutex + header asciiCastHeader + last time.Time + closed bool +} + +type asciiCastHeader struct { + Version int `json:"version"` + Term asciiCastTerm `json:"term"` + Timestamp int64 `json:"timestamp"` + Title string `json:"title,omitempty"` + Env map[string]string `json:"env,omitempty"` +} + +type asciiCastTerm struct { + Columns int `json:"cols"` + Rows int `json:"rows"` + Type string `json:"type"` +} + +type asciiCastDuration float64 + +func (d asciiCastDuration) MarshalJSON() ([]byte, error) { + return []byte(strconv.FormatFloat(float64(d), 'f', 6, 64)), nil +} + +type asciiCastFrame struct { + Delay float64 + Data []byte +} + +func (f asciiCastFrame) MarshalJSON() ([]byte, error) { + s, _ := json.Marshal(string(f.Data)) + return []byte(fmt.Sprintf(`[%.6f, %s]`, f.Delay, s)), nil +} + +func newAsciiCastRecorder(wc io.WriteCloser, info Info) (*asciiCastRecorder, error) { + now := time.Now() + if err := json.NewEncoder(wc).Encode(asciiCastHeader{ + Version: 3, + Term: asciiCastTerm{ + Columns: info.Columns, + Rows: info.Rows, + Type: info.TerminalType, + }, + Timestamp: now.Unix(), + Title: info.Title, + }); err != nil { + return nil, err + } + if _, err := io.WriteString(wc, "\n"); err != nil { + return nil, err + } + return &asciiCastRecorder{ + wc: wc, + last: now, + }, nil +} + +func (r *asciiCastRecorder) Close() error { + return r.wc.Close() +} + +func (r *asciiCastRecorder) writeFrame(kind rune, p []byte) (int, error) { + r.mu.Lock() + defer r.mu.Unlock() + + if r.closed { + return 0, io.ErrClosedPipe + } + + var ( + now = time.Now() + delay = now.Sub(r.last) + ) + + v, _ := json.Marshal(string(p)) + _, err := io.WriteString(r.wc, "["+strconv.FormatFloat(delay.Seconds(), 'f', 6, 64)+", \""+string(kind)+"\", "+string(v)+"]\n") + + r.last = now + return len(p), err +} + +type asciiCastWriter struct { + r *asciiCastRecorder + kind rune +} + +func (w *asciiCastWriter) Close() error { + return w.r.Close() +} + +func (w *asciiCastWriter) Write(p []byte) (int, error) { + return w.r.writeFrame(w.kind, p) +} + +func (r *asciiCastRecorder) Reads() io.WriteCloser { + return &asciiCastWriter{r, 'o'} +} + +func (r *asciiCastRecorder) Writes() io.WriteCloser { + return &asciiCastWriter{r, 'i'} +} + +func (r *asciiCastRecorder) Resize(columns, rows int) { + var ( + now = time.Now() + delay = now.Sub(r.last) + ) + r.mu.Lock() + _, _ = fmt.Fprintf(r.wc, "[%.6f, \"r\", \"%dx%d\"]\n", delay.Seconds(), columns, rows) + r.last = now + r.mu.Unlock() +} + +var ( + _ Recorder = (*asciiCastRecorder)(nil) + _ Resizer = (*asciiCastRecorder)(nil) +) diff --git a/recorder/recorder.go b/recorder/recorder.go new file mode 100644 index 0000000..3b75552 --- /dev/null +++ b/recorder/recorder.go @@ -0,0 +1,68 @@ +package recorder + +import ( + "errors" + "io" + "os" +) + +type Format int + +const ( + Text Format = iota + TTYRec + ASCIICastv3 +) + +var ( + ErrFormat = errors.New("recorder: unknown format") +) + +type Recorder interface { + Reads() io.WriteCloser + Writes() io.WriteCloser +} + +type Resizer interface { + Resize(columns, rows int) +} + +type Info struct { + Columns int + Rows int + TerminalType string + Title string + Env map[string]string +} + +func New(name string, format Format, info Info) (Recorder, error) { + f, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY, 0o640) + if err != nil { + return nil, err + } + return NewWriter(f, format, info) +} + +func NewWriter(wc io.WriteCloser, format Format, info Info) (Recorder, error) { + switch format { + case Text: + return textRecorder{wc}, nil + + case ASCIICastv3: + return newAsciiCastRecorder(wc, info) + + case TTYRec: + return newTTYRecRecorder(wc), nil + + default: + return nil, ErrFormat + } +} + +type nopCloser struct { + io.Writer +} + +func (nopCloser) Close() error { + return nil +} diff --git a/recorder/text.go b/recorder/text.go new file mode 100644 index 0000000..83fbca3 --- /dev/null +++ b/recorder/text.go @@ -0,0 +1,15 @@ +package recorder + +import "io" + +type textRecorder struct { + io.WriteCloser +} + +func (r textRecorder) Reads() io.WriteCloser { + return r.WriteCloser +} + +func (r textRecorder) Writes() io.WriteCloser { + return r.WriteCloser +} diff --git a/recorder/ttyrec.go b/recorder/ttyrec.go new file mode 100644 index 0000000..61e1b56 --- /dev/null +++ b/recorder/ttyrec.go @@ -0,0 +1,72 @@ +package recorder + +import ( + "encoding/binary" + "io" + "sync" + "syscall" + "time" +) + +type ttyRecordHeader struct { + Sec int32 + USec int32 + Len int32 +} + +func (h *ttyRecordHeader) Now() { + var ( + now = time.Now() + tv = syscall.NsecToTimeval(now.UnixNano()) + ) + h.Sec = int32(tv.Sec) + h.USec = tv.Usec +} + +func (h *ttyRecordHeader) WriteTo(w io.Writer) (int64, error) { + var b [6]byte + binary.LittleEndian.PutUint32(b[0:], uint32(h.Sec)) + binary.LittleEndian.PutUint32(b[2:], uint32(h.USec)) + binary.LittleEndian.PutUint32(b[4:], uint32(h.Len)) + n, err := w.Write(b[:]) + return int64(n), err +} + +type ttyRecRecorder struct { + mu sync.Mutex + wc io.WriteCloser + header *ttyRecordHeader +} + +func newTTYRecRecorder(wc io.WriteCloser) *ttyRecRecorder { + return &ttyRecRecorder{ + wc: wc, + header: new(ttyRecordHeader), + } +} + +func (r *ttyRecRecorder) Close() error { + return r.wc.Close() +} + +func (w *ttyRecRecorder) Write(p []byte) (n int, err error) { + w.mu.Lock() + defer w.mu.Unlock() + + w.header.Now() + if _, err = w.header.WriteTo(w.wc); err != nil { + return + } + if _, err = w.wc.Write(p); err != nil { + return + } + return len(p) + 9, nil +} + +func (r *ttyRecRecorder) Reads() io.WriteCloser { + return r +} + +func (r *ttyRecRecorder) Writes() io.WriteCloser { + return nopCloser{io.Discard} +} diff --git a/ssh/channel.go b/ssh/channel.go new file mode 100644 index 0000000..a1f5a4a --- /dev/null +++ b/ssh/channel.go @@ -0,0 +1,52 @@ +package ssh + +import ( + "errors" + "io" + + "golang.org/x/crypto/ssh" +) + +type DupeChannel struct { + ssh.Channel + + // Reads writes read actions. + Reads io.WriteCloser + + // Writer writes write actions. + Writes io.WriteCloser +} + +func (c DupeChannel) Close() error { + var errs []error + for _, closer := range []io.Closer{ + c.Channel, + c.Reads, + c.Writes, + } { + if closer == nil { + continue + } + if cerr := closer.Close(); cerr != nil { + errs = append(errs, cerr) + } + } + return errors.Join(errs...) +} + +func (c DupeChannel) Read(p []byte) (n int, err error) { + if c.Reads == nil { + return c.Channel.Read(p) + } + return io.TeeReader(c.Channel, c.Reads).Read(p) +} + +func (c DupeChannel) Write(p []byte) (n int, err error) { + if c.Writes == nil { + return c.Channel.Write(p) + } + if n, err = c.Channel.Write(p); n > 0 { + _, _ = c.Writes.Write(p[:]) + } + return +} diff --git a/ssh/client.go b/ssh/client.go new file mode 100644 index 0000000..4216697 --- /dev/null +++ b/ssh/client.go @@ -0,0 +1,28 @@ +package ssh + +import ( + "net" + + "golang.org/x/crypto/ssh" +) + +type Client struct { + netConn net.Conn + client *ssh.Client +} + +type ClientConfig struct { + ssh.ClientConfig +} + +func NewClient(conn net.Conn, config *ClientConfig) (*Client, error) { + sshConn, channels, requests, err := ssh.NewClientConn(conn, conn.RemoteAddr().String(), &config.ClientConfig) + if err != nil { + return nil, err + } + + return &Client{ + netConn: conn, + client: ssh.NewClient(sshConn, channels, requests), + }, nil +} diff --git a/ssh/compat.go b/ssh/compat.go new file mode 100644 index 0000000..d6653d1 --- /dev/null +++ b/ssh/compat.go @@ -0,0 +1,46 @@ +package ssh + +import ( + "golang.org/x/crypto/ssh" +) + +const ( + ChannelTypeDefault = "" + ChannelTypeAgent = "auth-agent@openssh.com" + ChannelTypeDirectTCPIP = "direct-tcpip" + ChannelTypeSession = "session" +) + +const ( + RequestTypeAgent = "auth-agent-req@openssh.com" + RequestTypeEnv = "env" + RequestTypeExec = "exec" + RequestTypePTY = "pty-req" + RequestTypeShell = "shell" + RequestTypeWindowChange = "window-change" +) + +// Type aliases for convenience. +type ( + CertChecker = ssh.CertChecker + Certificate = ssh.Certificate + Conn = ssh.Conn + ConnMetadata = ssh.ConnMetadata + Permissions = ssh.Permissions + PublicKey = ssh.PublicKey + ServerConfig = ssh.ServerConfig + Signer = ssh.Signer +) + +func MarshalAuthorizedKey(in PublicKey) []byte { + return ssh.MarshalAuthorizedKey(in) +} + +func ParseAuthorizedKey(in []byte) (out PublicKey, options []string, err error) { + out, _, options, _, err = ssh.ParseAuthorizedKey(in) + return +} + +func ParsePublicKey(in []byte) (out PublicKey, err error) { + return ssh.ParsePublicKey(in) +} diff --git a/ssh/context.go b/ssh/context.go new file mode 100644 index 0000000..a7e0085 --- /dev/null +++ b/ssh/context.go @@ -0,0 +1,204 @@ +package ssh + +import ( + "encoding/binary" + "encoding/hex" + "io" + "math/rand" + "net" + "time" + + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/logger" +) + +var seed = rand.NewSource(time.Now().UnixNano()) + +type Context interface { + ssh.ConnMetadata + + // ID is the unique identifier. + ID() string + + // Conn is the [ssh.Conn]. + Conn() ssh.Conn + + // NetConn is the underlying [net.Conn]. + NetConn() net.Conn + + // Close the client connection. + Close() error +} + +type sshContext struct { + id uint64 + server *Server + netConn net.Conn + conn *ssh.ServerConn + log logger.Structured +} + +func newSSHContext(server *Server, netConn net.Conn, conn *ssh.ServerConn, log logger.Structured) *sshContext { + ctx := &sshContext{ + id: uint64(seed.Int63()), + server: server, + netConn: netConn, + conn: conn, + } + ctx.log = log.Value("context", ctx.ID()) + return ctx +} + +// User returns the user ID for this connection. +func (ctx *sshContext) User() string { + return ctx.conn.User() +} + +// SessionID returns the session hash, also denoted by H. +func (ctx *sshContext) SessionID() []byte { + return ctx.conn.SessionID() +} + +// ClientVersion returns the client's version string as hashed +// into the session ID. +func (ctx *sshContext) ClientVersion() []byte { + return ctx.conn.ClientVersion() +} + +// ServerVersion returns the server's version string as hashed +// into the session ID. +func (ctx *sshContext) ServerVersion() []byte { + return ctx.conn.ServerVersion() +} + +// RemoteAddr returns the remote address for this connection. +func (ctx *sshContext) RemoteAddr() net.Addr { + return ctx.netConn.RemoteAddr() +} + +// LocalAddr returns the local address for this connection. +func (ctx *sshContext) LocalAddr() net.Addr { + return ctx.netConn.LocalAddr() +} + +func (ctx *sshContext) handleChannels(channels <-chan ssh.NewChannel) (err error) { + for newChan := range channels { + var ( + kind = newChan.ChannelType() + log = ctx.log.Value("channel", kind) + ) + log.Trace("Client requested new channel") + + handler, ok := ctx.server.ChannelHandler[kind] + if !ok { + handler = ctx.server.ChannelHandler[ChannelTypeDefault] + } + + if handler != nil { + var ( + channel ssh.Channel + requests <-chan *ssh.Request + ) + if channel, requests, err = newChan.Accept(); err != nil { + return + } + if err = handler.HandleChannel(ctx, channel, requests, newChan.ExtraData()); err != nil { + return + } + } else if kind == ChannelTypeDirectTCPIP && ctx.server.PortForwardHandler != nil { + if err = ctx.handleDirectTCPIP(newChan); err != nil { + return + } + } else { + ctx.log.Debug("Rejecting unsupported channel type") + if err = newChan.Reject(ssh.Prohibited, ""); err != nil { + return + } + } + } + + // Our client hang up. + return io.EOF +} + +func (ctx *sshContext) handleDirectTCPIP(newChan ssh.NewChannel) (err error) { + var payload struct { + Host string + Port uint32 + OriginAddr string + OriginPort uint32 + } + if err = ssh.Unmarshal(newChan.ExtraData(), &payload); err != nil { + _ = newChan.Reject(ssh.Prohibited, "") + return + } + + var ip net.IP + if ip = net.ParseIP(payload.Host); ip == nil { + // Not an IP + var ips []net.IP + if ips, err = net.LookupIP(payload.Host); err != nil { + _ = newChan.Reject(ssh.ConnectionFailed, err.Error()) + return + } else if len(ips) == 0 { + _ = newChan.Reject(ssh.ConnectionFailed, "") + return + } + ip = ips[0] + } + + var ( + raddr = &net.TCPAddr{ + IP: ip, + Port: int(payload.Port), + } + laddr = &net.TCPAddr{ + IP: net.ParseIP(payload.OriginAddr), + Port: int(payload.OriginPort), + } + ) + if payload.OriginAddr == "" && payload.OriginPort == 0 { + laddr = nil + } + + var conn net.Conn + if conn, err = ctx.server.PortForwardHandler.HandlePortForwardRequest(ctx, raddr, laddr); err != nil { + _ = newChan.Reject(ssh.ConnectionFailed, err.Error()) + return + } + defer func() { _ = conn.Close() }() + + var ( + channel ssh.Channel + requests <-chan *ssh.Request + ) + if channel, requests, err = newChan.Accept(); err != nil { + return + } + defer func() { _ = channel.Close() }() + go ssh.DiscardRequests(requests) + + go io.Copy(channel, conn) + _, err = io.Copy(conn, channel) + + return +} + +func (ctx *sshContext) Conn() ssh.Conn { + return ctx.conn +} + +func (ctx *sshContext) NetConn() net.Conn { + return ctx.netConn +} + +func (ctx *sshContext) Close() error { + return ctx.conn.Close() +} + +func (ctx *sshContext) ID() string { + var b [8]byte + binary.BigEndian.PutUint64(b[:], ctx.id) + return hex.EncodeToString(b[:]) +} diff --git a/ssh/handler.go b/ssh/handler.go new file mode 100644 index 0000000..2fe26b0 --- /dev/null +++ b/ssh/handler.go @@ -0,0 +1,352 @@ +package ssh + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/hex" + "errors" + "fmt" + "io" + "net" + "sort" + "sync" + "sync/atomic" + "time" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/agent" + + "git.maze.io/maze/conduit/internal/netutil" + "git.maze.io/maze/conduit/internal/stringutil" + "git.maze.io/maze/conduit/logger" + "git.maze.io/maze/conduit/ssh/sshutil" +) + +type ConnectHandler interface { + HandleConnect(net.Conn) (net.Conn, error) +} + +type ConnectHandlerFunc func(net.Conn) (net.Conn, error) + +func (f ConnectHandlerFunc) HandleConnect(c net.Conn) (net.Conn, error) { + return f(c) +} + +type AcceptHandler interface { + HandleAccept(net.Conn, *ssh.ServerConfig) (*ssh.ServerConn, <-chan ssh.NewChannel, <-chan *ssh.Request, error) +} + +// MaxConnections limits the maximum number of connections. +func MaxConnections(max int) ConnectHandler { + var connections atomic.Int64 + return ConnectHandlerFunc(func(c net.Conn) (net.Conn, error) { + if max <= 0 { + return nil, nil + } else if connections.Load() >= int64(max) { + return nil, errors.New("server: maximum number of connections reached") + } + + var ( + once sync.Once + cc = &netutil.ConnCloser{ + Conn: c, + Closer: func() error { + once.Do(func() { connections.Add(-1) }) + return c.Close() + }, + } + ) + connections.Add(1) + return cc, nil + }) +} + +type ChannelHandler interface { + HandleChannel(Context, ssh.Channel, <-chan *ssh.Request, []byte) error +} + +type ChannelHandlerFunc func(Context, ssh.Channel, <-chan *ssh.Request, []byte) error + +func (f ChannelHandlerFunc) HandleChannel(ctx Context, channel ssh.Channel, requests <-chan *ssh.Request, extra []byte) error { + return f(ctx, channel, requests, extra) +} + +type debugSessionInfo struct { + start time.Time + duration time.Duration + method string + agent agent.Agent + agentRequest bool + agentChannel ssh.Channel + agentError error + env map[string]string + pty *sshutil.PTYRequest + windowChange *sshutil.WindowChangeRequest + unsupported []string +} + +func newDebugSessionInfo() *debugSessionInfo { + return &debugSessionInfo{ + start: time.Now(), + env: make(map[string]string), + } +} + +// DebugSession is a session channel handler that will print debug information to the client, which +// may aid with troubleshooting SSH connectivity or client issues. +func DebugSession() ChannelHandler { + debugPrivateKey, _ := rsa.GenerateKey(rand.Reader, 1024) + + return ChannelHandlerFunc(func(ctx Context, channel ssh.Channel, requests <-chan *ssh.Request, _ []byte) error { + defer channel.Close() + + var ( + log = ctx.(*sshContext).log + done = make(chan struct{}, 1) + info = newDebugSessionInfo() + conn = ctx.Conn() + err error + ) + + go func() { + var ( + reply bool + reesponse []byte + ) + for request := range requests { + log.Values(logger.Values{ + "request": request.Type, + }).Trace("New session channel request") + switch request.Type { + case RequestTypeAgent: + info.agentRequest = true + agentChannel, agentRequests, err := conn.OpenChannel(ChannelTypeAgent, nil) + if err != nil { + info.agentError = err + } else { + go ssh.DiscardRequests(agentRequests) + info.agent = agent.NewClient(agentChannel) + info.agentChannel = agentChannel + } + reply = true + + case RequestTypeEnv: + var payload *sshutil.EnvRequest + if payload, err = sshutil.ParseEnvRequest(request.Payload); err != nil { + log.Err(err).Debug("Corrupted env request payload, discarding") + } else { + log.Values(logger.Values{ + "key": payload.Key, + "value": payload.Value, + }).Trace("Client requested env variable") + info.env[payload.Key] = payload.Value + } + reply = true + + case RequestTypePTY: + if info.pty, err = sshutil.ParsePTYRequest(request.Payload); err != nil { + log.Err(err).Debug("Corrupted pty request payload, discarding") + } else { + log.Values(logger.Values{ + "term": info.pty.Term, + "size": fmt.Sprintf("%dx%d", info.pty.Columns, info.pty.Rows), + }).Trace("Client requested PTY") + } + reply = true + + case RequestTypeExec, RequestTypeShell: + info.method = request.Type + select { + case <-done: + default: + close(done) + } + //return + + case RequestTypeWindowChange: + if info.windowChange, err = sshutil.ParseWindowChangeRequest(request.Payload); err != nil { + log.Err(err).Debug("Corrupted window change request payload, discarding") + } else { + log.Values(logger.Values{ + "size": fmt.Sprintf("%dx%d", info.windowChange.Columns, info.windowChange.Rows), + }).Trace("Client requested window change") + } + reply = true + + default: + log.Values(logger.Values{ + "request": request.Type, + "payload": hex.EncodeToString(request.Payload), + }).Trace("Client requested something we don't understand, ignored") + info.unsupported = append(info.unsupported, request.Type) + } + + if request.WantReply { + if err := request.Reply(reply, reesponse); err != nil { + log.Err(err).Debug("Error sending session channel request reply: terminating") + _ = channel.Close() + select { + case <-done: + default: + close(done) + } + return + } + } + } + select { + case <-done: + default: + close(done) + } + }() + + select { + case <-done: + case <-time.After(10 * time.Second): + io.WriteString(channel, "Timeout waiting for your client to send either an exec or shell request\r\n") + } + + // Any requests that follow are ignored from hereon forward. + go ssh.DiscardRequests(requests) + + // Attempt to request agent forwarding + if info.agent == nil && !info.agentRequest { + agentChannel, agentRequests, err := conn.OpenChannel(ChannelTypeAgent, nil) + if err != nil { + info.agentError = err + } else { + go ssh.DiscardRequests(agentRequests) + info.agent = agent.NewClient(agentChannel) + info.agentChannel = agentChannel + } + } + + info.duration = time.Since(info.start) + printSessionInfo(ctx, channel, info, debugPrivateKey) + + return nil + }) +} + +func printSessionInfo(ctx Context, channel ssh.Channel, info *debugSessionInfo, key crypto.PrivateKey) { + if info.agentChannel != nil { + defer info.agentChannel.Close() + } + + conn := ctx.Conn().(*ssh.ServerConn) + + fmt.Fprintf(channel, "It took your client %s to request %s\r\n\r\n", info.duration, info.method) + fmt.Fprintf(channel, "SSH connection information:\r\n") + fmt.Fprintf(channel, " Server:\r\n") + fmt.Fprintf(channel, " Version: \x1b[1m%s\x1b[0m\r\n", conn.ServerVersion()) + fmt.Fprintf(channel, " Client:\r\n") + fmt.Fprintf(channel, " Version: \x1b[1m%s\x1b[0m\r\n", conn.ClientVersion()) + fmt.Fprintf(channel, " Username: \x1b[1m%s\x1b[0m\r\n", conn.User()) + + if conn.Permissions != nil { + if conn.Permissions.CriticalOptions != nil { + fmt.Fprint(channel, " Options:\r\n") + for k := range stringutil.MapKeys(conn.Permissions.CriticalOptions) { + fmt.Fprintf(channel, " ✅ %s=%s\r\n", k, conn.Permissions.CriticalOptions[k]) + } + } + if conn.Permissions.Extensions != nil { + fmt.Fprint(channel, " Extensions:\r\n") + for k := range stringutil.MapKeys(conn.Permissions.Extensions) { + fmt.Fprintf(channel, " ✅ %s=%s\r\n", k, conn.Permissions.Extensions[k]) + } + } + } + + fmt.Fprintf(channel, " Environment: (%d variables sent)\r\n", len(info.env)) + for k := range stringutil.MapKeys(info.env) { + fmt.Fprintf(channel, " ✅ %s=%s\r\n", k, info.env[k]) + } + + if info.pty == nil { + fmt.Fprint(channel, " PTY: not requested\r\n") + } else { + fmt.Fprintf(channel, " PTY: %d cols, %d rows, %dx%d, %q\r\n", + info.pty.Columns, info.pty.Rows, info.pty.Width, info.pty.Height, info.pty.Term) + } + + if info.windowChange == nil { + fmt.Fprint(channel, " Window: not requested\r\n") + } else { + fmt.Fprintf(channel, " Window: %d cols, %d rows, %dx%d", + info.windowChange.Columns, info.windowChange.Rows, + info.windowChange.Width, info.windowChange.Height) + } + + if len(info.unsupported) > 0 { + fmt.Fprintf(channel, " Requests: (%d unsupported):\r\n", len(info.unsupported)) + sort.Strings(info.unsupported) + for _, v := range info.unsupported { + fmt.Fprintf(channel, " ❌ %s\r\n", v) + } + } else { + fmt.Fprint(channel, " Requests: no unsupported requests\r\n") + } + + if info.agentRequest { + if info.agentError != nil { + fmt.Fprint(channel, " Agent: requested but unavailable:\r\n") + fmt.Fprintf(channel, " ❌ %v\r\n", info.agentError) + } + } else { + if info.agentError == nil { + fmt.Fprint(channel, " Agent: not requested by client, but accepted, upgrade your client!\r\n") + } else { + fmt.Fprint(channel, " Agent: not requested by client, and client refused our attempt (good!):\r\n") + fmt.Fprintf(channel, " ❌ %v\r\n", info.agentError) + } + } + + if info.agent != nil { + fmt.Fprint(channel, " Agent: available, checking keys:\r\n") + keys, err := info.agent.List() + if err != nil { + fmt.Fprintf(channel, " ❌ %v\r\n", err) + } else { + for _, key := range keys { + blob := make([]byte, 8) + rand.Reader.Read(blob) + if _, err := info.agent.Sign(key, blob); err != nil { + fmt.Fprint(channel, " ❌ ") + } else { + fmt.Fprint(channel, " ✅ ") + } + fmt.Fprintf(channel, "%-4d %s %s (%s)\r\n", + sshutil.KeyBits(key), sshutil.KeyFingerprint(key), + key.Comment, sshutil.KeyType(key)) + } + } + + fmt.Fprint(channel, " Agent: available, checking add/remove:\r\n") + if err = info.agent.Add(agent.AddedKey{ + PrivateKey: key, + LifetimeSecs: 5, + Comment: "Test by conduit", + }); err != nil { + fmt.Fprintf(channel, " ❌ Add: %v\r\n", err) + } else { + fmt.Fprint(channel, " ✅ Add\r\n") + } + pk, _ := ssh.NewPublicKey(key.(*rsa.PrivateKey).Public()) + if err = info.agent.Remove(pk); err != nil { + fmt.Fprintf(channel, " ❌ Remove: %v\r\n", err) + } else { + fmt.Fprint(channel, " ✅ Remove\r\n") + } + } +} + +func StaticSession(message string) ChannelHandler { + return ChannelHandlerFunc(func(ctx Context, channel ssh.Channel, requests <-chan *ssh.Request, _ []byte) error { + go ssh.DiscardRequests(requests) + io.WriteString(channel, message+"\r\n") + return channel.Close() + }) +} diff --git a/ssh/handler_tunnel.go b/ssh/handler_tunnel.go new file mode 100644 index 0000000..23ffb24 --- /dev/null +++ b/ssh/handler_tunnel.go @@ -0,0 +1,47 @@ +package ssh + +import ( + "context" + "errors" + "net" + + "git.maze.io/maze/conduit/logger" + "golang.org/x/crypto/ssh" +) + +type Dialer interface { + DialContext(ctx context.Context, network, address string) (net.Conn, error) +} + +func ForwardTunnel(dialer Dialer) ChannelHandler { + if dialer == nil { + dialer = new(net.Dialer) + } + return ChannelHandlerFunc(func(ctx Context, channel ssh.Channel, requests <-chan *ssh.Request, _ []byte) error { + return errors.New("byez!") + }) +} + +type PortForwardRequestHandler interface { + HandlePortForwardRequest(ctx Context, raddr, laddr net.Addr) (net.Conn, error) +} + +type PortForwardRequestHandlerFunc func(Context, net.Addr, net.Addr) (net.Conn, error) + +func (f PortForwardRequestHandlerFunc) HandlePortForwardRequest(ctx Context, raddr, laddr net.Addr) (net.Conn, error) { + return f(ctx, raddr, laddr) +} + +func PortForwardDialer(dialer Dialer) PortForwardRequestHandler { + if dialer == nil { + dialer = new(net.Dialer) + } + return PortForwardRequestHandlerFunc(func(ctx Context, raddr, laddr net.Addr) (net.Conn, error) { + log := ctx.(*sshContext).log.Values(logger.Values{ + "laddr": laddr.String(), + "raddr": raddr.String(), + }) + log.Debug("Dialing port forwarding request") + return dialer.DialContext(context.Background(), raddr.Network(), raddr.String()) + }) +} diff --git a/ssh/keys.go b/ssh/keys.go new file mode 100644 index 0000000..b7e47fc --- /dev/null +++ b/ssh/keys.go @@ -0,0 +1,37 @@ +package ssh + +import ( + "os" + "strings" + + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/logger" +) + +func LoadPrivateKey(name string) (ssh.Signer, error) { + if strings.Contains(name, "-----BEGIN") && strings.Contains(name, "PRIVATE KEY-----") { + logger.StandardLog.Debug("Loading private key from string") + return ssh.ParsePrivateKey([]byte(name)) + } + logger.StandardLog.Value("path", name).Debug("Loading private key") + b, err := os.ReadFile(name) + if err != nil { + return nil, err + } + return ssh.ParsePrivateKey(b) +} + +func LoadPrivateKeyWithPassphrase(name string, passphrase []byte) (ssh.Signer, error) { + if strings.Contains(name, "-----BEGIN") && strings.Contains(name, "PRIVATE KEY-----") { + logger.StandardLog.Debug("Loading private key from string (with passphrase)") + return ssh.ParsePrivateKeyWithPassphrase([]byte(name), passphrase) + } + + logger.StandardLog.Value("path", name).Debug("Loading private key (with passphrase)") + b, err := os.ReadFile(name) + if err != nil { + return nil, err + } + return ssh.ParsePrivateKeyWithPassphrase(b, passphrase) +} diff --git a/ssh/server.go b/ssh/server.go new file mode 100644 index 0000000..7758144 --- /dev/null +++ b/ssh/server.go @@ -0,0 +1,115 @@ +package ssh + +import ( + "net" + + "golang.org/x/crypto/ssh" + + "git.maze.io/maze/conduit/auth" + "git.maze.io/maze/conduit/internal/netutil" + "git.maze.io/maze/conduit/logger" +) + +type Server struct { + // ConnectHandler gets called before accepting new connections. + ConnectHandler []ConnectHandler + + // AcceptHandler accepts new SSH connections, it takes care of authenticating, etc. + AcceptHandler AcceptHandler + + // Handler per channel type. + ChannelHandler map[string]ChannelHandler + + // PortForwardHandler + PortForwardHandler PortForwardRequestHandler + + // FIPSMode enables FIPS 140-2 compatible ciphers, key exchanges, etc. + FIPSMode bool // TODO(maze): implement + + // Logger for our server. + Logger logger.Structured + + // serverConfig is our SSH server configuration. + serverConfig *ssh.ServerConfig +} + +func NewServer(keys []ssh.Signer) *Server { + config := new(ssh.ServerConfig) + config.SetDefaults() + config.ServerVersion = "SSH-2.0-conduit" + + for _, key := range keys { + config.AddHostKey(key) + } + + return &Server{ + ChannelHandler: make(map[string]ChannelHandler), + Logger: logger.StandardLog, + serverConfig: config, + } +} + +func (s *Server) Serve(l net.Listener) error { + for { + c, err := l.Accept() + if err != nil { + return err + } + go s.handle(c) + } +} + +func (s *Server) handle(c net.Conn) { + log := s.Logger.Values(logger.Values{ + "client": c.RemoteAddr().String(), + "server": c.LocalAddr().String(), + }) + log.Debug("New client connection") + + defer func() { + if err := c.Close(); err != nil && !netutil.IsClosing(err) { + log = log.Err(err) + } + log.Debug("Closing client connection") + }() + + for _, h := range s.ConnectHandler { + n, err := h.HandleConnect(c) + if err != nil { + log.Err(err).Warn("Error from connect handler, closing client connection") + return + } else if n != nil { + log.Debugf("Replacing client connection with %T", n) + c = n + } + } + + // Configure our SSH server. + handler := s.AcceptHandler + if handler == nil { + log.Warn("No accept handler configured, using NO AUTHENTICATION") + handler = auth.None{} + } + + // We made it, now let's talk some SSH. + sshConn, channels, requests, err := handler.HandleAccept(c, s.serverConfig) + if err != nil { + log.Err(err).Warn("Error establishing SSH session with client") + return + } + go ssh.DiscardRequests(requests) + + log = log.Value("user", sshConn.User()) + ctx := newSSHContext(s, c, sshConn, log) + log = log.Value("context", ctx.ID()) + log.Value("version", string(sshConn.ClientVersion())).Info("New SSH client") + + if err = ctx.handleChannels(channels); err != nil { + if netutil.IsClosing(err) { + log.Err(err).Debug("Client handler terminated") + } else { + log.Err(err).Warn("Error handling channel requests") + } + return + } +} diff --git a/ssh/sshutil/key.go b/ssh/sshutil/key.go new file mode 100644 index 0000000..d4ff800 --- /dev/null +++ b/ssh/sshutil/key.go @@ -0,0 +1,62 @@ +package sshutil + +import ( + "crypto/sha256" + "encoding/base64" + "math/big" + + "golang.org/x/crypto/ssh" +) + +func KeyBits(key ssh.PublicKey) int { + if key == nil { + return 0 + } + switch key.Type() { + case ssh.KeyAlgoECDSA256: + return 256 + case ssh.KeyAlgoSKECDSA256: + return 256 + case ssh.KeyAlgoECDSA384: + return 384 + case ssh.KeyAlgoECDSA521: + return 521 + case ssh.KeyAlgoED25519: + return 256 + case ssh.KeyAlgoSKED25519: + return 256 + case ssh.KeyAlgoRSA: + var w struct { + Name string + E *big.Int + N *big.Int + Rest []byte `ssh:"rest"` + } + _ = ssh.Unmarshal(key.Marshal(), &w) + return w.N.BitLen() + default: + return 0 + } +} + +func KeyType(key ssh.PublicKey) string { + if key == nil { + return "" + } + switch key.Type() { + case ssh.KeyAlgoECDSA256, ssh.KeyAlgoSKECDSA256, ssh.KeyAlgoECDSA384, ssh.KeyAlgoECDSA521: + return "ECDSA" + case ssh.KeyAlgoED25519, ssh.KeyAlgoSKED25519: + return "ED25519" + case ssh.KeyAlgoRSA: + return "RSA" + default: + return key.Type() + } +} + +func KeyFingerprint(key ssh.PublicKey) string { + h := sha256.New() + h.Write(key.Marshal()) + return "SHA256:" + base64.RawStdEncoding.EncodeToString(h.Sum(nil)) +} diff --git a/ssh/sshutil/request.go b/ssh/sshutil/request.go new file mode 100644 index 0000000..58d7c47 --- /dev/null +++ b/ssh/sshutil/request.go @@ -0,0 +1,47 @@ +package sshutil + +import "golang.org/x/crypto/ssh" + +type EnvRequest struct { + Key, Value string +} + +func ParseEnvRequest(data []byte) (*EnvRequest, error) { + r := new(EnvRequest) + if err := ssh.Unmarshal(data, r); err != nil { + return nil, err + } + return r, nil +} + +type PTYRequest struct { + Term string + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 + ModeList []byte +} + +func ParsePTYRequest(data []byte) (*PTYRequest, error) { + r := new(PTYRequest) + if err := ssh.Unmarshal(data, r); err != nil { + return nil, err + } + return r, nil +} + +type WindowChangeRequest struct { + Columns uint32 + Rows uint32 + Width uint32 + Height uint32 +} + +func ParseWindowChangeRequest(data []byte) (*WindowChangeRequest, error) { + r := new(WindowChangeRequest) + if err := ssh.Unmarshal(data, r); err != nil { + return nil, err + } + return r, nil +} diff --git a/storage/codec/binary.go b/storage/codec/binary.go new file mode 100644 index 0000000..e7b182c --- /dev/null +++ b/storage/codec/binary.go @@ -0,0 +1,248 @@ +package codec + +import ( + "bytes" + "encoding/binary" + "fmt" + "io" + "math" + + "golang.org/x/crypto/ssh" +) + +func init() { + Register("binray", func() Codec { return binaryCodec{order: binary.NativeEndian} }) + Register("be", func() Codec { return binaryCodec{order: binary.BigEndian} }) + Register("le", func() Codec { return binaryCodec{order: binary.LittleEndian} }) + Register("ssh", func() Codec { return sshCodec{} }) +} + +type binaryCodec struct { + order binary.ByteOrder + scratch [16]byte +} + +func (binaryCodec) Type() string { return "binary" } + +func (codec binaryCodec) Encode(value any) ([]byte, error) { + switch value := value.(type) { + case bool: + if value { + return []byte{1}, nil + } + return []byte{0}, nil + case *bool: + if *value { + return []byte{1}, nil + } + return []byte{0}, nil + case int: + codec.order.PutUint64(codec.scratch[:], uint64(value)) + return codec.scratch[:8], nil + case *int: + return codec.Encode(*value) + case int8: + return []byte{uint8(value)}, nil + case *int8: + return []byte{uint8(*value)}, nil + case int16: + codec.order.PutUint16(codec.scratch[:], uint16(value)) + return codec.scratch[:2], nil + case *int16: + return codec.Encode(*value) + case int32: + codec.order.PutUint32(codec.scratch[:], uint32(value)) + return codec.scratch[:4], nil + case *int32: + return codec.Encode(*value) + case int64: + codec.order.PutUint64(codec.scratch[:], uint64(value)) + return codec.scratch[:8], nil + case *int64: + return codec.Encode(*value) + case uint: + codec.order.PutUint64(codec.scratch[:], uint64(value)) + return codec.scratch[:8], nil + case *uint: + return codec.Encode(*value) + case uint8: + return []byte{value}, nil + case *uint8: + return []byte{*value}, nil + case uint16: + codec.order.PutUint16(codec.scratch[:], value) + return codec.scratch[:2], nil + case *uint16: + return codec.Encode(*value) + case uint32: + codec.order.PutUint32(codec.scratch[:], value) + return codec.scratch[:4], nil + case *uint32: + return codec.Encode(*value) + case uint64: + codec.order.PutUint64(codec.scratch[:], value) + return codec.scratch[:8], nil + case *uint64: + return codec.Encode(*value) + case float32: + codec.order.PutUint32(codec.scratch[:], math.Float32bits(value)) + return codec.scratch[:4], nil + case *float32: + codec.order.PutUint32(codec.scratch[:], math.Float32bits(*value)) + return codec.scratch[:4], nil + case float64: + codec.order.PutUint64(codec.scratch[:], math.Float64bits(value)) + return codec.scratch[:8], nil + case *float64: + codec.order.PutUint64(codec.scratch[:], math.Float64bits(*value)) + return codec.scratch[:8], nil + case complex64: + codec.order.PutUint32(codec.scratch[0:], math.Float32bits(real(value))) + codec.order.PutUint32(codec.scratch[4:], math.Float32bits(imag(value))) + return codec.scratch[:8], nil + case *complex64: + codec.order.PutUint32(codec.scratch[0:], math.Float32bits(real(*value))) + codec.order.PutUint32(codec.scratch[4:], math.Float32bits(imag(*value))) + return codec.scratch[:8], nil + case complex128: + codec.order.PutUint64(codec.scratch[0:], math.Float64bits(real(value))) + codec.order.PutUint64(codec.scratch[4:], math.Float64bits(imag(value))) + return codec.scratch[:16], nil + case *complex128: + codec.order.PutUint64(codec.scratch[0:], math.Float64bits(real(*value))) + codec.order.PutUint64(codec.scratch[4:], math.Float64bits(imag(*value))) + return codec.scratch[:16], nil + case string: + n := binary.PutUvarint(codec.scratch[:], uint64(len(value))) + return append(codec.scratch[:n], []byte(value)...), nil + case *string: + n := binary.PutUvarint(codec.scratch[:], uint64(len(*value))) + return append(codec.scratch[:n], []byte(*value)...), nil + case []byte: + n := binary.PutUvarint(codec.scratch[:], uint64(len(value))) + return append(codec.scratch[:n], value...), nil + case *[]byte: + n := binary.PutUvarint(codec.scratch[:], uint64(len(*value))) + return append(codec.scratch[:n], *value...), nil + default: + return nil, fmt.Errorf("codec: don't know how to binary encode %T", value) + } +} + +func (codec binaryCodec) Decode(data []byte, value any) error { + switch value := value.(type) { + case *bool: + if len(data) < 1 { + return io.ErrUnexpectedEOF + } + *value = data[0] != 0 + + case *int: + if len(data) < 8 { + return io.ErrUnexpectedEOF + } + *value = int(codec.order.Uint64(data)) + case *int8: + if len(data) < 1 { + return io.ErrUnexpectedEOF + } + *value = int8(data[0]) + case *int16: + if len(data) < 2 { + return io.ErrUnexpectedEOF + } + *value = int16(codec.order.Uint16(data)) + case *int32: + if len(data) < 4 { + return io.ErrUnexpectedEOF + } + *value = int32(codec.order.Uint32(data)) + case *int64: + if len(data) < 8 { + return io.ErrUnexpectedEOF + } + *value = int64(codec.order.Uint64(data)) + case *uint: + if len(data) < 8 { + return io.ErrUnexpectedEOF + } + *value = uint(codec.order.Uint64(data)) + case *uint8: + if len(data) < 1 { + return io.ErrUnexpectedEOF + } + *value = data[0] + case *uint16: + if len(data) < 1 { + return io.ErrUnexpectedEOF + } + *value = codec.order.Uint16(data) + case *uint32: + if len(data) < 4 { + return io.ErrUnexpectedEOF + } + *value = codec.order.Uint32(data) + case *uint64: + if len(data) < 8 { + return io.ErrUnexpectedEOF + } + *value = codec.order.Uint64(data) + case *float32: + if len(data) < 4 { + return io.ErrUnexpectedEOF + } + *value = math.Float32frombits(codec.order.Uint32(data)) + case *float64: + if len(data) < 8 { + return io.ErrUnexpectedEOF + } + *value = math.Float64frombits(codec.order.Uint64(data)) + case *complex64: + if len(data) < 8 { + return io.ErrUnexpectedEOF + } + *value = complex( + math.Float32frombits(codec.order.Uint32(data[0:])), + math.Float32frombits(codec.order.Uint32(data[4:])), + ) + case *complex128: + if len(data) < 16 { + return io.ErrUnexpectedEOF + } + *value = complex( + math.Float64frombits(codec.order.Uint64(data[0:])), + math.Float64frombits(codec.order.Uint64(data[8:])), + ) + case *string: + r := bytes.NewReader(data) + n, err := binary.ReadUvarint(r) + if err != nil { + return err + } + if uint64(r.Len()) < n { + return io.ErrUnexpectedEOF + } + *value = string(data[len(data)-r.Len():]) + + case *[]byte: + r := bytes.NewReader(data) + n, err := binary.ReadUvarint(r) + if err != nil { + return err + } + if uint64(r.Len()) < n { + return io.ErrUnexpectedEOF + } + copy(*value, data[len(data)-r.Len():]) + + default: + return fmt.Errorf("codec: don't know how to binary decode %T", value) + } + return nil +} + +type sshCodec struct{} + +func (sshCodec) Type() string { return "ssh" } +func (sshCodec) Encode(value any) ([]byte, error) { return ssh.Marshal(value), nil } +func (sshCodec) Decode(data []byte, value any) error { return ssh.Unmarshal(data, value) } diff --git a/storage/codec/codec.go b/storage/codec/codec.go new file mode 100644 index 0000000..c167359 --- /dev/null +++ b/storage/codec/codec.go @@ -0,0 +1,66 @@ +package codec + +import ( + "fmt" + "io" +) + +type Codec interface { + Type() string + Encode(any) ([]byte, error) + Decode([]byte, any) error +} + +type Stream interface { + Encode(any) error + Decode(any) error +} + +var ( + codecs = make(map[string]func() Codec) + streams = make(map[string]func(io.Reader, io.Writer) Stream) +) + +func Register(name string, create func() Codec) { + if _, dupe := codecs[name]; dupe { + panic(fmt.Sprintf("codec: duplicate codec %q registered", name)) + } + codecs[name] = create +} + +func New(name string) (Codec, error) { + if f, ok := codecs[name]; ok { + return f(), nil + } + return nil, fmt.Errorf("codec: no %q codec registered", name) +} + +func Must(name string) Codec { + c, err := New(name) + if err != nil { + panic(err) + } + return c +} + +func RegisterStream(name string, create func(io.Reader, io.Writer) Stream) { + if _, dupe := streams[name]; dupe { + panic(fmt.Sprintf("codec: duplicate stream %q registered", name)) + } + streams[name] = create +} + +func NewStream(name string, r io.Reader, w io.Writer) (Stream, error) { + if f, ok := streams[name]; ok { + return f(r, w), nil + } + return nil, fmt.Errorf("codec: no %q stream registered", name) +} + +func MustStream(name string, r io.Reader, w io.Writer) Stream { + s, err := NewStream(name, r, w) + if err != nil { + panic(err) + } + return s +} diff --git a/storage/codec/default.go b/storage/codec/default.go new file mode 100644 index 0000000..4445ee2 --- /dev/null +++ b/storage/codec/default.go @@ -0,0 +1,77 @@ +package codec + +import ( + "encoding/json" + "io" + + "github.com/goccy/go-yaml" +) + +func init() { + Register("json", func() Codec { return jsonCodec{} }) + Register("yaml", func() Codec { return yamlCodec{} }) +} + +type jsonCodec struct{} + +func (jsonCodec) Type() string { return "json" } +func (jsonCodec) Encode(value any) ([]byte, error) { return json.Marshal(value) } +func (jsonCodec) Decode(data []byte, value any) error { return json.Unmarshal(data, value) } + +type jsonStream struct { + decoder *json.Decoder + encoder *json.Encoder +} + +type JSONOption func(*json.Encoder) + +func NewJSONStream(r io.Reader, w io.Writer, options ...JSONOption) Stream { + s := &jsonStream{ + decoder: json.NewDecoder(r), + encoder: json.NewEncoder(w), + } + for _, option := range options { + option(s.encoder) + } + return s +} + +func JSONIndent(prefix, indent string) JSONOption { + return func(e *json.Encoder) { + e.SetIndent(prefix, indent) + } +} + +func JSONEscapeHTML(on bool) JSONOption { + return func(e *json.Encoder) { + e.SetEscapeHTML(on) + } +} + +func (s jsonStream) Encode(value any) error { return s.encoder.Encode(value) } +func (s jsonStream) Decode(value any) error { return s.decoder.Decode(value) } + +type yamlCodec struct{} + +func (yamlCodec) Type() string { return "yaml" } +func (yamlCodec) Encode(value any) ([]byte, error) { return yaml.Marshal(value) } +func (yamlCodec) Decode(data []byte, value any) error { return yaml.Unmarshal(data, value) } + +type yamlStream struct { + decoder *yaml.Decoder + encoder *yaml.Encoder +} + +func NewYaMLStream(r io.Reader, w io.Writer, options ...yaml.EncodeOption) Stream { + s := &yamlStream{ + decoder: yaml.NewDecoder(r), + encoder: yaml.NewEncoder(w), + } + for _, option := range options { + option(s.encoder) + } + return s +} + +func (s yamlStream) Encode(value any) error { return s.encoder.Encode(value) } +func (s yamlStream) Decode(value any) error { return s.decoder.Decode(value) } diff --git a/storage/io.go b/storage/io.go new file mode 100644 index 0000000..4201802 --- /dev/null +++ b/storage/io.go @@ -0,0 +1,225 @@ +package storage + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "sync" + "syscall" +) + +type ValueError struct { + Value any +} + +func (err ValueError) Error() string { + return fmt.Sprintf("kv: can't store value of type %T", err.Value) +} + +type readSeekerKV struct { + mu sync.Mutex + r io.ReadSeeker + c io.Closer + sep rune +} + +func (kv *readSeekerKV) scan(key string) (data string, ok bool, err error) { + kv.mu.Lock() + + if _, err = kv.r.Seek(0, io.SeekStart); err != nil { + return + } + + s := bufio.NewScanner(kv.r) + for s.Scan() { + line := s.Text() + if i := strings.IndexRune(line, kv.sep); i > -1 && line[:i] == key { + kv.mu.Unlock() + return line[i+1:], true, nil + } + } + err = s.Err() + kv.mu.Unlock() + return +} + +func (kv *readSeekerKV) Has(key string) bool { + _, ok, err := kv.scan(key) + return ok && err == nil +} + +func (kv *readSeekerKV) Get(key string) (value any, ok bool) { + value, ok, _ = kv.scan(key) + return +} + +func (kv *readSeekerKV) Close() error { + if kv.c == nil { + return nil + } + return kv.c.Close() +} + +func OpenKV(name string, sep rune) (KV, error) { + f, err := os.Open(name) + if err != nil { + return nil, err + } + return &readSeekerKV{ + r: f, + sep: sep, + }, nil +} + +type readSeekWriterKV struct { + readSeekerKV + w io.WriteSeeker +} + +type truncater interface { + Truncate(int64) error +} + +func (kv *readSeekWriterKV) Set(key string, value any) error { + var line string + switch value := value.(type) { + case string: + line = key + string(kv.sep) + value + "\n" + case []string: + line = key + string(kv.sep) + strings.Join(value, string(kv.sep)) + "\n" + default: + return ValueError{Value: value} + } + + kv.mu.Lock() + defer kv.mu.Unlock() + + if _, err := kv.r.Seek(0, io.SeekStart); err != nil { + return err + } + + var ( + lines []string + found bool + scanner = bufio.NewScanner(kv.r) + ) + for scanner.Scan() { + text := scanner.Text() + if i := strings.IndexRune(text, kv.sep); i > -1 { + if found = text[:i] == key; found { + lines = append(lines, line) + } else { + lines = append(lines, text) + } + } else { + lines = append(lines, line) + } + } + if !found { + lines = append(lines, line) + } + + // Writing strategy: if it's a file, write to a new file and move it over. + if f, ok := kv.w.(*os.File); ok { + return kv.replaceFile(f, lines) + } + + if t, ok := kv.w.(truncater); ok { + if err := t.Truncate(0); err != nil { + return err + } + } + + if _, err := kv.w.Seek(0, io.SeekStart); err != nil { + return err + } + + for _, line := range lines { + if _, err := io.WriteString(kv.w, line+"\n"); err != nil { + return err + } + } + + return nil +} + +func (kv *readSeekWriterKV) replaceFile(f *os.File, lines []string) (err error) { + var ( + prev = f.Name() + info os.FileInfo + ) + + if info, err = f.Stat(); err != nil { + return + } + + var ( + name = "." + filepath.Base(f.Name()) + ".*" + n *os.File + ) + if n, err = os.CreateTemp(filepath.Dir(f.Name()), name); err != nil { + return + } + name = n.Name() + + // Replicate original file mode + if err = os.Chmod(name, info.Mode()); err != nil { + _ = n.Close() + _ = os.Remove(name) + return + } + + // Replicate original file ownership + if stat, ok := info.Sys().(*syscall.Stat_t); ok { + // This may fail if we aren't allowed, ignore it. + _ = os.Chown(name, int(stat.Uid), int(stat.Gid)) + } + + // Write lines to tempfile. + for _, line := range lines { + if _, err = io.WriteString(n, line+"\n"); err != nil { + _ = n.Close() + _ = os.Remove(name) + return + } + } + + if err = n.Close(); err != nil { + _ = os.Remove(name) + return + } + + // Close original file and replace it. + _ = f.Close() + + if err = os.Rename(name, prev); err != nil { + return + } + + // Reopen file and replace our readers/writers/closers + if f, err = os.OpenFile(prev, os.O_APPEND|os.O_RDWR, info.Mode()|os.ModePerm); err != nil { + return + } + kv.r = f + kv.w = f + kv.c = f + return +} + +func OpenWritableKV(name string, sep rune, perm os.FileMode) (WritableKV, error) { + f, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, perm) + if err != nil { + return nil, err + } + return &readSeekWriterKV{ + readSeekerKV: readSeekerKV{ + r: f, + c: f, + sep: sep, + }, + w: f, + }, nil +} diff --git a/storage/io_test.go b/storage/io_test.go new file mode 100644 index 0000000..fc1b659 --- /dev/null +++ b/storage/io_test.go @@ -0,0 +1,124 @@ +package storage + +import ( + "errors" + "os" + "path/filepath" + "reflect" + "testing" +) + +func TestKV(t *testing.T) { + kv, err := OpenKV(filepath.Join("testdata", "kv"), ':') + if err != nil { + t.Skip(err) + return + } + + tests := []struct { + Key string + Value any + OK bool + }{ + {"test", "data", true}, + {"empty", "", true}, + {"", "emptykey", true}, + {"nonexistant", nil, false}, + {"ignored line because not relevant", nil, false}, + } + for _, test := range tests { + t.Run(test.Key, func(it *testing.T) { + value, ok := kv.Get(test.Key) + if ok != test.OK { + t.Errorf("expected ok %t, got %t", test.OK, ok) + } + if ok && !reflect.DeepEqual(value, test.Value) { + t.Errorf("expected value %v, got %v", test.Value, value) + } + }) + } +} + +func TestWritableKV(t *testing.T) { + orgfile := filepath.Join("testdata", "kv") + b, err := os.ReadFile(orgfile) + if err != nil { + t.Skip(err) + } + + w, err := os.CreateTemp(os.TempDir(), "kv.*") + if err != nil { + t.Skip(err) + return + } + testfile := w.Name() + t.Logf("using a copy of %s as %s", orgfile, testfile) + defer os.Remove(testfile) + + if _, err = w.Write(b); err != nil { + return + } + if err = w.Close(); err != nil { + return + } + + kv, err := OpenWritableKV(testfile, ':', 0640) + if err != nil { + t.Skip(err) + return + } + defer kv.Close() + + t.Run("write", func(t *testing.T) { + writeTests := []struct { + Key string + Value any + Error error + }{ + {"test", "newdata", nil}, + {"strings", []string{"test", "data"}, nil}, + {"int", 42, ValueError{Value: 42}}, + } + for _, test := range writeTests { + t.Run(test.Key, func(it *testing.T) { + err := kv.Set(test.Key, test.Value) + if err != nil { + if test.Error == nil { + it.Errorf("unepxected error %q (%T)", err, err) + } else if !errors.Is(err, test.Error) { + it.Errorf("expected error %q, but got %q (%T)", test.Error, err, err) + } + return + } else if err == nil && test.Error != nil { + it.Errorf("expected error %q, but got nil", err) + } + }) + } + }) + + t.Run("read", func(t *testing.T) { + readTests := []struct { + Key string + Value any + OK bool + }{ + {"test", "newdata", true}, + {"empty", "", true}, + {"", "emptykey", true}, + {"strings", []string{"test", "data"}, true}, + {"nonexistant", nil, false}, + {"ignored line because not relevant", nil, false}, + } + for _, test := range readTests { + t.Run(test.Key, func(it *testing.T) { + value, ok := kv.Get(test.Key) + if ok != test.OK { + t.Errorf("expected ok %t, got %t", test.OK, ok) + } + if ok && !reflect.DeepEqual(value, test.Value) { + t.Errorf("expected value %v, got %v", test.Value, value) + } + }) + } + }) +} diff --git a/storage/kv.go b/storage/kv.go new file mode 100644 index 0000000..af52e24 --- /dev/null +++ b/storage/kv.go @@ -0,0 +1,61 @@ +package storage + +import ( + "encoding/json" + + "git.maze.io/maze/conduit/storage/codec" +) + +// KV implements a key-value store. +type KV interface { + // Has checks if the key is present. + Has(key string) bool + + // Get a value by key. + Get(key string) (value []byte, ok bool) +} + +// WritableKV can store key-value items. +type WritableKV interface { + KV + + // Close the key-value storage. + Close() error + + // DeleteKey removes a key. + Delete(key string) error + + // Set a value by key. + Set(key string, value []byte) error +} + +type EncodedKV interface { + Has(key string) bool + Get(key string, value any) (ok bool, err error) +} + +type EncodedWritableKV interface { + EncodedKV + + Close() error + + Delete(key string) error + + Set(key string, value []byte) error +} + +type encodedKV struct { + kv KV + encode func(any) ([]byte, error) + decode func([]byte, any) error +} + +func (kv encodedKV) Has(key) + +func NewEncodedKV(kv KV, codec codec.Codec) EncodedKV { + return &encodedKV{ + kv: kv, + encode: json.Marshal, + decode: json.Unmarshal, + } +} diff --git a/storage/testdata/kv b/storage/testdata/kv new file mode 100644 index 0000000..da72d09 --- /dev/null +++ b/storage/testdata/kv @@ -0,0 +1,4 @@ +test:data +ignored line because not relevant +empty: +:emptykey diff --git a/testdata/conduit.ed25519 b/testdata/conduit.ed25519 new file mode 100644 index 0000000..7d2ca2f --- /dev/null +++ b/testdata/conduit.ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDRyBi3zVFplREzSzhuwqZInx5wRAk67DCAeODMiZ4XhQAAAKDwAZrd8AGa +3QAAAAtzc2gtZWQyNTUxOQAAACDRyBi3zVFplREzSzhuwqZInx5wRAk67DCAeODMiZ4XhQ +AAAEDhHNQENt5Ldinm1TSlGJTVdjniydYpsTNXYmyLK2BUHNHIGLfNUWmVETNLOG7Cpkif +HnBECTrsMIB44MyJnheFAAAAGENvbmR1aXQgZGVtbyBwcml2YXRlIGtleQECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/conduit.ed25519.pub b/testdata/conduit.ed25519.pub new file mode 100644 index 0000000..b796c73 --- /dev/null +++ b/testdata/conduit.ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINHIGLfNUWmVETNLOG7CpkifHnBECTrsMIB44MyJnheF Conduit demo private key diff --git a/testdata/conduit.rsa b/testdata/conduit.rsa new file mode 100644 index 0000000..9d6004b --- /dev/null +++ b/testdata/conduit.rsa @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEArnzuzlKni2ZFF+6hmrTpO/Z2U0Bqgmx8U6es/DW/udhyYkQ14C32 +P98y1/95DnY1a0Lo8BBH2w+iFToxd4Jw5IGL45yUTlxIr95NlAKWM62yXmxaIXBT665N1q +m8znjEF3IhLkjciVd9xTGAZ2fGPmK3+WuMeUdKcW5nQpsUjoYoTiGWJSjYz6t1FX18LYfz +VuCoh1JJrh+neMeA9zjCfsSnivRLtObMwVsER2N76ZX9EmIsA3d8T5RWpTxtlMwN4//pHb +UH9KjSRslqY0Yx7/QbyTD/xvmNgnJL8oHrWAX3L1/SRm/6fBtSclOeTgw+ZmsQ6wkhZvGq +g27wi5ngGwAAA9DJal7zyWpe8wAAAAdzc2gtcnNhAAABAQCufO7OUqeLZkUX7qGatOk79n +ZTQGqCbHxTp6z8Nb+52HJiRDXgLfY/3zLX/3kOdjVrQujwEEfbD6IVOjF3gnDkgYvjnJRO +XEiv3k2UApYzrbJebFohcFPrrk3WqbzOeMQXciEuSNyJV33FMYBnZ8Y+Yrf5a4x5R0pxbm +dCmxSOhihOIZYlKNjPq3UVfXwth/NW4KiHUkmuH6d4x4D3OMJ+xKeK9Eu05szBWwRHY3vp +lf0SYiwDd3xPlFalPG2UzA3j/+kdtQf0qNJGyWpjRjHv9BvJMP/G+Y2CckvygetYBfcvX9 +JGb/p8G1JyU55ODD5maxDrCSFm8aqDbvCLmeAbAAAAAwEAAQAAAQEAkqLjffjwXLIhtq8Q +mJcYuw+w+N3VpK3O/e6X7YyuB1zjI7n3HOMDY0IL1IIaFhE5a17bq4PDH1HQAM7a63hvr1 +k/WpUn/YKIg2PrBkv2No/uqnOceyWPIS1mtNQIm+vZv2pmgCMzUyh3xdSH+F65t4v22GGN +uA41fYYuuUbiy7KHsJy0JYE04VMO7XipWUOJoaqOBosS/gnRKd4e1axa8kAbOQbpzi4C7a +EVNHiwiWwSuuBtLzWC1NvTMNVn6dQCmKHcLCdumxZ0iXzz/nvFFkfNkYE5Gn0wdeMZK4Wh +ffAOqjcA94D+UNzK0pMzO1o4H54wPraJy+0LUP0LPQAm4QAAAIBPHtw0GedG6SKH4hMhsO +KeKq4tXHu7blSjsMtV+OGYAhl3LT4JOCDUjLczUU8gKbI6wwR3YIk9K/1s7G2DOYbil01F +5IphOYEYRyzxrDjKm/hnI0GhRaScipgxICqu1HlIR1CtC36ZRpamhufC0P0h4+eY1t1saT +4Tppu3NAlSLwAAAIEA2EVuuPOhgQxoK21cxG4mdpjYmNbhBbr1xPWO3mmxN3iyilE5wuSY +A/r3HDJl2qzLpaG9CQ4VT9GywuGX3lTXiOOc98zNAqyUYgDmCcZqlsaeSt+WJVBWgBRCz4 +BwM9MZ4wLSzvBWSZiIls3OZuVWjLGDZuImyfWmN8YV5aPCMsUAAACBAM6KkL5IURjyD3gi +sQA/1Qtz4j7fMou69GqhMhZPhvt/BPGe761ciH1+4ixweb+lHCqZjW5MB9LKYH1Dpjq7X1 +maiOLcPYPBKvSwYQmCcGKct1DXY8623QKYZHqmBa7NJZUjuvC2reIc/a2uwWRV0CKXexpw +0mMIZroWkk5xUXVfAAAAGENvbmR1aXQgZGVtbyBwcml2YXRlIGtleQEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/conduit.rsa.pub b/testdata/conduit.rsa.pub new file mode 100644 index 0000000..31aebea --- /dev/null +++ b/testdata/conduit.rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCufO7OUqeLZkUX7qGatOk79nZTQGqCbHxTp6z8Nb+52HJiRDXgLfY/3zLX/3kOdjVrQujwEEfbD6IVOjF3gnDkgYvjnJROXEiv3k2UApYzrbJebFohcFPrrk3WqbzOeMQXciEuSNyJV33FMYBnZ8Y+Yrf5a4x5R0pxbmdCmxSOhihOIZYlKNjPq3UVfXwth/NW4KiHUkmuH6d4x4D3OMJ+xKeK9Eu05szBWwRHY3vplf0SYiwDd3xPlFalPG2UzA3j/+kdtQf0qNJGyWpjRjHv9BvJMP/G+Y2CckvygetYBfcvX9JGb/p8G1JyU55ODD5maxDrCSFm8aqDbvCLmeAb Conduit demo private key diff --git a/testdata/example.ed25519 b/testdata/example.ed25519 new file mode 100644 index 0000000..06b86b9 --- /dev/null +++ b/testdata/example.ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCcAzG/ziVMEPefEVafcSXLygbyZ6mgbUqvn2GQhJ+awwAAAJhCdsmgQnbJ +oAAAAAtzc2gtZWQyNTUxOQAAACCcAzG/ziVMEPefEVafcSXLygbyZ6mgbUqvn2GQhJ+aww +AAAEAO9YENyIi8hSQ1VYifWhK/+r0q6+zieVq/QIKttoxapZwDMb/OJUwQ958RVp9xJcvK +BvJnqaBtSq+fYZCEn5rDAAAAEEV4YW1wbGUgdXNlciBrZXkBAgMEBQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/example.ed25519-cert.pub b/testdata/example.ed25519-cert.pub new file mode 100644 index 0000000..b56e03d --- /dev/null +++ b/testdata/example.ed25519-cert.pub @@ -0,0 +1 @@ +ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIH2JTPouysbfVmVz7meUXWd3Gy0vbaDFBlg27Eg1i1MNAAAAIJwDMb/OJUwQ958RVp9xJcvKBvJnqaBtSq+fYZCEn5rDAAAAAAAAAAAAAAABAAAAB2V4YW1wbGUAAAATAAAAB2V4YW1wbGUAAAAEZGVtbwAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEArnzuzlKni2ZFF+6hmrTpO/Z2U0Bqgmx8U6es/DW/udhyYkQ14C32P98y1/95DnY1a0Lo8BBH2w+iFToxd4Jw5IGL45yUTlxIr95NlAKWM62yXmxaIXBT665N1qm8znjEF3IhLkjciVd9xTGAZ2fGPmK3+WuMeUdKcW5nQpsUjoYoTiGWJSjYz6t1FX18LYfzVuCoh1JJrh+neMeA9zjCfsSnivRLtObMwVsER2N76ZX9EmIsA3d8T5RWpTxtlMwN4//pHbUH9KjSRslqY0Yx7/QbyTD/xvmNgnJL8oHrWAX3L1/SRm/6fBtSclOeTgw+ZmsQ6wkhZvGqg27wi5ngGwAAARQAAAAMcnNhLXNoYTItNTEyAAABAFK5fNyZp5ZwEXsDaOcsxP3+PqycVunqeGig0WdN61amDShHu3r+tO03iDlu1C8nVWdGfwIrfh9CekxorozVpCR9KMkahozcPGcWo7tYN7/CRfJt4PrN4AO1mAhhTC90V1awBaVI3fjK3fOk0xVkkyCWBsFI1Dks8ImnAuX2eFgG0IFboN0fa9yKDPx2cOPVlrV3Coq49q0ivu0aipNd+M80ayntlKFCr4A/t2lTBeKHeiqSP6sP7puN3iWZgztDFkMxNJezMnu0dTq1/5rhSdzcoX1SYkOMS32J+njcXjC8tWsFlhRSPsx7OqFdFVX1NPgc5IArw3Tj+mQGrKdnYsc= Example user key diff --git a/testdata/example.ed25519.pub b/testdata/example.ed25519.pub new file mode 100644 index 0000000..e08559f --- /dev/null +++ b/testdata/example.ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJwDMb/OJUwQ958RVp9xJcvKBvJnqaBtSq+fYZCEn5rD Example user key diff --git a/testdata/keys/test_id_ed25519_a b/testdata/keys/test_id_ed25519_a new file mode 100644 index 0000000..fad3ed0 --- /dev/null +++ b/testdata/keys/test_id_ed25519_a @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBaNZbepRJPtVVKzG4ZWlaZCNKdUxVBrox2iip+/laBmwAAAIi3gDMRt4Az +EQAAAAtzc2gtZWQyNTUxOQAAACBaNZbepRJPtVVKzG4ZWlaZCNKdUxVBrox2iip+/laBmw +AAAEBk1ZexnhZdDjMBYGVc1MfVIJPYxLsqxuVT/7QrAPJexlo1lt6lEk+1VUrMbhlaVpkI +0p1TFUGujHaKKn7+VoGbAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_a.pub b/testdata/keys/test_id_ed25519_a.pub new file mode 100644 index 0000000..189fc1d --- /dev/null +++ b/testdata/keys/test_id_ed25519_a.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFo1lt6lEk+1VUrMbhlaVpkI0p1TFUGujHaKKn7+VoGb diff --git a/testdata/keys/test_id_ed25519_b b/testdata/keys/test_id_ed25519_b new file mode 100644 index 0000000..bb63d24 --- /dev/null +++ b/testdata/keys/test_id_ed25519_b @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAgPXUIzXl93gb5DlyTfpyZtQtlvVLUYi2yvTvOzI5IqwAAAIj0LYbK9C2G +ygAAAAtzc2gtZWQyNTUxOQAAACAgPXUIzXl93gb5DlyTfpyZtQtlvVLUYi2yvTvOzI5Iqw +AAAECLb8tIZGTc0AocsQx7FxKHoFyQ5fR8aYYitu2NiujlgyA9dQjNeX3eBvkOXJN+nJm1 +C2W9UtRiLbK9O87MjkirAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_b.pub b/testdata/keys/test_id_ed25519_b.pub new file mode 100644 index 0000000..1cfa4e1 --- /dev/null +++ b/testdata/keys/test_id_ed25519_b.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICA9dQjNeX3eBvkOXJN+nJm1C2W9UtRiLbK9O87Mjkir diff --git a/testdata/keys/test_id_ed25519_c b/testdata/keys/test_id_ed25519_c new file mode 100644 index 0000000..4058696 --- /dev/null +++ b/testdata/keys/test_id_ed25519_c @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDm3ZNg2OwpyOgLse4weKA0AoVDc9rY5XEHw/y8wzBhLgAAAIhJAmhKSQJo +SgAAAAtzc2gtZWQyNTUxOQAAACDm3ZNg2OwpyOgLse4weKA0AoVDc9rY5XEHw/y8wzBhLg +AAAEDjbxojnzIkz9CuH3QANAkk7Kox3Oz85Nq2uVlhf6kmJObdk2DY7CnI6Aux7jB4oDQC +hUNz2tjlcQfD/LzDMGEuAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_c.pub b/testdata/keys/test_id_ed25519_c.pub new file mode 100644 index 0000000..56b683b --- /dev/null +++ b/testdata/keys/test_id_ed25519_c.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIObdk2DY7CnI6Aux7jB4oDQChUNz2tjlcQfD/LzDMGEu diff --git a/testdata/keys/test_id_ed25519_d b/testdata/keys/test_id_ed25519_d new file mode 100644 index 0000000..ed56fe7 --- /dev/null +++ b/testdata/keys/test_id_ed25519_d @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDBQODCk5lDPxFVJnX3xc/yVM8Xq3MVt0RDkjWdd9pDkwAAAIgFh+AQBYfg +EAAAAAtzc2gtZWQyNTUxOQAAACDBQODCk5lDPxFVJnX3xc/yVM8Xq3MVt0RDkjWdd9pDkw +AAAEAMdFkVlAmVL0ecGpOYK+o/uJvunuJIiScQbke8xRLfS8FA4MKTmUM/EVUmdffFz/JU +zxercxW3REOSNZ132kOTAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_d.pub b/testdata/keys/test_id_ed25519_d.pub new file mode 100644 index 0000000..378fa46 --- /dev/null +++ b/testdata/keys/test_id_ed25519_d.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMFA4MKTmUM/EVUmdffFz/JUzxercxW3REOSNZ132kOT diff --git a/testdata/keys/test_id_ed25519_e b/testdata/keys/test_id_ed25519_e new file mode 100644 index 0000000..acec40a --- /dev/null +++ b/testdata/keys/test_id_ed25519_e @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCSAEB5cNSSrzWoPCCq53E2AvYzV76T3QjWGj7TFZaNYwAAAIhAyiXGQMol +xgAAAAtzc2gtZWQyNTUxOQAAACCSAEB5cNSSrzWoPCCq53E2AvYzV76T3QjWGj7TFZaNYw +AAAED2o3NXUBsaKL1aKda3pxLimBGTcPuzkRhYZ5Dys7ERw5IAQHlw1JKvNag8IKrncTYC +9jNXvpPdCNYaPtMVlo1jAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_e.pub b/testdata/keys/test_id_ed25519_e.pub new file mode 100644 index 0000000..d7489a6 --- /dev/null +++ b/testdata/keys/test_id_ed25519_e.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJIAQHlw1JKvNag8IKrncTYC9jNXvpPdCNYaPtMVlo1j diff --git a/testdata/keys/test_id_ed25519_f b/testdata/keys/test_id_ed25519_f new file mode 100644 index 0000000..44730c7 --- /dev/null +++ b/testdata/keys/test_id_ed25519_f @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCanTkfwlCIaVcacPGL3jeNVwoXbWmK+akknwunyq6uWgAAAIg4gKjhOICo +4QAAAAtzc2gtZWQyNTUxOQAAACCanTkfwlCIaVcacPGL3jeNVwoXbWmK+akknwunyq6uWg +AAAEBYShYJSTJICVjAX7WyLePUtRfF8ThMl6BUHsgGUcLLS5qdOR/CUIhpVxpw8YveN41X +ChdtaYr5qSSfC6fKrq5aAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_f.pub b/testdata/keys/test_id_ed25519_f.pub new file mode 100644 index 0000000..9543d08 --- /dev/null +++ b/testdata/keys/test_id_ed25519_f.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJqdOR/CUIhpVxpw8YveN41XChdtaYr5qSSfC6fKrq5a diff --git a/testdata/keys/test_id_ed25519_g b/testdata/keys/test_id_ed25519_g new file mode 100644 index 0000000..29faee4 --- /dev/null +++ b/testdata/keys/test_id_ed25519_g @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCmgF9b/O4j4Mp/gD3akcmOQg3uLiooXqTXGy0IzQCreQAAAIhOHBGGThwR +hgAAAAtzc2gtZWQyNTUxOQAAACCmgF9b/O4j4Mp/gD3akcmOQg3uLiooXqTXGy0IzQCreQ +AAAEDEkL3EQCRMpsA2WCw9ItOv+BOaJAj3ukvbr3p/9VsXmaaAX1v87iPgyn+APdqRyY5C +De4uKihepNcbLQjNAKt5AAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_g.pub b/testdata/keys/test_id_ed25519_g.pub new file mode 100644 index 0000000..2eb0f95 --- /dev/null +++ b/testdata/keys/test_id_ed25519_g.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKaAX1v87iPgyn+APdqRyY5CDe4uKihepNcbLQjNAKt5 diff --git a/testdata/keys/test_id_ed25519_h b/testdata/keys/test_id_ed25519_h new file mode 100644 index 0000000..1ef0b3f --- /dev/null +++ b/testdata/keys/test_id_ed25519_h @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCuBduE8JMIOG256dOIHsmL+9746xpONczBzGGwUv9DzgAAAIgVDPWqFQz1 +qgAAAAtzc2gtZWQyNTUxOQAAACCuBduE8JMIOG256dOIHsmL+9746xpONczBzGGwUv9Dzg +AAAECOgmmc+eoWrBB0hY1yTB7NTS9oIzDSnYYUPSX/p3E4Cq4F24Twkwg4bbnp04geyYv7 +3vjrGk41zMHMYbBS/0POAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_h.pub b/testdata/keys/test_id_ed25519_h.pub new file mode 100644 index 0000000..05dba39 --- /dev/null +++ b/testdata/keys/test_id_ed25519_h.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK4F24Twkwg4bbnp04geyYv73vjrGk41zMHMYbBS/0PO diff --git a/testdata/keys/test_id_ed25519_i b/testdata/keys/test_id_ed25519_i new file mode 100644 index 0000000..c84d2fb --- /dev/null +++ b/testdata/keys/test_id_ed25519_i @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAP2hN6igLqAA4zBhse8CSStWYPO31HUPukb7ixYNG85AAAAIie1TyintU8 +ogAAAAtzc2gtZWQyNTUxOQAAACAP2hN6igLqAA4zBhse8CSStWYPO31HUPukb7ixYNG85A +AAAEDgLZocjGTRre5cBGYPiQhrgW1SS+hyCz1G86Z2Pgg9Tw/aE3qKAuoADjMGGx7wJJK1 +Zg87fUdQ+6RvuLFg0bzkAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_i.pub b/testdata/keys/test_id_ed25519_i.pub new file mode 100644 index 0000000..67d9eae --- /dev/null +++ b/testdata/keys/test_id_ed25519_i.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA/aE3qKAuoADjMGGx7wJJK1Zg87fUdQ+6RvuLFg0bzk diff --git a/testdata/keys/test_id_ed25519_j b/testdata/keys/test_id_ed25519_j new file mode 100644 index 0000000..7ab6807 --- /dev/null +++ b/testdata/keys/test_id_ed25519_j @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDB4NfG2rYL07aZjhY2QWpHqcosZ0XDIYv1VsU0AwYUAwAAAIgAMqKwADKi +sAAAAAtzc2gtZWQyNTUxOQAAACDB4NfG2rYL07aZjhY2QWpHqcosZ0XDIYv1VsU0AwYUAw +AAAEBE92CUagxRtrqwPnOk3NxWJ38lMoI1jCrXF3iZYDjDysHg18batgvTtpmOFjZBakep +yixnRcMhi/VWxTQDBhQDAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_j.pub b/testdata/keys/test_id_ed25519_j.pub new file mode 100644 index 0000000..d03e521 --- /dev/null +++ b/testdata/keys/test_id_ed25519_j.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMHg18batgvTtpmOFjZBakepyixnRcMhi/VWxTQDBhQD diff --git a/testdata/keys/test_id_ed25519_k b/testdata/keys/test_id_ed25519_k new file mode 100644 index 0000000..a82d8df --- /dev/null +++ b/testdata/keys/test_id_ed25519_k @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDlA9NjGTu0MfR5t/6Qbw+X9YhAxY/e4STc8l5l5oiMzwAAAIixZyEHsWch +BwAAAAtzc2gtZWQyNTUxOQAAACDlA9NjGTu0MfR5t/6Qbw+X9YhAxY/e4STc8l5l5oiMzw +AAAECJz/+yGGs6Tv3DAb/Bmodgj4nsHShB+qn4dPIIlWE+BuUD02MZO7Qx9Hm3/pBvD5f1 +iEDFj97hJNzyXmXmiIzPAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_k.pub b/testdata/keys/test_id_ed25519_k.pub new file mode 100644 index 0000000..82cccc3 --- /dev/null +++ b/testdata/keys/test_id_ed25519_k.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOUD02MZO7Qx9Hm3/pBvD5f1iEDFj97hJNzyXmXmiIzP diff --git a/testdata/keys/test_id_ed25519_l b/testdata/keys/test_id_ed25519_l new file mode 100644 index 0000000..ffc3e05 --- /dev/null +++ b/testdata/keys/test_id_ed25519_l @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACA9c0oRAlg1bQV4aASHj4fsBCtsuvWHG70M7I270P/DHgAAAIh1NgxzdTYM +cwAAAAtzc2gtZWQyNTUxOQAAACA9c0oRAlg1bQV4aASHj4fsBCtsuvWHG70M7I270P/DHg +AAAEAKRU+DiCxLCMuuN89DphFJ9pWshOGymhWBFyD37VgkoD1zShECWDVtBXhoBIePh+wE +K2y69YcbvQzsjbvQ/8MeAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_l.pub b/testdata/keys/test_id_ed25519_l.pub new file mode 100644 index 0000000..c2ef515 --- /dev/null +++ b/testdata/keys/test_id_ed25519_l.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAID1zShECWDVtBXhoBIePh+wEK2y69YcbvQzsjbvQ/8Me diff --git a/testdata/keys/test_id_ed25519_m b/testdata/keys/test_id_ed25519_m new file mode 100644 index 0000000..a7c19d7 --- /dev/null +++ b/testdata/keys/test_id_ed25519_m @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDwr8Ap993ioXt7nVs16V9Q8K8rem1ebmT6DL0fncKqyQAAAIignn3boJ59 +2wAAAAtzc2gtZWQyNTUxOQAAACDwr8Ap993ioXt7nVs16V9Q8K8rem1ebmT6DL0fncKqyQ +AAAECUi0UUw2RmFxKAlwcHZxqkT3cCbcNqNRk/rZnkZaFIl/CvwCn33eKhe3udWzXpX1Dw +ryt6bV5uZPoMvR+dwqrJAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_m.pub b/testdata/keys/test_id_ed25519_m.pub new file mode 100644 index 0000000..1640f8c --- /dev/null +++ b/testdata/keys/test_id_ed25519_m.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPCvwCn33eKhe3udWzXpX1Dwryt6bV5uZPoMvR+dwqrJ diff --git a/testdata/keys/test_id_ed25519_n b/testdata/keys/test_id_ed25519_n new file mode 100644 index 0000000..0bb14a3 --- /dev/null +++ b/testdata/keys/test_id_ed25519_n @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACAL+rCmYdr90w9sQGTc4q2jrXC0T/HiXV7hAC/rGSbAEgAAAIj8310O/N9d +DgAAAAtzc2gtZWQyNTUxOQAAACAL+rCmYdr90w9sQGTc4q2jrXC0T/HiXV7hAC/rGSbAEg +AAAECEqFXGeY12fcYUBwBn9ltRldW9IeP6/cI084TuLUBmdwv6sKZh2v3TD2xAZNziraOt +cLRP8eJdXuEAL+sZJsASAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_n.pub b/testdata/keys/test_id_ed25519_n.pub new file mode 100644 index 0000000..0860b72 --- /dev/null +++ b/testdata/keys/test_id_ed25519_n.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAv6sKZh2v3TD2xAZNziraOtcLRP8eJdXuEAL+sZJsAS diff --git a/testdata/keys/test_id_ed25519_o b/testdata/keys/test_id_ed25519_o new file mode 100644 index 0000000..b2e11a6 --- /dev/null +++ b/testdata/keys/test_id_ed25519_o @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDrlvw5/7W09kgkralSUFN6SU/INhuo1lxMGgm9sF3jiQAAAIgJq4FqCauB +agAAAAtzc2gtZWQyNTUxOQAAACDrlvw5/7W09kgkralSUFN6SU/INhuo1lxMGgm9sF3jiQ +AAAECZKFVeNm3E4LFqGUZjE2KRUE6DtqSRAoUspke+mQYGPuuW/Dn/tbT2SCStqVJQU3pJ +T8g2G6jWXEwaCb2wXeOJAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_o.pub b/testdata/keys/test_id_ed25519_o.pub new file mode 100644 index 0000000..df08674 --- /dev/null +++ b/testdata/keys/test_id_ed25519_o.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOuW/Dn/tbT2SCStqVJQU3pJT8g2G6jWXEwaCb2wXeOJ diff --git a/testdata/keys/test_id_ed25519_p b/testdata/keys/test_id_ed25519_p new file mode 100644 index 0000000..ae10cfa --- /dev/null +++ b/testdata/keys/test_id_ed25519_p @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDBVOlRzQJsfAtS8RHRfe8/RUe53TXgeKpJui4+bQ4cXAAAAIiWs4rJlrOK +yQAAAAtzc2gtZWQyNTUxOQAAACDBVOlRzQJsfAtS8RHRfe8/RUe53TXgeKpJui4+bQ4cXA +AAAEC5vxPtZLKeN8A3jAyK5O9SLYbp3LWwbDKZLgQWwmOwA8FU6VHNAmx8C1LxEdF97z9F +R7ndNeB4qkm6Lj5tDhxcAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_p.pub b/testdata/keys/test_id_ed25519_p.pub new file mode 100644 index 0000000..8a74dfd --- /dev/null +++ b/testdata/keys/test_id_ed25519_p.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMFU6VHNAmx8C1LxEdF97z9FR7ndNeB4qkm6Lj5tDhxc diff --git a/testdata/keys/test_id_ed25519_q b/testdata/keys/test_id_ed25519_q new file mode 100644 index 0000000..6bfeac0 --- /dev/null +++ b/testdata/keys/test_id_ed25519_q @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACANC9xH5Wixxwpy0IVJNfKHdWEqIz5bpGDX8H21fhmSKAAAAIjzbmzD825s +wwAAAAtzc2gtZWQyNTUxOQAAACANC9xH5Wixxwpy0IVJNfKHdWEqIz5bpGDX8H21fhmSKA +AAAEDi1lOpgb7xVeKVcn6NtL+9YuxDKyW8VTqYnB/DTvfrNQ0L3EflaLHHCnLQhUk18od1 +YSojPlukYNfwfbV+GZIoAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_q.pub b/testdata/keys/test_id_ed25519_q.pub new file mode 100644 index 0000000..2605dc5 --- /dev/null +++ b/testdata/keys/test_id_ed25519_q.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA0L3EflaLHHCnLQhUk18od1YSojPlukYNfwfbV+GZIo diff --git a/testdata/keys/test_id_ed25519_r b/testdata/keys/test_id_ed25519_r new file mode 100644 index 0000000..f4d3f58 --- /dev/null +++ b/testdata/keys/test_id_ed25519_r @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACADzkeAxKvKL5pKUlTt1SezzmpZS73AgaFA+/oOa6AFfAAAAIgXQ569F0Oe +vQAAAAtzc2gtZWQyNTUxOQAAACADzkeAxKvKL5pKUlTt1SezzmpZS73AgaFA+/oOa6AFfA +AAAECg8l+0RHTtTR4KaHGoSDQUwjf1diHrl4X3VksZ2JytwgPOR4DEq8ovmkpSVO3VJ7PO +allLvcCBoUD7+g5roAV8AAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_r.pub b/testdata/keys/test_id_ed25519_r.pub new file mode 100644 index 0000000..f6f34ec --- /dev/null +++ b/testdata/keys/test_id_ed25519_r.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAPOR4DEq8ovmkpSVO3VJ7POallLvcCBoUD7+g5roAV8 diff --git a/testdata/keys/test_id_ed25519_s b/testdata/keys/test_id_ed25519_s new file mode 100644 index 0000000..37684da --- /dev/null +++ b/testdata/keys/test_id_ed25519_s @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCFd9rETu9NZWCGUIszhtv+HIxpq3SABTvu0ixK2eDHygAAAIjirHXj4qx1 +4wAAAAtzc2gtZWQyNTUxOQAAACCFd9rETu9NZWCGUIszhtv+HIxpq3SABTvu0ixK2eDHyg +AAAECJzn3k3eM9694KCZiOBZPPFFNXqng2vV5niPVDMT3aQIV32sRO701lYIZQizOG2/4c +jGmrdIAFO+7SLErZ4MfKAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_s.pub b/testdata/keys/test_id_ed25519_s.pub new file mode 100644 index 0000000..b090988 --- /dev/null +++ b/testdata/keys/test_id_ed25519_s.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIV32sRO701lYIZQizOG2/4cjGmrdIAFO+7SLErZ4MfK diff --git a/testdata/keys/test_id_ed25519_t b/testdata/keys/test_id_ed25519_t new file mode 100644 index 0000000..87e51db --- /dev/null +++ b/testdata/keys/test_id_ed25519_t @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCbKLr5W3EoAEEC/y9KFr/nAm7mRcMv246pOw6ZBiSeJwAAAIjEXWV/xF1l +fwAAAAtzc2gtZWQyNTUxOQAAACCbKLr5W3EoAEEC/y9KFr/nAm7mRcMv246pOw6ZBiSeJw +AAAED3mCjxtEhxY5/Jm0ByKs2+/8PsNdWJPjdQ6QZFIBbczZsouvlbcSgAQQL/L0oWv+cC +buZFwy/bjqk7DpkGJJ4nAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_t.pub b/testdata/keys/test_id_ed25519_t.pub new file mode 100644 index 0000000..d8469dc --- /dev/null +++ b/testdata/keys/test_id_ed25519_t.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJsouvlbcSgAQQL/L0oWv+cCbuZFwy/bjqk7DpkGJJ4n diff --git a/testdata/keys/test_id_ed25519_u b/testdata/keys/test_id_ed25519_u new file mode 100644 index 0000000..ac3336e --- /dev/null +++ b/testdata/keys/test_id_ed25519_u @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDAQJBCBUJ55Aqy7c0eqvlM8FdrTynepQr72uaWofnVrAAAAIiWeKP9lnij +/QAAAAtzc2gtZWQyNTUxOQAAACDAQJBCBUJ55Aqy7c0eqvlM8FdrTynepQr72uaWofnVrA +AAAEAoLltBG4iUsZDBTaWDv3mie5zJn6tMIQz9l5jCB1phlcBAkEIFQnnkCrLtzR6q+Uzw +V2tPKd6lCvva5pah+dWsAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_u.pub b/testdata/keys/test_id_ed25519_u.pub new file mode 100644 index 0000000..27a8218 --- /dev/null +++ b/testdata/keys/test_id_ed25519_u.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMBAkEIFQnnkCrLtzR6q+UzwV2tPKd6lCvva5pah+dWs diff --git a/testdata/keys/test_id_ed25519_v b/testdata/keys/test_id_ed25519_v new file mode 100644 index 0000000..4591ee5 --- /dev/null +++ b/testdata/keys/test_id_ed25519_v @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDK3NCwJUuiCYMoAh3WcP0uDgSMJxdOTjNVkAZFMjbjOwAAAIj2Cvar9gr2 +qwAAAAtzc2gtZWQyNTUxOQAAACDK3NCwJUuiCYMoAh3WcP0uDgSMJxdOTjNVkAZFMjbjOw +AAAEDw5HzNtISf8jObCiX1TIfMG4riUafWdmaMnbHylD9Pdsrc0LAlS6IJgygCHdZw/S4O +BIwnF05OM1WQBkUyNuM7AAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_v.pub b/testdata/keys/test_id_ed25519_v.pub new file mode 100644 index 0000000..747ba9f --- /dev/null +++ b/testdata/keys/test_id_ed25519_v.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMrc0LAlS6IJgygCHdZw/S4OBIwnF05OM1WQBkUyNuM7 diff --git a/testdata/keys/test_id_ed25519_w b/testdata/keys/test_id_ed25519_w new file mode 100644 index 0000000..fac8cc1 --- /dev/null +++ b/testdata/keys/test_id_ed25519_w @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACA2M9JkSMBC875ww1f5tYpFGc0sHbvTVW0ZbiOO/6mGBgAAAIi+yvZUvsr2 +VAAAAAtzc2gtZWQyNTUxOQAAACA2M9JkSMBC875ww1f5tYpFGc0sHbvTVW0ZbiOO/6mGBg +AAAECTIiMBCKvnKDMSTN/cSJh0vTRlQQ5274pROcyxYXYOIzYz0mRIwELzvnDDV/m1ikUZ +zSwdu9NVbRluI47/qYYGAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_w.pub b/testdata/keys/test_id_ed25519_w.pub new file mode 100644 index 0000000..e4a6328 --- /dev/null +++ b/testdata/keys/test_id_ed25519_w.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDYz0mRIwELzvnDDV/m1ikUZzSwdu9NVbRluI47/qYYG diff --git a/testdata/keys/test_id_ed25519_x b/testdata/keys/test_id_ed25519_x new file mode 100644 index 0000000..1e51c7d --- /dev/null +++ b/testdata/keys/test_id_ed25519_x @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACBsnmWDuYTUNvauU7ZBdrvOyHo5eNqkk80OcuOSMj9tcgAAAIirtwdgq7cH +YAAAAAtzc2gtZWQyNTUxOQAAACBsnmWDuYTUNvauU7ZBdrvOyHo5eNqkk80OcuOSMj9tcg +AAAED42nmxcvm2cvfubKitLujExxqkPWrOftVpFaHf/AzghGyeZYO5hNQ29q5TtkF2u87I +ejl42qSTzQ5y45IyP21yAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_x.pub b/testdata/keys/test_id_ed25519_x.pub new file mode 100644 index 0000000..e669b65 --- /dev/null +++ b/testdata/keys/test_id_ed25519_x.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGyeZYO5hNQ29q5TtkF2u87Iejl42qSTzQ5y45IyP21y diff --git a/testdata/keys/test_id_ed25519_y b/testdata/keys/test_id_ed25519_y new file mode 100644 index 0000000..28e3a5a --- /dev/null +++ b/testdata/keys/test_id_ed25519_y @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCuRiKLmd3Nn/g68KvM98MSQNwCwqoitRJ0ACK6Td0ddAAAAIhGhwJJRocC +SQAAAAtzc2gtZWQyNTUxOQAAACCuRiKLmd3Nn/g68KvM98MSQNwCwqoitRJ0ACK6Td0ddA +AAAEC5FBhUCcbqEb7Q+mIOYaSdQjJjMo8Z5kiC7/TSWArLIa5GIouZ3c2f+Drwq8z3wxJA +3ALCqiK1EnQAIrpN3R10AAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_y.pub b/testdata/keys/test_id_ed25519_y.pub new file mode 100644 index 0000000..b9c4739 --- /dev/null +++ b/testdata/keys/test_id_ed25519_y.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK5GIouZ3c2f+Drwq8z3wxJA3ALCqiK1EnQAIrpN3R10 diff --git a/testdata/keys/test_id_ed25519_z b/testdata/keys/test_id_ed25519_z new file mode 100644 index 0000000..5e82bc1 --- /dev/null +++ b/testdata/keys/test_id_ed25519_z @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCGJwm/k2TVkk7PwO/evicuZJsTUaz8JtZaO8GWhn/5iwAAAIh1liY3dZYm +NwAAAAtzc2gtZWQyNTUxOQAAACCGJwm/k2TVkk7PwO/evicuZJsTUaz8JtZaO8GWhn/5iw +AAAECDaKj+ZrVQd/GvV/sPIcFPXc/X7yVdzdeCPqr5OeXNAIYnCb+TZNWSTs/A796+Jy5k +mxNRrPwm1lo7wZaGf/mLAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_ed25519_z.pub b/testdata/keys/test_id_ed25519_z.pub new file mode 100644 index 0000000..c809466 --- /dev/null +++ b/testdata/keys/test_id_ed25519_z.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIYnCb+TZNWSTs/A796+Jy5kmxNRrPwm1lo7wZaGf/mL diff --git a/testdata/keys/test_id_rsa_a b/testdata/keys/test_id_rsa_a new file mode 100644 index 0000000..e0a668a --- /dev/null +++ b/testdata/keys/test_id_rsa_a @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAz6rgM/t/WYNtarYTlyqGFB8mB9oNM8YmKz6swVkQfJGcYlojfCJd +CD/ktUR+pKIRU23Fjydb7H58xOSaQvwng9fYGyTVn6rjTyjxpkR6yFO/4+LkLBWwd9JcrA +7u8B1M9fTL7ZWKFYTkaHDBpj1NimEcZyx26YNe5oawINbiJUORm8M6Pb91dAU1P+Fd3G4R +mlVcC26aHAEmyP0QiNIIa4JEyQGfWh+0KYat/6i2wcQozfAXzBQapmSzFmQ3VG+VlqhKAS +yBntrV109axmyMw7ZyRizoOgSmC9rOlS1H1gzcfVfzPCRZIxxtDse515VyWoL8ExrcPodu +GgvSWJIKBQAAA7gkQHLfJEBy3wAAAAdzc2gtcnNhAAABAQDPquAz+39Zg21qthOXKoYUHy +YH2g0zxiYrPqzBWRB8kZxiWiN8Il0IP+S1RH6kohFTbcWPJ1vsfnzE5JpC/CeD19gbJNWf +quNPKPGmRHrIU7/j4uQsFbB30lysDu7wHUz19MvtlYoVhORocMGmPU2KYRxnLHbpg17mhr +Ag1uIlQ5Gbwzo9v3V0BTU/4V3cbhGaVVwLbpocASbI/RCI0ghrgkTJAZ9aH7Qphq3/qLbB +xCjN8BfMFBqmZLMWZDdUb5WWqEoBLIGe2tXXT1rGbIzDtnJGLOg6BKYL2s6VLUfWDNx9V/ +M8JFkjHG0Ox7nXlXJagvwTGtw+h24aC9JYkgoFAAAAAwEAAQAAAQAUfSV1u4CLEl5/QsuY +BIgKADC+YZMf4pLtfVeKFAF+aNdnFMGfg7xuDe/AuqtMDvlqo8/0GcwF/0zMGEnoBfzYEz +mtI+s3v6KBsuZ0oxkCHtE16LLebp1klQqzpx2H7zwioC5mmHRxPxVSAJ36it3Vo1MsEsUA +sQdx8yP5eXoCdwScAE8mtQmEMJq+CWXEhAgbfmJFBWf6HfvFTXAYyBBtqrG1qv3uiQg1MH +Bq6ZVieUQD8PUPb8BT0+1ESQbQ0eJqUG3Zp1rBYv5keW+e5WPKeYwd7mEIZCDWOquNqlk7 +jbZ1Ql7byf9iqVvKgje68RQTAZC9oS5IsA91tKyVcylBAAAAgQCeq6id3eH0xBjVVfU3CM +NWreYIbiEdkkb4YDbPcrqxx4VQbCajOIj9U4gObEjSSlliP7C13NW+6pVmsomfap86oWFz +v/eGRj+a3gxmtJKnROzkqfximsid779IHUH07fkI97CIri7qnXBhCV0RksG/PwahdDBvzy +FfZ8eYvyLEuAAAAIEA83yloPKa/V78mA/IrAyCJG74Aw8sDKaHx8hwacJCUQnZIPPOuMiq +O7Dhgmpqh/Yek3oEvPMTzy7WvmUFGKjkKPzaX6NC1NEj9PRRNdhK/wR5E464QnRO7o9jrJ +Xc2/uDMR1YpQi1lqkcayKnA36h+o5HpSORxVl5+BuLDH5VCvkAAACBANpW+5VfkHdmbe6Q +I/JFW8m2yYWCkWE213II0NooMCxYlt+P9vmDAmM9yUsfp809P9AxRxa7/OY1RJaxmPWUeX +rYkDmgTMvkr/+NPm/7oaUw0KCycoz1GEBhUzEv78gvdtLFFOr6TrkbWKzOMjPOqi7d0yTs +c1FCFFgSc7I1yM5tAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_a.pub b/testdata/keys/test_id_rsa_a.pub new file mode 100644 index 0000000..dde84e8 --- /dev/null +++ b/testdata/keys/test_id_rsa_a.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDPquAz+39Zg21qthOXKoYUHyYH2g0zxiYrPqzBWRB8kZxiWiN8Il0IP+S1RH6kohFTbcWPJ1vsfnzE5JpC/CeD19gbJNWfquNPKPGmRHrIU7/j4uQsFbB30lysDu7wHUz19MvtlYoVhORocMGmPU2KYRxnLHbpg17mhrAg1uIlQ5Gbwzo9v3V0BTU/4V3cbhGaVVwLbpocASbI/RCI0ghrgkTJAZ9aH7Qphq3/qLbBxCjN8BfMFBqmZLMWZDdUb5WWqEoBLIGe2tXXT1rGbIzDtnJGLOg6BKYL2s6VLUfWDNx9V/M8JFkjHG0Ox7nXlXJagvwTGtw+h24aC9JYkgoF diff --git a/testdata/keys/test_id_rsa_b b/testdata/keys/test_id_rsa_b new file mode 100644 index 0000000..95eabea --- /dev/null +++ b/testdata/keys/test_id_rsa_b @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAxavNj37IOyo01OSzeI4pMSEw0IQlgd0xcUHV04GLSreNPoL/PIWZ +HpjNTGE80d9G1F/83EbIqFEsK97u/9i2u7YvgaIpbB46xMeMPp8TRggnvA9zOiuV4vDDy9 +gKm1xapD0hKV/qbK7PqiC3zYbNZVgLDexiz9HEyMT8pxTRAsCZ1JZXjdIbJ/DfG0AqFCJ2 +UBph8CB6uth/tyD27+1ZKNydGa6qqODqn3leQQNAcu2T/tiXfWbCydnn2ogC3LOqE5NL/e +cFUN+sKetOcfXj2IOsx8yGQ36QONNEanromReY6Yhu6t3m9PgsrHzG1NAaX3h5gqteTxWK +q3vBygCWdwAAA7iWYdgMlmHYDAAAAAdzc2gtcnNhAAABAQDFq82Pfsg7KjTU5LN4jikxIT +DQhCWB3TFxQdXTgYtKt40+gv88hZkemM1MYTzR30bUX/zcRsioUSwr3u7/2La7ti+Boils +HjrEx4w+nxNGCCe8D3M6K5Xi8MPL2AqbXFqkPSEpX+psrs+qILfNhs1lWAsN7GLP0cTIxP +ynFNECwJnUlleN0hsn8N8bQCoUInZQGmHwIHq62H+3IPbv7Vko3J0Zrqqo4OqfeV5BA0By +7ZP+2Jd9ZsLJ2efaiALcs6oTk0v95wVQ36wp605x9ePYg6zHzIZDfpA400RqeuiZF5jpiG +7q3eb0+CysfMbU0BpfeHmCq15PFYqre8HKAJZ3AAAAAwEAAQAAAQEAlCwyjIv2wBLlQ2ij +94xo6ED+/G7yRqRt+X3mMQN8TGfG4sMDXHSw0LStv8Fr+84oo+3H976/jxw5xjD4aqQiZh +ctCTlrX3ZRi0whnpdTtT1Lu7bBIyuk0Va1DkpFuSvDbjW8c+1IJmvrkpWv8cpIkN0sfCVj +9HBD2FOBKCV8f1halxTUpzihZD8YBEX3If0062q1I9GfBEi/+7kmzC2R3kaYIOt3KSZdfw +/l+xbyhzNEbAbN0rB43wSHK3tCuTb+m2MYFo6tzNDqXQ7k+4cvO2rGb5Bb8EEn6YKwMHWy +A9JUfRdFDbyaXb9dm9P0Ilq9x+nBlUBf+RW0fL9iqhs1MQAAAIEA1fEIWEXs4PyEw2O0Xc +EiMeyvYIdeBEx+Kn9VH+Ozqpwo8MHe5Hm4RowmUUK8FtqPUMUOSojLwIpZ5Enz0yRAiqxd +j5CoDYe19pQMRt3y291HHR3eYTPfAJGvDeVm/GhmY4ArTjB7CJD+XSgB2Q17UXK/q7JTH2 +WGgGVM1PD3QhUAAACBAO8UMkNkzJC480gVmd3mv2hQ567kBUYXa5mltMoaLHbpQ6Xb0lm/ +wBN7kvHHkA2Mrg5WLDMlHGz5ils9vUmXdk2GxdeBWhCzCHvrlBUwqZuibamp+3BU8Yy7FJ +GJBQXMZDGQqW5PJGuZsezGLSUpbhr/1gcWYYrrMXMCAlrXJJ0VAAAAgQDTqVm7HOTahgAP +AWhp7CRu+NUASIe04kBiWZ8bCZIT2S/Qdpc0xyo04VuNnJ65TZXJ2C6GKuEgJw7DluQEbB +2rKZsWXZpooOP7EdeFPbDnfYUCV+06aAFVr/yzrV78ZiUOUuvaJ9eUoOVeTiuc16bTLhxb +moV51dFIWiF+frnAWwAAAAAB +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_b.pub b/testdata/keys/test_id_rsa_b.pub new file mode 100644 index 0000000..f9dcd1e --- /dev/null +++ b/testdata/keys/test_id_rsa_b.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFq82Pfsg7KjTU5LN4jikxITDQhCWB3TFxQdXTgYtKt40+gv88hZkemM1MYTzR30bUX/zcRsioUSwr3u7/2La7ti+BoilsHjrEx4w+nxNGCCe8D3M6K5Xi8MPL2AqbXFqkPSEpX+psrs+qILfNhs1lWAsN7GLP0cTIxPynFNECwJnUlleN0hsn8N8bQCoUInZQGmHwIHq62H+3IPbv7Vko3J0Zrqqo4OqfeV5BA0By7ZP+2Jd9ZsLJ2efaiALcs6oTk0v95wVQ36wp605x9ePYg6zHzIZDfpA400RqeuiZF5jpiG7q3eb0+CysfMbU0BpfeHmCq15PFYqre8HKAJZ3 diff --git a/testdata/keys/test_id_rsa_c b/testdata/keys/test_id_rsa_c new file mode 100644 index 0000000..e2d62b1 --- /dev/null +++ b/testdata/keys/test_id_rsa_c @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAnVeMVX1Wf8x6l/u4EWhN4adodAlepYhVJKWDJPJV2nKhw5b/se5E +DLW1I90iEzX59Nv/rKDVIBsuySw4O+1AWKz1NP0W1+b2MdA3fnBPtZmjTWYZGCIYO/FKDB +L6k2n6xFQFmUrwEWl+l/CNVNQ8BSoarqJr98WK7Gv36oKV6MMojVEtAjoTe7cmkbA/rX7v ++L0ZepnZUX69O5AW5QqVrmE6x8WoziHpTFwcjdLgdSNh9rLtIUUhxJ9wtd53ha3S0JIYK8 +5GYqiKqrky2iEyUx1jfQU4vVTOdBVmg/DpX1UQhOKdPx7KWSVFONGFxdqjWbgGxSO46gQt +Wo1vSO8XVQAAA7iBGsIhgRrCIQAAAAdzc2gtcnNhAAABAQCdV4xVfVZ/zHqX+7gRaE3hp2 +h0CV6liFUkpYMk8lXacqHDlv+x7kQMtbUj3SITNfn02/+soNUgGy7JLDg77UBYrPU0/RbX +5vYx0Dd+cE+1maNNZhkYIhg78UoMEvqTafrEVAWZSvARaX6X8I1U1DwFKhquomv3xYrsa/ +fqgpXowyiNUS0COhN7tyaRsD+tfu/4vRl6mdlRfr07kBblCpWuYTrHxajOIelMXByN0uB1 +I2H2su0hRSHEn3C13neFrdLQkhgrzkZiqIqquTLaITJTHWN9BTi9VM50FWaD8OlfVRCE4p +0/HspZJUU40YXF2qNZuAbFI7jqBC1ajW9I7xdVAAAAAwEAAQAAAQBga+BiG3dAxixyqx23 +/TWNDaxjP8KyqoOS+6MMpz2Zqxoa5JQ5lAw7xbcGDWPuZeuR9KgQQFebX00il2i2wMPoE8 +YI/Phcwvsot7LqVyVh4o9CxUink21KfqmkEJVidVXprpvnvxazdPTRY+cYjx9hAtZrIroL +kxF5pQi+u9vmUYyihsVfCBkgrdJ4EAdgBNms67QSU9DRQHC3JMTgaJLFLQ9pV9kIpej6Nt +Mfwzy5PdWlfpqe3m+5WQ17zzc06eQl1jJ26qLEK3nW6z3DXj7teQo9lrbsukfaAVCk5vip +FY+qLL22POziGCiRYqMcZGoBsEclOxbAko/R7ezrFnDZAAAAgCyOb+sqV1mICyfv6Fw0iY +QqfHXIGrOL6jj0yCvfDsBqZlRKrSYGjMRdDDZtdZa2xqRlHWs59tUR9xYzItfK7eldS9zi +Wd/HTpczy9JDurVF6tXYuwEaBNcHfx5HAyqsuCnFJurso+IE6ohDBm97QFbTDuxhIVEQoV +pkRI1R542VAAAAgQDNPwlxMZIUZBUyiVjU1rkemRk/jmYpTABCjHS69cFRFjhmH1qUZCGf +jg3gVzkcCIIdFDE8ZYqLrlK+pMzSxonpyntEtPqkXx23bEh885zEdazZYHTfIgmnK3CESL +N/QNoPTBFrmgNGp0QedLcZsRPdQ13uudnl5Cqax3DrBOdCCwAAAIEAxD/3szYPZb/+iY8W +rAVkXww0t6ekUysUu1ImKCf0yixix/H35VTIJYmHU52UZamj3TTijT7GfU4/Sq9Fj6j2v6 +68Miop6nUqvZt/GV9BzeZ3NW7SsHO6SkAvOZGwZRVGViP4kwlHtLSwtBJlYSLxY5e+/fBF +6SF0WRbRAK7xSB8AAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_c.pub b/testdata/keys/test_id_rsa_c.pub new file mode 100644 index 0000000..4ca49b3 --- /dev/null +++ b/testdata/keys/test_id_rsa_c.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCdV4xVfVZ/zHqX+7gRaE3hp2h0CV6liFUkpYMk8lXacqHDlv+x7kQMtbUj3SITNfn02/+soNUgGy7JLDg77UBYrPU0/RbX5vYx0Dd+cE+1maNNZhkYIhg78UoMEvqTafrEVAWZSvARaX6X8I1U1DwFKhquomv3xYrsa/fqgpXowyiNUS0COhN7tyaRsD+tfu/4vRl6mdlRfr07kBblCpWuYTrHxajOIelMXByN0uB1I2H2su0hRSHEn3C13neFrdLQkhgrzkZiqIqquTLaITJTHWN9BTi9VM50FWaD8OlfVRCE4p0/HspZJUU40YXF2qNZuAbFI7jqBC1ajW9I7xdV diff --git a/testdata/keys/test_id_rsa_d b/testdata/keys/test_id_rsa_d new file mode 100644 index 0000000..be41422 --- /dev/null +++ b/testdata/keys/test_id_rsa_d @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAuB6SxdHSvZZ2krjEosHlViPq4XIJV+H797EzjockyIt+5SyCkk0W +RGcpUar5Ks/GROOD8tCtt1H8A27eAChVgqAjp71r8ffWXyY6gqwNrKUqpuwqp9E5NdUmmg +ebSM09Gm7hQGViPcBE6n+Et15GdFscllnUFB1fo3Ri/QW721oYAKTnVgL8a4uHcSf0hXUo +5KW0v2ZBAXgyVcR/jCYQeJVzicGL8QT5aU1h/cf7rwIIn9vL3lmVhUuEKnFTKd3HEqwIhx +q8XvrDrK9qP3Yy+Z9w3HESoQLTowROg/3cNCTidFFLsEY1ZlDhJ0NvARRfut8zCxPWwAYv +mZbNw40RjwAAA7jQRdvG0EXbxgAAAAdzc2gtcnNhAAABAQC4HpLF0dK9lnaSuMSiweVWI+ +rhcglX4fv3sTOOhyTIi37lLIKSTRZEZylRqvkqz8ZE44Py0K23UfwDbt4AKFWCoCOnvWvx +99ZfJjqCrA2spSqm7Cqn0Tk11SaaB5tIzT0abuFAZWI9wETqf4S3XkZ0WxyWWdQUHV+jdG +L9BbvbWhgApOdWAvxri4dxJ/SFdSjkpbS/ZkEBeDJVxH+MJhB4lXOJwYvxBPlpTWH9x/uv +Agif28veWZWFS4QqcVMp3ccSrAiHGrxe+sOsr2o/djL5n3DccRKhAtOjBE6D/dw0JOJ0UU +uwRjVmUOEnQ28BFF+63zMLE9bABi+Zls3DjRGPAAAAAwEAAQAAAQA/+b+eJQ5I+iMxUCpj +M2RwzgIrW+iLNObMZjI38BWD6ZSqJREprMdz9TSSQApcKd4GyddrY78416/tsXRKMbmkre +g1vMfpR+ihHzNtfEM0Hh/36E/aCAUNA4dqNgJrBYWZzbVPHutH8asAoFaJMKTigVYpmATt +CFfW//X1XSytRsXuTwbKjPa52CyC7IzSJwYoiPKRshaIKKqTGVj+deLJIWNVw3mGPmdOAr +xV0NDS/cm0AdzKvEnyDR5h34SFNh9o4rAl8ekVITXizuV1IkPuqqPNjH4s5MAH4iUa4hfU +TgvjczndVwIrpfYTMn1CFP2LN99x2Clp8x0ZGgK3SNoBAAAAgBs7Y+07c4Hh/o+zBpcIsY +na1yVx6v/5lnXe2FylUi4PnT/qwKIA5egxB1hnkijLncaYc4wvyQXSRSXzqVCMmpvJetFh +Jy9OmU6cMSH5lRw6ZXezFUu52Z4JfIgiO5QxewGcB+H+b8FUGpXQc8DNvS9HC46q/JK6ix +uohOP+m2rkAAAAgQDeBXIgCQtDiXsbLJ8ifebpVw6ogK2rNKg4rHIVpv7iajzpbzydCEyP +5gexj9jq3MTdBP56sgRAcPk6SfpfbngpMSWSXUdp8JhzWSMXu2t+fdGk1Y3c1wR1ynY5gS +IreYTp4DpVELt/iIe4SzTdmPI1ShU/LEAXChbityAbtCWJwQAAAIEA1Ewsj4XxuifSNNU6 +WuFVw/zCfxt1iTqWS9HWqXs44hJvmkGnhXDh/Vx8v3zCm67deL++JUc2NQlFG7Teqkq5P9 +HxYfdCRIwJbJDUff/AIwxYcvG0W2lH1UXBsZOSlnDm7XRlxtD/C/6FSMYEOGjfZKvQtnhw +yKKuyFzrkXG1T08AAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_d.pub b/testdata/keys/test_id_rsa_d.pub new file mode 100644 index 0000000..6e0b3e5 --- /dev/null +++ b/testdata/keys/test_id_rsa_d.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4HpLF0dK9lnaSuMSiweVWI+rhcglX4fv3sTOOhyTIi37lLIKSTRZEZylRqvkqz8ZE44Py0K23UfwDbt4AKFWCoCOnvWvx99ZfJjqCrA2spSqm7Cqn0Tk11SaaB5tIzT0abuFAZWI9wETqf4S3XkZ0WxyWWdQUHV+jdGL9BbvbWhgApOdWAvxri4dxJ/SFdSjkpbS/ZkEBeDJVxH+MJhB4lXOJwYvxBPlpTWH9x/uvAgif28veWZWFS4QqcVMp3ccSrAiHGrxe+sOsr2o/djL5n3DccRKhAtOjBE6D/dw0JOJ0UUuwRjVmUOEnQ28BFF+63zMLE9bABi+Zls3DjRGP diff --git a/testdata/keys/test_id_rsa_e b/testdata/keys/test_id_rsa_e new file mode 100644 index 0000000..a5a3a7a --- /dev/null +++ b/testdata/keys/test_id_rsa_e @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA16PmxlDP0/hiHqBYcBNkGMkTIJBJfjaaZAE3307H5fq5fGkvXNE7 +NKgXgQUBMoUbyJdK8nKx6cyR6m49rmbwxiJL+hV3N9+91f270Ey7HsgA/UJi+SMpPOH+Z3 +6yx+kdAkM2K0C1aS4e5HsDlSsL7FIST6mXdTfED/cr61/rLLdVcaIUHWlL3rvcpD8rSciM +hRPknsAf7lHmKGfw1k8QS4q3OtjP1lBCTLyZRAAZMz3ZpFNCdYdL7gTyJoAYjpMfSHcw3l +S6jHmEXWXB+X/e0AD9toXtw2j2on+pTvjjqzyPqjXl9pyvaiohaLJvAVx4u2bAnwwKXhKG +o9IBXExyNwAAA7gSKTPlEikz5QAAAAdzc2gtcnNhAAABAQDXo+bGUM/T+GIeoFhwE2QYyR +MgkEl+NppkATffTsfl+rl8aS9c0Ts0qBeBBQEyhRvIl0rycrHpzJHqbj2uZvDGIkv6FXc3 +373V/bvQTLseyAD9QmL5Iyk84f5nfrLH6R0CQzYrQLVpLh7kewOVKwvsUhJPqZd1N8QP9y +vrX+sst1VxohQdaUveu9ykPytJyIyFE+SewB/uUeYoZ/DWTxBLirc62M/WUEJMvJlEABkz +PdmkU0J1h0vuBPImgBiOkx9IdzDeVLqMeYRdZcH5f97QAP22he3DaPaif6lO+OOrPI+qNe +X2nK9qKiFosm8BXHi7ZsCfDApeEoaj0gFcTHI3AAAAAwEAAQAAAQBvDzn23336JM2cCmch +6cq+vSh9t/Ix77taUApPl0p61sObIK1E93mvp/ba+xHfzBBAIjN2ZL3etP5dp8NaxCezU8 +yfk1igTTp/MLv/DtW8h7OCvCMvPv2pHpehA+7d6OCI3sbSRfRopFlCwVxaAh6U6rwFjUm1 +FDCFLH7RLch4cU06G4s1tbgR1Jq5KUxuDJpXdWuYMAemJraSDvzZpF8w2nwpxhuMsny2h9 +Cu/8VQMXWc+Lk89N96+rkXS1MEJLOAJvh91dzWcP5FCy4QIIjO2lHT1VYvMTghnETvwk76 +Zi6TEaF5uEPoZgEMvHbQLaGT+h20E/kl0k+Eiyt3BriBAAAAgQD2Y7fxLxbN9mhmamh3JX +qoG5aZ/TpjbVeEsJUzC0Rj+qKibQuSHxGaGAe+TKGEDTDu/AA3BpMaiOb/M/5whLDTIf+8 +4UGYtg94CaH8IDMi/FwyClFX2sHEfoVrcOtMqwxhF+rOWEjR15bCyn+zAxstSVAuu/ZW/m +B22/ViV2VqVQAAAIEA+Y3i2gHsBpPVH7lPCAvOypMtFBKkXhAEQIugJAEfHdPdCyXJawZz +jK14ZeA0y1j8xbma/zIvvNZSKM2G6jK1ypCWVjn3Uv8Rnk8P+toJzzR/EmYBnRTaFjjvDw +cRd6XJUSshTpsmwE/PK7yz7ZNJSUk6Nid2VIfhODofW0DStvcAAACBAN01xILPghx71sx3 +vouvjazjzccbiM0Rvw15+I3Wr/W0UawFBkCDk6zS74iBLkHBaqpGyw46HF+q8Uo5Mt3Unq +XMPTqBEFktauWQDqBwhwkct1yxt/0BanQNwkbtSEsXcH278c/nm/cu2W2P64Rzij3CzLaI +Gzf+/HhTdHDNSQ7BAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_e.pub b/testdata/keys/test_id_rsa_e.pub new file mode 100644 index 0000000..009d33e --- /dev/null +++ b/testdata/keys/test_id_rsa_e.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDXo+bGUM/T+GIeoFhwE2QYyRMgkEl+NppkATffTsfl+rl8aS9c0Ts0qBeBBQEyhRvIl0rycrHpzJHqbj2uZvDGIkv6FXc3373V/bvQTLseyAD9QmL5Iyk84f5nfrLH6R0CQzYrQLVpLh7kewOVKwvsUhJPqZd1N8QP9yvrX+sst1VxohQdaUveu9ykPytJyIyFE+SewB/uUeYoZ/DWTxBLirc62M/WUEJMvJlEABkzPdmkU0J1h0vuBPImgBiOkx9IdzDeVLqMeYRdZcH5f97QAP22he3DaPaif6lO+OOrPI+qNeX2nK9qKiFosm8BXHi7ZsCfDApeEoaj0gFcTHI3 diff --git a/testdata/keys/test_id_rsa_f b/testdata/keys/test_id_rsa_f new file mode 100644 index 0000000..a54dd42 --- /dev/null +++ b/testdata/keys/test_id_rsa_f @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAyh6WpteVZSCwFZqwSWf3gQvFzdVP8XVocNcGa6JHNu8WXLojs4YJ +b7+xiD9iq+sJZMRNwRKUTHOGCXg/FIvnGGBfKVCDpMeIrY0PADscDWFZSwCrw5wmlgnBGv +qG3HTeJGt09Kpb4j0ARE2jzoA+SVEZ9EsMRpEz2Z/B5DLodblv4hPlw0gZUtqiuNtGLJa6 +F/Bqca7q57YKjhcayL+lzrAO4R4O5qrRVey20QVO1VYo9/XaLIVQohRNSWxbeHFN7ILL85 +2r4d2g80VX3wCgHa6aJxsaZXHfhRZC0eSEEfaMsU5hpvOgAdiNXiVoKUE1/Vxe0hHUuScZ +fQjyeQbkqQAAA7jwtfko8LX5KAAAAAdzc2gtcnNhAAABAQDKHpam15VlILAVmrBJZ/eBC8 +XN1U/xdWhw1wZrokc27xZcuiOzhglvv7GIP2Kr6wlkxE3BEpRMc4YJeD8Ui+cYYF8pUIOk +x4itjQ8AOxwNYVlLAKvDnCaWCcEa+obcdN4ka3T0qlviPQBETaPOgD5JURn0SwxGkTPZn8 +HkMuh1uW/iE+XDSBlS2qK420YslroX8GpxrurntgqOFxrIv6XOsA7hHg7mqtFV7LbRBU7V +Vij39doshVCiFE1JbFt4cU3sgsvznavh3aDzRVffAKAdrponGxplcd+FFkLR5IQR9oyxTm +Gm86AB2I1eJWgpQTX9XF7SEdS5Jxl9CPJ5BuSpAAAAAwEAAQAAAQBHJ72hvwO5NmjHMk4d +iZx4scxRSmd6efrqvshj3B12GXWl72HiMa3gyXuWm7oBDB2ie/ljn0bWI1VswAh1E0uoKU +VXJiEyQYy8jIxbaHPXwg3Nec7+blqnN4fkF46izgHmkG38NH1kiFUvtqWUbVGAPi0E8+a+ +cR/v3W5FCCcOSjoJ7u9Zo1pp1LfQzMEewtK4cFgnAvNtCHhHRLH+R7aKVu/zlO4IWB0j3Y +BY8TZYlRSrOXQyyLE6u+0CJfX6KLb8GrYiHxgEgXrt+0Nl1GWCyHDu/IKa9S8n6edAElTn +bSeV94VSkf4vB7ikQpLYc3my/j5vG9SodxBNnKCH/cKBAAAAgCS/s5NZQFBfdRBbFSg4Hm +FzcfnB2YBuOt8VBS0jPH6OfqXsJBMaJl0c36ESuuORDS50N8oXTjhbOkUL172U6qAur7rC +VKpWy2QoqajGp1Ti5aWDPA+o4HiKWOqqHK7DW5hG2C8/1OCQtyJmDUuL5wVepWGfl4cGVD +ivUowcF5PVAAAAgQDnV8NkOIaUyhv3Cz5wqn35jnF/jpkT2mc6tk8UB4T0hXMuobR57Ku9 +NKFfKyujpBAjjCaxVg67WVsKZP2fvBVJ8q641XM8JzxVPEO4magw4MTxttzEUXoASg7hCa +FXjGAOPpnGCahHr0O2HUO8RWpPApNWOO88HZyBOdWwoTFxpwAAAIEA36l13LKrouxZdaXA +JDRLVQF5DWyMfhvpNIGI5mk+XgBM6shpkGQXhF/YQWKMc1bIiYA/ArXU/DbTGE5iv6Pf6J +UmPy5DWxhwTax2mZPnwQAK0KfW0WEbnv8ZrUzFmqkSPoCQFrWI8KIMxE1cnym3dJPoFfFB +FvZOq2JNX/jfoS8AAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_f.pub b/testdata/keys/test_id_rsa_f.pub new file mode 100644 index 0000000..4bb6cc9 --- /dev/null +++ b/testdata/keys/test_id_rsa_f.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKHpam15VlILAVmrBJZ/eBC8XN1U/xdWhw1wZrokc27xZcuiOzhglvv7GIP2Kr6wlkxE3BEpRMc4YJeD8Ui+cYYF8pUIOkx4itjQ8AOxwNYVlLAKvDnCaWCcEa+obcdN4ka3T0qlviPQBETaPOgD5JURn0SwxGkTPZn8HkMuh1uW/iE+XDSBlS2qK420YslroX8GpxrurntgqOFxrIv6XOsA7hHg7mqtFV7LbRBU7VVij39doshVCiFE1JbFt4cU3sgsvznavh3aDzRVffAKAdrponGxplcd+FFkLR5IQR9oyxTmGm86AB2I1eJWgpQTX9XF7SEdS5Jxl9CPJ5BuSp diff --git a/testdata/keys/test_id_rsa_g b/testdata/keys/test_id_rsa_g new file mode 100644 index 0000000..d66b195 --- /dev/null +++ b/testdata/keys/test_id_rsa_g @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAocRUQ+20toWiB8Ut1A90jTbz5Pi8wlX8iGp6jtoOPyILoWgnZ6Pu +BVQcUSgGWLTOZmTVyOsQprho0K9B155z7vlRxFlfCt2sjYA6TPGcWYEbLBRXw/m6yQBIsy +lY3YQcjodbHhNmgWIk/pjkhOd3Qbft770Mm7y1xY3LoeB+P3WQCGJtsmJWi+ppFhtVRBPi +KVrcNWbKUVaVhtgdONEYX6lv2eeEYA4VPiFAqPTsy/s3NZQXxrdz0vhIf899yFRcbPdauJ +L38OnuXDGdL2RAl7a8L2nGSHNH1TO+D4RUv9et7qiY2LsYNjDa+UpNlEJtLiomaDnRpuFN +fK8RSgVp/QAAA7jCU/D3wlPw9wAAAAdzc2gtcnNhAAABAQChxFRD7bS2haIHxS3UD3SNNv +Pk+LzCVfyIanqO2g4/IguhaCdno+4FVBxRKAZYtM5mZNXI6xCmuGjQr0HXnnPu+VHEWV8K +3ayNgDpM8ZxZgRssFFfD+brJAEizKVjdhByOh1seE2aBYiT+mOSE53dBt+3vvQybvLXFjc +uh4H4/dZAIYm2yYlaL6mkWG1VEE+IpWtw1ZspRVpWG2B040RhfqW/Z54RgDhU+IUCo9OzL ++zc1lBfGt3PS+Eh/z33IVFxs91q4kvfw6e5cMZ0vZECXtrwvacZIc0fVM74PhFS/163uqJ +jYuxg2MNr5Sk2UQm0uKiZoOdGm4U18rxFKBWn9AAAAAwEAAQAAAQEAitQnTTTPhmUhLXH3 +Zqrs59Rlc/hUZqPM2nYnQDm7SEs7mAX+618wgrowoyWqbK6fBBk9r/Sskahpq878vsNp04 +l8tvnMK1MbJ0UIw8gG2nmGjEnZGINV3fK0ca3mahVbPKbFeAZGTnugLC+izrynCGuFc93W +2EEX7rdGL2Kudl5c1C39KCspKRHXhJR6xqrd7Kn91EN5D6eiqMtYkIqwsulX4BD2QuAaWw +a7GG+e3jT4ANnvZgKyAqZsTZUT9zkubqlwQItM7WkR7lcwHrkeRoyAJWV0LQbMGmWCHu7r +twdS67Q8CA1Mn9lSZa6AJPCZ04vC7yGm4xjXBm7jAWTvAQAAAIAJL0KAa47dFhrYG9MEDU +JKcZmEUVkllylnM5TNy5VmMYC6VctdD4TX/HuxF0bgU0Ja3c8BNjqvNaIXn+fOXpEJa6GD +LRmz4mft0wtc2BUgQimvOQTBkofvATx4HDRDn4cxbJQER8+Vics0T0A4nulyAR8yUg5E0M +K312MBTsN/pQAAAIEA1UlBBXmooTqMJavSFr/S+pRJ3EiYkB+z76yZAeRQ8icJ3EUaPhmP +Pz8W27jLBqzJZo8gQTyIT3c0iCXNMNB/g8mmClX8uD9MN4tLTrds+YSAHpX/YG692OLXy9 +RGyo43X18xmW+8HJSkcDYZmXTqD267ri7/hznl790Rx11T0BEAAACBAMIpyfSsjZHnxpVQ +j3zEpF0IqL0UQL1NUvdXpJnmwYEsSc/NM5DLe3r34WuWVtXfmbV1tIYx+54r1+3GyS4gBq +IXo4n7KJEcCqCyrARE1jXVymJ00EFj07lCfxBhEYqH6R+g9wte1K+5MJLk1OFnfmMCPeI5 +5g8NIGNJ+yeDY2ctAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_g.pub b/testdata/keys/test_id_rsa_g.pub new file mode 100644 index 0000000..dbbd62e --- /dev/null +++ b/testdata/keys/test_id_rsa_g.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQChxFRD7bS2haIHxS3UD3SNNvPk+LzCVfyIanqO2g4/IguhaCdno+4FVBxRKAZYtM5mZNXI6xCmuGjQr0HXnnPu+VHEWV8K3ayNgDpM8ZxZgRssFFfD+brJAEizKVjdhByOh1seE2aBYiT+mOSE53dBt+3vvQybvLXFjcuh4H4/dZAIYm2yYlaL6mkWG1VEE+IpWtw1ZspRVpWG2B040RhfqW/Z54RgDhU+IUCo9OzL+zc1lBfGt3PS+Eh/z33IVFxs91q4kvfw6e5cMZ0vZECXtrwvacZIc0fVM74PhFS/163uqJjYuxg2MNr5Sk2UQm0uKiZoOdGm4U18rxFKBWn9 diff --git a/testdata/keys/test_id_rsa_h b/testdata/keys/test_id_rsa_h new file mode 100644 index 0000000..f17dee2 --- /dev/null +++ b/testdata/keys/test_id_rsa_h @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAtGiVO0jiiSPjgAYF/MBb4Ut+WQmWsdZafcuqz7zmKQcXpNc1kLVq +Ai0mnaUctTeDbn7rGADsntJaztqPypmJtS0ajfKpSRTxz7kINVfrd8aU272/RrXoGjba87 +OZQSJSfWqdWfRek7t6XI6jWYbJ76x1Yqm+/KVAP7AF4G3/dPDQ0eTKrddu3sP9r0KYERST +1+9KIznBZMl/HaOUr+lvvePxAanDh9B75W2e3udST6aMElz7ccvifuyaExGerLtR94Fawc +pBX4u/+gw9ajVXXjdQZNvjNB0u9V4ns2xEoCP2+H1ibGIjtBSAZ++g1kGoCkJJNRzgaRXE +Q33QvITdUwAAA7iN/zftjf837QAAAAdzc2gtcnNhAAABAQC0aJU7SOKJI+OABgX8wFvhS3 +5ZCZax1lp9y6rPvOYpBxek1zWQtWoCLSadpRy1N4NufusYAOye0lrO2o/KmYm1LRqN8qlJ +FPHPuQg1V+t3xpTbvb9GtegaNtrzs5lBIlJ9ap1Z9F6Tu3pcjqNZhsnvrHViqb78pUA/sA +Xgbf908NDR5Mqt127ew/2vQpgRFJPX70ojOcFkyX8do5Sv6W+94/EBqcOH0HvlbZ7e51JP +powSXPtxy+J+7JoTEZ6su1H3gVrBykFfi7/6DD1qNVdeN1Bk2+M0HS71XiezbESgI/b4fW +JsYiO0FIBn76DWQagKQkk1HOBpFcRDfdC8hN1TAAAAAwEAAQAAAQEAg8GJk4WUZKIASYSc +pM37jFutR+uX0gOBjTPMaPFFipsyamH+LdRAOpytAh8PC04iv/UTTzGFnix0w3Od5lIh31 +60cQzTE5unSpSQ6RxvzMcL+jvm0QaTH2oGmEsHVylw40WU65hr5iran3RQ2lZ1boQeKtNb +t3ocpSax9AmBsu3eKzqRiZXon0wTaeaWxAXJZsNluEIuH7Q+hh+EIVRzUTLqoW3tUkYzEQ ++SpV8Yo0c4G14Dg0dho8L3uTSwim57nwOfT0KV87ulidZetccYD04dpcs0uEA7cTBz0Dqi +9fCzMklflCv+6jiMJ+seOD9EWQnr4W4HeWrYzu789B9KqQAAAIBHW4aSgCT+3XE84FChZ5 +QyKzMMooTBIhTD9XhwWzYT5nTHwfDFSet228UEUWJh6pHhJauzxuJP2vOYzjOPYe7PaouA +Q6DLx9y9GpQIVa92B7BoiIuQE3hDxNaIUzq5gafMExOk0EhP7y1kKYiIT7/Cl2US/vRe9m +v1q6CS/wJxNAAAAIEA6V70OZW7LSjghEw3Y+ZClh2r2b9ZPqve3fslky7EwA954HPXI9zs +4Q6Btsa6iiMu2fqq5uqborMyPws8mbsNkNl7mjKpPU+veD4csMS5UCDS4dwFGa+W+aTowp +tErNkG+set90CBETzxBnjIBS56k45eXaMaVMBXh3WiRZVv5NcAAACBAMXm7Oz1/UFNi1uH ++HFTQhGZnH73h+zSt1iJRqmd+tc4U6IwbCrSKpmuzxcF+ZtCb12oDF7bIqMwJQfivMPIC4 +wP1jDSTBekN3pMFLrc5aMiH8x+bRMgxI2cr/enLH2+OcVCuDpTOGzL9aRjjghwRypDBcb6 +jH3lEtcnudDe/f/lAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_h.pub b/testdata/keys/test_id_rsa_h.pub new file mode 100644 index 0000000..5560a46 --- /dev/null +++ b/testdata/keys/test_id_rsa_h.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC0aJU7SOKJI+OABgX8wFvhS35ZCZax1lp9y6rPvOYpBxek1zWQtWoCLSadpRy1N4NufusYAOye0lrO2o/KmYm1LRqN8qlJFPHPuQg1V+t3xpTbvb9GtegaNtrzs5lBIlJ9ap1Z9F6Tu3pcjqNZhsnvrHViqb78pUA/sAXgbf908NDR5Mqt127ew/2vQpgRFJPX70ojOcFkyX8do5Sv6W+94/EBqcOH0HvlbZ7e51JPpowSXPtxy+J+7JoTEZ6su1H3gVrBykFfi7/6DD1qNVdeN1Bk2+M0HS71XiezbESgI/b4fWJsYiO0FIBn76DWQagKQkk1HOBpFcRDfdC8hN1T diff --git a/testdata/keys/test_id_rsa_i b/testdata/keys/test_id_rsa_i new file mode 100644 index 0000000..4cf49fd --- /dev/null +++ b/testdata/keys/test_id_rsa_i @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAvuHbQp2qPeNgWtyStNQCSX3PStbzEBO3TFBhU/wBpj+l4eatLo4X +uD1rKvDj/iBFu2qUb7Cvtq/NgvKtgIJzm0rFewdUwoHTlNtMOgGJbQWFRA/Of6GOHrsOTR +22u/9dKKT9C+i+55vKBHjUsIYIfSd76wLUUaU3D6kwoiHf7mQWNd+ysPlLpsdNfksO3Jtz +rav3LDuox6itRUvSOY+GPUvnUs1SpzQ7iPxeH/UvjOIdST+CgrtVvnpDchD0E/OOr+DLKz +wDbf02W+JV7eiOoOrB3auOq32wonJr5xFT26FoI7lDgyUHt4oynnIuriBmnjBPSn3o2lnr +MrgJpRgzNQAAA7g9DZw3PQ2cNwAAAAdzc2gtcnNhAAABAQC+4dtCnao942Ba3JK01AJJfc +9K1vMQE7dMUGFT/AGmP6Xh5q0ujhe4PWsq8OP+IEW7apRvsK+2r82C8q2AgnObSsV7B1TC +gdOU20w6AYltBYVED85/oY4euw5NHba7/10opP0L6L7nm8oEeNSwhgh9J3vrAtRRpTcPqT +CiId/uZBY137Kw+Uumx01+Sw7cm3Otq/csO6jHqK1FS9I5j4Y9S+dSzVKnNDuI/F4f9S+M +4h1JP4KCu1W+ekNyEPQT846v4MsrPANt/TZb4lXt6I6g6sHdq46rfbCicmvnEVPboWgjuU +ODJQe3ijKeci6uIGaeME9KfejaWesyuAmlGDM1AAAAAwEAAQAAAQBotGFeaDfTHN9k1+tN +b6IViAJmLgRERFxeaxUISYrqE629fZhvmhfewyn+8wZNBozZojvsDEJGdqbHdrC/dk9N1r +T+qGXRD//sfmKQyvkEKWwRio4eKpNXvgm5OVxcQVZrjh0UJEZbq3QkAdfBQLR1Wmw53GJ0 +qqjKWWdI+EXqcF9RXyULS4koM9FLV+KvZPJk1Xw0rDPEiP9oNS1m+KRctSXIgfew0edpJW +39JDjZXuk29rPNe3A9PigsR/Sy53WVJAh7oRonx93Mlc1rNzEfBWDz29AAfvHADOtCQHIC +qf5p2nleiGG56amWv016vZ5cBPkiGgRibhux72sRtBOBAAAAgQCf/Mq9z0ylrRR+D8Vz9g +OXzNKV9LvgiPLmLgwh3IiRW3Ku8ybP4zCi26Ydn+U7dkDBRsStJuaRQlGeyH+QWRhCs0HV +SyF859cFmjI6Q35wAxrnrBX99T0SlLlNoM25oHxa4eIhf2ARhKd5JM8UT2sNqPtktMllF6 +KyntKeinqTvAAAAIEA64wSqGvkkxVZsurldsWlWW4QZyHL9emu2EKdNU8m+/04JzLcjEo1 +eVMoLe513wTXRtrCOz38Lc9X7wM+VnJtMiRaWGuaCkUlaqONDpd+f1e/18ZtuZNukUDA65 +26HEozR76nb8XGYGcO4szALLDpKKJ1WuzhJz2M+PR1FC+qytUAAACBAM90793v741dmai9 +GrxgmN+QcPae2nwtm1kxVgVY1FEoO1OdSFQejzI/bBjCsnO0tx+uX84veyhbRhi9aU6+SU +eN+HJzrlKuVGX9kXlgyut8bCbj0Fvz+8PyCQoxxYls7Ug5id4Z3JVAv4lTtwiiBWD3GWFt +Iq5ldU4FWvFn5zbhAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_i.pub b/testdata/keys/test_id_rsa_i.pub new file mode 100644 index 0000000..5624aee --- /dev/null +++ b/testdata/keys/test_id_rsa_i.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+4dtCnao942Ba3JK01AJJfc9K1vMQE7dMUGFT/AGmP6Xh5q0ujhe4PWsq8OP+IEW7apRvsK+2r82C8q2AgnObSsV7B1TCgdOU20w6AYltBYVED85/oY4euw5NHba7/10opP0L6L7nm8oEeNSwhgh9J3vrAtRRpTcPqTCiId/uZBY137Kw+Uumx01+Sw7cm3Otq/csO6jHqK1FS9I5j4Y9S+dSzVKnNDuI/F4f9S+M4h1JP4KCu1W+ekNyEPQT846v4MsrPANt/TZb4lXt6I6g6sHdq46rfbCicmvnEVPboWgjuUODJQe3ijKeci6uIGaeME9KfejaWesyuAmlGDM1 diff --git a/testdata/keys/test_id_rsa_j b/testdata/keys/test_id_rsa_j new file mode 100644 index 0000000..bcd4917 --- /dev/null +++ b/testdata/keys/test_id_rsa_j @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAzH98WvudICsikzE7J0eRpo0VaJspUWnA+6v+xX45bbDRT3VH91dW +QeskvVYaKRUDHERHW4OET/KHNG8k9XNrBH/7D6t+srBCnDkiMOWLPZOPEIq/AEOyZDcfKj +j8Ustmx4T9c9+3CM4IIPrcZ8c34KxYEN539ec+uBDz7yevNIucwr2V7iRwRSiBjANZgtrG +sShOYLcXv88879t0lvxpxBQAyHEyQn7rBmqpDAjrSFqNCBGlyMqHCMbBjJMTOrSYYosimY +2iEN8DVc3tPtfZDfAhSjb14zjXHrV1K+lspMYGNnc0V7Z5cqMSL2CyMcoHLzkZUiKMxSr/ +IDBKDU8ZLwAAA7iD3rJZg96yWQAAAAdzc2gtcnNhAAABAQDMf3xa+50gKyKTMTsnR5GmjR +VomylRacD7q/7FfjltsNFPdUf3V1ZB6yS9VhopFQMcREdbg4RP8oc0byT1c2sEf/sPq36y +sEKcOSIw5Ys9k48Qir8AQ7JkNx8qOPxSy2bHhP1z37cIzggg+txnxzfgrFgQ3nf15z64EP +PvJ680i5zCvZXuJHBFKIGMA1mC2saxKE5gtxe/zzzv23SW/GnEFADIcTJCfusGaqkMCOtI +Wo0IEaXIyocIxsGMkxM6tJhiiyKZjaIQ3wNVze0+19kN8CFKNvXjONcetXUr6WykxgY2dz +RXtnlyoxIvYLIxygcvORlSIozFKv8gMEoNTxkvAAAAAwEAAQAAAQADN5I2QDUVl3ggxqp+ +kLKq4xdOIPrCbB58FxWX98dhgWGZlWe/bWFPAir+y4b31HvLCwvwVZ5UP6V+3qy/K8SoOn +cxpOoGV52EpaVzDXlmS5b5lKUylN4Ok4tq96CIkOiaBO6TzxYrDkq0ch3biBfJ8gwkn8tV +VAD5llKwFXi9I9nk9bUnwyF/O8Y1qAY25cARA2hH/4bbjGwBkXh0KhUj30hbhSAVkxIMDi +4xFL3fZMov05DFvqqQIcSSBBGSMjFKQonUXqiCufVY5ar0sNYb8vBPWKXylFsVQlqhm3Sz +2Gjatd8yPpRyoIjC4ovJniaQA6vbJACuIQMWnQUiDb+xAAAAgG21cnPiyz9rOxmkeUvz5K +R4otRFHYRs2Yne6OnWKVhMV2VVIhclVgT3cZRPB7rZGjAu0rnLu9KbebaonJjvL+8VmSHt +jw06itZBabJDKuw9BZwjHX+KUzfl3Z7qqTMGicmM0dmFwdmZeXjJCYyUk68uFVezlXwd8X +zlBqzF6F52AAAAgQD79ZKGMJ3vHImMWMjiqNFpCeU9Fpbw8tXbveqorHjC3BLjgy/YL/WY +7s5TduItqGGyRQnurKj5u1Qb9HGncym8uobTGff7IkCJpnx4jcVtYTrQOtzUt3h0vaNTAb +TaCYNmgUYV1n7t2OCQtZR20U7tyeY+p8RmAvfXWSMk6qCC+QAAAIEAz8cPORcXcuo6yCaB +wlrqFrKQ9h+2qKTtklyHwlKrDnWuH810GpTbWsBmZCrk87ijS1aVWO0Xyq7y2E07mGiATw +EzN68jNtokSsqgs2xX/rkci9ZAutBNAeKThryuqSCSJ79wtTIQY9IhAzvk5SzZaM/JkzN3 +RkKmEMdF0i8gX2cAAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_j.pub b/testdata/keys/test_id_rsa_j.pub new file mode 100644 index 0000000..4351c33 --- /dev/null +++ b/testdata/keys/test_id_rsa_j.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDMf3xa+50gKyKTMTsnR5GmjRVomylRacD7q/7FfjltsNFPdUf3V1ZB6yS9VhopFQMcREdbg4RP8oc0byT1c2sEf/sPq36ysEKcOSIw5Ys9k48Qir8AQ7JkNx8qOPxSy2bHhP1z37cIzggg+txnxzfgrFgQ3nf15z64EPPvJ680i5zCvZXuJHBFKIGMA1mC2saxKE5gtxe/zzzv23SW/GnEFADIcTJCfusGaqkMCOtIWo0IEaXIyocIxsGMkxM6tJhiiyKZjaIQ3wNVze0+19kN8CFKNvXjONcetXUr6WykxgY2dzRXtnlyoxIvYLIxygcvORlSIozFKv8gMEoNTxkv diff --git a/testdata/keys/test_id_rsa_k b/testdata/keys/test_id_rsa_k new file mode 100644 index 0000000..ae5e0ea --- /dev/null +++ b/testdata/keys/test_id_rsa_k @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAtPL5ElUDH2I3bo2DWxcDAnTAOwsCjZOGmhbn7GlVA5sY4VR8Fn78 +FTH84Nq7xZUJH89VtjKz+2mpwc3ktzuOmrNrfrh962EoEhTf08jizv9j8q7cERWAmH0gnb +g1NdKCTXrXeFcfdLs3EGhIENFcs7/Ts59Y09TT4LF0djBaDc93PKWvq70F5g/qcAvmkIPw +G5ye438uxp8QM2iIYcSArNAyDbuJgeK35zXiUqjt7oZigoSR7FRsrJIgN2SCG4w/skvWSe +t2HTbYXbZdBSsmECzrVnCoSmNtgJ+1dNI4NfW+tYT0yJzU81/zKjY+xpO3Nh3AH1dzHcBE +g7kGg1a66QAAA7jiibxm4om8ZgAAAAdzc2gtcnNhAAABAQC08vkSVQMfYjdujYNbFwMCdM +A7CwKNk4aaFufsaVUDmxjhVHwWfvwVMfzg2rvFlQkfz1W2MrP7aanBzeS3O46as2t+uH3r +YSgSFN/TyOLO/2PyrtwRFYCYfSCduDU10oJNetd4Vx90uzcQaEgQ0Vyzv9Ozn1jT1NPgsX +R2MFoNz3c8pa+rvQXmD+pwC+aQg/AbnJ7jfy7GnxAzaIhhxICs0DINu4mB4rfnNeJSqO3u +hmKChJHsVGyskiA3ZIIbjD+yS9ZJ63YdNthdtl0FKyYQLOtWcKhKY22An7V00jg19b61hP +TInNTzX/MqNj7Gk7c2HcAfV3MdwESDuQaDVrrpAAAAAwEAAQAAAQASBta7dc6u1LkTAXQh +ogRBvWHdP/aMJjNVrIyEZ+X5Sv1KjYEjOJlify1jNUdZosRa7+vpD9oQZPFthlaPzpYhYV +iHMlgRGAfG7V9zGONHnffWGIeCCDNcfbK/I7fhWcxzbZaDvaI7nd8Zelobms/ydFzh2zEd +QWmuS99/WsfN8j9LcjS4PVk+EVBe60tKAneVuB7WdEpp8aG7dCfmoIFElQu+ZG+/M4zUbN +RcvpxpVqVzFIX1s//pJPKWCUAQuCDjnwE2ispA6NICn7Ozm0XL/UGGeElnWmxmP4mFRbDn +BAZ3q5zoBvzNMqNV5+hihSxZeKx5KBjvCET2VMaTZ/zVAAAAgGEqr4dS4piTuFlCCoMMrl +VEzwfsVMF8het3GjMarQx9F6cOyQTVCpbh0WqJKo5mo8Rb9MuyqzHyORQByxtxSWjmFNFl +0O7QRXfVefhBxft5FQ7Ni+58g9igb7W85mIclsqmZxN5/Z4nmB94CrcZZDlAMI2euPCttB +9U4Ti905fQAAAAgQDjLCwPYs6bAxnGQQTRUfq5+QdYotzQOUkk7G3ctWrtGVc8/8v9z7uQ +nM2oCPGZBqvhf5kBbUvHM1WIvA5vfoCZ5TyAgBxU/04j93eRdM/U7HIVmtgFkosTEDW4Ac +8dD3hi/dfXIlNDhviMzYQebNzjI2TtgPHJ8A9QN3SDEkwZHwAAAIEAy+k04pIsHVpLP5UG +co0pM+6aRXwmRp//ZV9zTgyN9vgchvnGXrySMdbKiOF/c6XmjLTk26b9cpLip8EiyE/zoK +KZmE5CX+c/cIyaiHbLAm97GVdOJGYPfeXFZZj/fe5YIRxnGpr1vpXy7Rq8Tzp7PBw/vtMS +km6JPC9XVnX/wvcAAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_k.pub b/testdata/keys/test_id_rsa_k.pub new file mode 100644 index 0000000..45befb0 --- /dev/null +++ b/testdata/keys/test_id_rsa_k.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC08vkSVQMfYjdujYNbFwMCdMA7CwKNk4aaFufsaVUDmxjhVHwWfvwVMfzg2rvFlQkfz1W2MrP7aanBzeS3O46as2t+uH3rYSgSFN/TyOLO/2PyrtwRFYCYfSCduDU10oJNetd4Vx90uzcQaEgQ0Vyzv9Ozn1jT1NPgsXR2MFoNz3c8pa+rvQXmD+pwC+aQg/AbnJ7jfy7GnxAzaIhhxICs0DINu4mB4rfnNeJSqO3uhmKChJHsVGyskiA3ZIIbjD+yS9ZJ63YdNthdtl0FKyYQLOtWcKhKY22An7V00jg19b61hPTInNTzX/MqNj7Gk7c2HcAfV3MdwESDuQaDVrrp diff --git a/testdata/keys/test_id_rsa_l b/testdata/keys/test_id_rsa_l new file mode 100644 index 0000000..5e27bde --- /dev/null +++ b/testdata/keys/test_id_rsa_l @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAsaEk5eyw9KPzvqxUHrcbJ+Tou77e6i+SbcAFH+owJ9emsQ6cNLrZ +Oq7ksMhRortR4WWmRM4RNIu/uO8HqpEovy2mDr/I/LNORq9j9EdRDGb0C5firBwlWHhhVX +CdA49J/1SYcniz8da9j/xXfBnszLc2C83AdObigmbK5fr8tieu+Tk92Iuex9X/RZIgR6nP +FZsND9aXSXphxnZB5yGVOWJKk+WZLKTHxMSp0wfU2J4b6xSVRp+9Luiv3ikFmH4sAGfMlb +3Isg21zpTNo6obeA0CIPAsKlvyKFJPbsostCqtDDtmjUaWMiOcnbjOtp7zQYL6FNA0DIY+ +C+AEJkfifQAAA7gjtQHVI7UB1QAAAAdzc2gtcnNhAAABAQCxoSTl7LD0o/O+rFQetxsn5O +i7vt7qL5JtwAUf6jAn16axDpw0utk6ruSwyFGiu1HhZaZEzhE0i7+47weqkSi/LaYOv8j8 +s05Gr2P0R1EMZvQLl+KsHCVYeGFVcJ0Dj0n/VJhyeLPx1r2P/Fd8GezMtzYLzcB05uKCZs +rl+vy2J675OT3Yi57H1f9FkiBHqc8Vmw0P1pdJemHGdkHnIZU5YkqT5ZkspMfExKnTB9TY +nhvrFJVGn70u6K/eKQWYfiwAZ8yVvciyDbXOlM2jqht4DQIg8CwqW/IoUk9uyiy0Kq0MO2 +aNRpYyI5yduM62nvNBgvoU0DQMhj4L4AQmR+J9AAAAAwEAAQAAAQBOlk/f1cc4hSz6x/1z +lWlTTihVoA25qJBtNA5QX2ksx3E9Voxvm9ct6mDNgeL0T1HbmgBk4pKkH8wUAtTudsiDRK +v6TImcm6vfBAwB8GeASDBizLEBmnY/XQsbJPais4t2hrPnuVVLgsldYbRG+KVWsRwdPsuO +LPQ740STWWbjI5AZI2pFv4wpj44nFs8rlt9kC0O6JzXP7dRbPh+uVMPDed+KhRxpLNAESN +pFVVqGGKjD0gUxWBTypGv31KyeIg1L/5IfP7l7VXsrtrc0szeaYMmXK36tl+8k2EZF+VPK +OzVO5YN8zInnWVrnOOIOZjE4pwotinmnWUB81LR573vVAAAAgHXmgOQ8dK04kUc62kocsj +3RW4uckodHVK56Alqz7KLoWMJtsDvYVs3csRnwSJm4dKLHQm4LHDic1nbqrClbZ+DQCiVW +dTgfPPmSp6F9UbOkBN7zOVIZ+Nye+I55mPpie+DDOnWy0BxVtBwKbljF/5a3DF0rdZFKOZ +I2stof6lWpAAAAgQDoT+/a+x6ENxcd4U7SVesrhuaA7bqKeLmE9urTd3VdtZm1yiNGNFB6 +I6Rou1bgriVjioCB5mUJNwEUO+FqAAgX8iaQlADoUhXhpKmOvgy6MIgKgjpnt0VVu71Pdq +WBHrkjj/Y1ufV1Yfb+Bv+YEphPy0K6Y4rK7Z7mLMwfOJXQpwAAAIEAw73RklHpAyQNjySP +rIkuYVlEBb3fjaYIUGITvqLFi/aMJ5YTmL5z2RNSpVqwl9F7Opo1OXYhxFuOO/xmzk/Ibr +ZoB0fr9uf/nJCsUAELBqk1xTuMr9d2B475/rji3nYDC3m4YXZWW43iEHPivjxVrhfx6Nyk +jSdjqJYcvQGmVDsAAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_l.pub b/testdata/keys/test_id_rsa_l.pub new file mode 100644 index 0000000..3bfc216 --- /dev/null +++ b/testdata/keys/test_id_rsa_l.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCxoSTl7LD0o/O+rFQetxsn5Oi7vt7qL5JtwAUf6jAn16axDpw0utk6ruSwyFGiu1HhZaZEzhE0i7+47weqkSi/LaYOv8j8s05Gr2P0R1EMZvQLl+KsHCVYeGFVcJ0Dj0n/VJhyeLPx1r2P/Fd8GezMtzYLzcB05uKCZsrl+vy2J675OT3Yi57H1f9FkiBHqc8Vmw0P1pdJemHGdkHnIZU5YkqT5ZkspMfExKnTB9TYnhvrFJVGn70u6K/eKQWYfiwAZ8yVvciyDbXOlM2jqht4DQIg8CwqW/IoUk9uyiy0Kq0MO2aNRpYyI5yduM62nvNBgvoU0DQMhj4L4AQmR+J9 diff --git a/testdata/keys/test_id_rsa_m b/testdata/keys/test_id_rsa_m new file mode 100644 index 0000000..ccc7176 --- /dev/null +++ b/testdata/keys/test_id_rsa_m @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAw5Kx5XFCHX54PbBPd3H5sRwx04s244bjSPRDcd1V3M63wJvsowJY +EAFAQ7XZag2IEbvvZnclNPcniabp33G8s6cMCz9BpKInbfPoCjZiS9VQ25LlBop3N01yqX +j014GuXwHkq+AA7wg5kOFegHOb9fJxscBNjZILKISOdcXP9Ke1j+mzkKgtsJwKxrr3EqV1 +TxGWVuQi2U7B6R4MRmFQR0JG6uqYJCILaqOAUgpTx5f4f2CJJKiF64eWFlnTW/9WkJP0xe +FRGAraFx9uecfEgi/4KO2DcGvd3XJZ/LRNJfFqoqvliYCpTIhSxSSFhF7hsZFsb3y7N/B6 +yPeHUCOy9wAAA7jp50j+6edI/gAAAAdzc2gtcnNhAAABAQDDkrHlcUIdfng9sE93cfmxHD +HTizbjhuNI9ENx3VXczrfAm+yjAlgQAUBDtdlqDYgRu+9mdyU09yeJpunfcbyzpwwLP0Gk +oidt8+gKNmJL1VDbkuUGinc3TXKpePTXga5fAeSr4ADvCDmQ4V6Ac5v18nGxwE2NkgsohI +51xc/0p7WP6bOQqC2wnArGuvcSpXVPEZZW5CLZTsHpHgxGYVBHQkbq6pgkIgtqo4BSClPH +l/h/YIkkqIXrh5YWWdNb/1aQk/TF4VEYCtoXH255x8SCL/go7YNwa93dcln8tE0l8Wqiq+ +WJgKlMiFLFJIWEXuGxkWxvfLs38HrI94dQI7L3AAAAAwEAAQAAAQBbf1UVOCCrddDBY6w6 +AKvFzaAODXKXjjLlncm0Cs4x6NCVHx3dwan7iupLA1f9QulnCprdQF664eqwftOqAyc6ka +uT03tdj7OMwHDN3lWsjtcucxINNljCGczMNtetv3EZ5+PY+YrWSe3Mt3Oj+vJ7ZXsFoydl +VW88/I890FcTaVUw3bV1Eydtz4p5PKHh5WzjjrXAxUcEcTMZe2cHTvlWHkeSlkMU0zPMky +NAm4FB8D72AaRZbgEJ5MFSq8+XmfjAk5lHtu9N/YDfr+/2lLcQnSxS/CHw16RVE3zjaV+I +kZB46rQgrMnwJYJ9tz7RKLgd71Dx82MMvLghnNHI+AxRAAAAgQClAjLz5MDXkyC/p3VASc +V52U2faFIgtxBb++MCGDqUruvX0PGxRh/r5PNz4eNnTi4sW8HTDg66Bd8swOy+rUZoUHrx +15nVHPiEuXVGsBC+jfU50Jo0SceL1gsifvG19SXXmhEg7CfTc8UtAebguRBbSAr0UqaBBb +Si4LNF59xqkAAAAIEA74MSaMq/NlNcK8imuSQ3Wf1dbWfXPqnBbflk568wF6nMIxn+oz9l +K5+Tm74BHK04xT5Ehkl92ABe/y+r5hsslQF9uIcCK1DieV9el4reEWQSMvVsNOgDBtx6Fr +PXSTItf/+ar+FyOKE2yhlthw8HlsQ6yC9Fk2J26Z8zRuelxzkAAACBANEJSPUrN1ZsD8BT +viJGGrChjVdRoY/eKR0+C2FEk0MtH7n9LLvZkoM75V75SnMytMHNDncrqxr+34W2634pUs +yPDxKhNXApwcxwk5SgaA27CEeRIZCvY0tfDU20mfZNLYklTrxYZ3S6QHQ/Du71ZAo0p7cI +4aQwN73hLXeOuZuvAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_m.pub b/testdata/keys/test_id_rsa_m.pub new file mode 100644 index 0000000..b3331cc --- /dev/null +++ b/testdata/keys/test_id_rsa_m.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDkrHlcUIdfng9sE93cfmxHDHTizbjhuNI9ENx3VXczrfAm+yjAlgQAUBDtdlqDYgRu+9mdyU09yeJpunfcbyzpwwLP0Gkoidt8+gKNmJL1VDbkuUGinc3TXKpePTXga5fAeSr4ADvCDmQ4V6Ac5v18nGxwE2NkgsohI51xc/0p7WP6bOQqC2wnArGuvcSpXVPEZZW5CLZTsHpHgxGYVBHQkbq6pgkIgtqo4BSClPHl/h/YIkkqIXrh5YWWdNb/1aQk/TF4VEYCtoXH255x8SCL/go7YNwa93dcln8tE0l8Wqiq+WJgKlMiFLFJIWEXuGxkWxvfLs38HrI94dQI7L3 diff --git a/testdata/keys/test_id_rsa_n b/testdata/keys/test_id_rsa_n new file mode 100644 index 0000000..81053f4 --- /dev/null +++ b/testdata/keys/test_id_rsa_n @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAliGxRTdmW2YXtwYH74sdsGYar7OuHo1VxuwgWTutcVj/14oRmLyo +6ERpbMIWOQLqJuPcAjCDp0v3hDusY4E0kGS7aYlj7dndKZcWzTqgNh4+E98bLNDrNtB2Dy +0OtVOK3/Ir9GOmDFaZ8ESsha+VSU/Cqj3eFq+bRo3i3LpK9mNpu2zuCai0iDptJPW8mA/+ +OaUUksG128ZOGn+1lLvUb6tvDLNM++ObeU/iSAx6N4n+qJQNw2oUeDxyFes8Sf17aB7XyH +bWW77FbHeI/C71ZizPgSD1PA6uoFNCF0kKF7zkiquXuazlwyNp3Iiqzstr08oF2UOkLMXY +2SllVk9JYQAAA7juqlpy7qpacgAAAAdzc2gtcnNhAAABAQCWIbFFN2ZbZhe3Bgfvix2wZh +qvs64ejVXG7CBZO61xWP/XihGYvKjoRGlswhY5Auom49wCMIOnS/eEO6xjgTSQZLtpiWPt +2d0plxbNOqA2Hj4T3xss0Os20HYPLQ61U4rf8iv0Y6YMVpnwRKyFr5VJT8KqPd4Wr5tGje +Lcukr2Y2m7bO4JqLSIOm0k9byYD/45pRSSwbXbxk4af7WUu9Rvq28Ms0z745t5T+JIDHo3 +if6olA3DahR4PHIV6zxJ/XtoHtfIdtZbvsVsd4j8LvVmLM+BIPU8Dq6gU0IXSQoXvOSKq5 +e5rOXDI2nciKrOy2vTygXZQ6QsxdjZKWVWT0lhAAAAAwEAAQAAAQBajhQI9fONabUgNkkP +GdBcmBm8pRp/XyzAAak5aZ+iYNZgwez/Pbx77NfSO/d/h9yIfKQ1xeGDRXUUk5HVqxdziO +7qJ27ZBrI55eJhA92jBvbWsZoInDNkKrUrTjgy8yMvFTmh/YEorAvKOwH82P2o7K00VoDh +GicElYyjKtMQbdXVXDuIUOttdDxGhSgKj/hWt+U/oADsOjwzAIONnFgc75Ho8foyj8LLzB +/CXubQgsfgG42LyGnjuP6WdpB41wfS85DMp1j1rMjqiGZRbD0vWmGwqO7sy9pzrQ4YMh1q +VJ2DwbP5gk4rM9ldGupw2/g9w7IMovnLYkzfuLTLyONJAAAAgQCUWCpIV+qgrVnoWWMK5L +oxjCJ40jOuRSdEScCma9+e3GAcRNfcmzcEEGJ+DnAPeK32EJqFsTyy2qsfTfEXi/smOA/F +Ia6JlrMcKhOsJDU1Ed3heCA8sbGoVY0bbD8dvjQc/r2eyxk7gbf3f6I6s8mDpmBsxrrEt+ +E4SLB53hyGtAAAAIEAxTicD5EWX4HJIBJQvZgF4+xbp3vJZRbh0uLwn9qJHU+LhhEhnZhe +w1lxioPR1D1GSZMExokkkZOP06FqxP07YI4osMVz2m/XB2ST4QihpwXWO9sIRnFabZcAad +xSRVXQH027Z+CX2zBz8l035K82ywcSxN/tawEWIdtPaHF2RMsAAACBAMLgSpnSIhBw33YP +xdbWVVYPECA4um0op9zAf29eXiVTzlGbbPvyFXxIi569xk3azmWalRVDK4Fj0fiXxq/NF3 +s94PA1qq21pnJNxVEHx5ma8xwybekumWwHHKrJNBmG1jjBli8TcWR6MVWvmXbsFHwo3v4I +8APK155r0XzlsREDAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_n.pub b/testdata/keys/test_id_rsa_n.pub new file mode 100644 index 0000000..8a716b3 --- /dev/null +++ b/testdata/keys/test_id_rsa_n.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCWIbFFN2ZbZhe3Bgfvix2wZhqvs64ejVXG7CBZO61xWP/XihGYvKjoRGlswhY5Auom49wCMIOnS/eEO6xjgTSQZLtpiWPt2d0plxbNOqA2Hj4T3xss0Os20HYPLQ61U4rf8iv0Y6YMVpnwRKyFr5VJT8KqPd4Wr5tGjeLcukr2Y2m7bO4JqLSIOm0k9byYD/45pRSSwbXbxk4af7WUu9Rvq28Ms0z745t5T+JIDHo3if6olA3DahR4PHIV6zxJ/XtoHtfIdtZbvsVsd4j8LvVmLM+BIPU8Dq6gU0IXSQoXvOSKq5e5rOXDI2nciKrOy2vTygXZQ6QsxdjZKWVWT0lh diff --git a/testdata/keys/test_id_rsa_o b/testdata/keys/test_id_rsa_o new file mode 100644 index 0000000..d79b293 --- /dev/null +++ b/testdata/keys/test_id_rsa_o @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAsod6L/7Tjv13yIH049GApbNQiniSt4XGPoSzHacoFk21X6HD6JBf +/eV4gg10lgfcrU/5D2s8SNIf20/qOcWhXi2vechHZ4VPu9JNX4EHZ9SJMnPrLMZRrcy7rQ +WXPaSp/siJ5V59jPMnl8OUCgZiBIZauUdnbV9kyc1/mOP3v/N/y4a4NmoLAIkEif7rNTM0 +9fPnzme0d58WMbby8Q9k46+iwmWDVew86zd24vdVtQXOE5G+GsGNpILINuEStJRKbKmKG6 +rOvc1LdDfRMwJag39U/OeyRF0x+HS7a5KHfgrkLXwcwZcw0V5Z10CE2m8avYJMtIFDQmbE +y1NtbsTVewAAA7j++921/vvdtQAAAAdzc2gtcnNhAAABAQCyh3ov/tOO/XfIgfTj0YCls1 +CKeJK3hcY+hLMdpygWTbVfocPokF/95XiCDXSWB9ytT/kPazxI0h/bT+o5xaFeLa95yEdn +hU+70k1fgQdn1Ikyc+ssxlGtzLutBZc9pKn+yInlXn2M8yeXw5QKBmIEhlq5R2dtX2TJzX ++Y4/e/83/Lhrg2agsAiQSJ/us1MzT18+fOZ7R3nxYxtvLxD2Tjr6LCZYNV7DzrN3bi91W1 +Bc4Tkb4awY2kgsg24RK0lEpsqYobqs69zUt0N9EzAlqDf1T857JEXTH4dLtrkod+CuQtfB +zBlzDRXlnXQITabxq9gky0gUNCZsTLU21uxNV7AAAAAwEAAQAAAQEAjm6jvzLxGj2CmUeF +V1TH7GsZMMBtXVmvBiXcuSWpR9ILiBaJNDwrOWcgM7IEd8Yh+2u4auLFH9v0aoZhemIAEi +EkxGZTAXZMXK616CILBH1+T/BHiltWwvB6MtopBZcfwRPBYGt4g0FjLyNsMmtRPAbszrD3 +BT18Lbxztkj6WfrXOhd1MLLNssV9LWco/bvVYVCez2TRe0T8uPIj8Pxl7srFTjhZUh9wPA +bEjlLt7kimFFDNlSBOcM9D67mzkPaZcpOtfxShm+pENkZsOAzfOipo25tQjawd20VhAVWd +vBb0rbomVEuUmTKgDYrGv0BRAP3AnkQZeJ7dwmkogqfI8QAAAIEAyaOp9LfM1zy0MN1pkZ +Do9X/Ofggim/LSlf1XyIHZ8LZ/VP8cqHKyl8LIY9Iu7VjpoSL4kJ90r2qLSsGRJCyf1Ost ++5ehBFqDmn7IYi+cGYxkQjQvwpOZzh07sL+JhwLZlagTd+o6z1h63n/uumyD+fTTKU6U2V +EdVwFyaU5qc28AAACBAOFuLKCr2qcsHfL0nVrwpxNopPc4Nw2UwtMqRprCDGDknuqLTvxI +htbZyXZkLy16v0IHrR7PBfLcmbJEAXo+AWzDIPd7QaUcdKBxhTnNr783Xu1c4ZfDXnPMZ0 +C01CxDiBuDTPl75eUwJNdE5uvpf4a3V7kz016/s+n8FLQyx8ptAAAAgQDKvSCESzG3BHQT +7ebdOJZCuQBnq7SeTsTqwvoHk+Zv69RtjcpqDhZuj4Uq63+a0ik6JhVH2dlWDZgKhqufG+ +BrOUTjVz38KXW7HcApPWxsM32uYldg+WXbqOD41cq6hNI8GlJKtqMpKDca1M5mBWWD8FDl +NaCqHZTtd6iBVz+uhwAAAAAB +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_o.pub b/testdata/keys/test_id_rsa_o.pub new file mode 100644 index 0000000..7f9055a --- /dev/null +++ b/testdata/keys/test_id_rsa_o.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyh3ov/tOO/XfIgfTj0YCls1CKeJK3hcY+hLMdpygWTbVfocPokF/95XiCDXSWB9ytT/kPazxI0h/bT+o5xaFeLa95yEdnhU+70k1fgQdn1Ikyc+ssxlGtzLutBZc9pKn+yInlXn2M8yeXw5QKBmIEhlq5R2dtX2TJzX+Y4/e/83/Lhrg2agsAiQSJ/us1MzT18+fOZ7R3nxYxtvLxD2Tjr6LCZYNV7DzrN3bi91W1Bc4Tkb4awY2kgsg24RK0lEpsqYobqs69zUt0N9EzAlqDf1T857JEXTH4dLtrkod+CuQtfBzBlzDRXlnXQITabxq9gky0gUNCZsTLU21uxNV7 diff --git a/testdata/keys/test_id_rsa_p b/testdata/keys/test_id_rsa_p new file mode 100644 index 0000000..bcd0d2e --- /dev/null +++ b/testdata/keys/test_id_rsa_p @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEApfdnP8dje/rTr/HWMcaRWAgkdNf5bSPanlIoZoa4d3tH36vXYzmC +C9KSNVfoxeps+9mrazBLdm5ssTyDMKMWyPunq1Q3139BmON/5s5wotDSFBO3LFO2RGa00P +P90h9zbI+hlckn0iNlXXlHqXgtFCsE0/mlrz90zBC/W1owPAlq4ZTZTc691Yn6jP4UzwyJ +mxdq3HJGrNbuge7qXM1nvS7140aYMbKbwhs0AUGMNAp8B+Be7B9wGMXE9d1h2tCQH4gMwQ +TmpDP/h9DoiyJlgufip6IwLWDKt41Y72+6Kzi8leBQXtEfUP+sX8J4leFR3FJlRJJGjIFs +vBC7WED9AQAAA7ih3AUfodwFHwAAAAdzc2gtcnNhAAABAQCl92c/x2N7+tOv8dYxxpFYCC +R01/ltI9qeUihmhrh3e0ffq9djOYIL0pI1V+jF6mz72atrMEt2bmyxPIMwoxbI+6erVDfX +f0GY43/mznCi0NIUE7csU7ZEZrTQ8/3SH3Nsj6GVySfSI2VdeUepeC0UKwTT+aWvP3TMEL +9bWjA8CWrhlNlNzr3VifqM/hTPDImbF2rcckas1u6B7upczWe9LvXjRpgxspvCGzQBQYw0 +CnwH4F7sH3AYxcT13WHa0JAfiAzBBOakM/+H0OiLImWC5+KnojAtYMq3jVjvb7orOLyV4F +Be0R9Q/6xfwniV4VHcUmVEkkaMgWy8ELtYQP0BAAAAAwEAAQAAAQAYLKNDbP2Wr4BqHmW+ +tlexZO46fsL8HL7ZVMpBtNnlvXVIpTUoZNYGdtOJJZboTS82uAv5/vze8VKvtkq9qOPGvr +E7iXbg0s5xQKH+so9Bh/0xo3WI5WClMfigfwm3s5ESfRFiSfyc5T78kI/ZpYGbcHCxl6mE +j0+p8b6NxeNEuzZj4CKgkm5FKcW8e8AShnPOTn90swwFuFUz6trW+n5TSxC/qVZKGPhGZp +rXEzBlZxJ8X0nAqPl+jWGTtitxc7kjKNreHS3Sc7EasSLorQsVnQazR5hEZVgPdubYzl+B +W7s9Of1HsSS6kg44OaAlcjzwxb0YLYXjv9JdkjkuE5JJAAAAgGyDfd8O0b3fm4YLVDB377 +uKvLuFM1aeL+rdEmVmviIIXEW/uXngqZiGtV6w3K6fSMjVsaVVwqdwLIrnI7vDmzvJZlZl +MULymgNc1Q5SwcbSA11sb3AHMtt6FTCFqMOFm6LSQJ6pt6UZqB3yJKq2vOxxVXaOinQyw5 +Cp7nwbmYu8AAAAgQDOajxkihphkH7770SHRUSIrd+khppCMJnbJhasEvbscLkPngKKutVG +WBgHRpGIjszR6fht6s4vAmERG96E+KABOL+eTirr/MWZrdw8+fDU3opNfAmkltkcHQe0dn +/+NngMYqCT06ThdbbebbsvWzKvwFIuFUn6XvP9b9XZJb+ZKwAAAIEAzdW6pfuCnkBt35uz +UPoUNcpY0f7jUK4cELlUOim6cv2v4LoG6PQ6wYFtZHbyG1YW/ySri4dwaXzCWIkRLM9VHx +o4GU3J1jEt1Utw5k10L6b9nwfrsRRCzWwMXYVmlMGNPHvWfbEik3c76S867Tdmbhwkvp2K +Gx8UKbIe62oa1IMAAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_p.pub b/testdata/keys/test_id_rsa_p.pub new file mode 100644 index 0000000..042d519 --- /dev/null +++ b/testdata/keys/test_id_rsa_p.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl92c/x2N7+tOv8dYxxpFYCCR01/ltI9qeUihmhrh3e0ffq9djOYIL0pI1V+jF6mz72atrMEt2bmyxPIMwoxbI+6erVDfXf0GY43/mznCi0NIUE7csU7ZEZrTQ8/3SH3Nsj6GVySfSI2VdeUepeC0UKwTT+aWvP3TMEL9bWjA8CWrhlNlNzr3VifqM/hTPDImbF2rcckas1u6B7upczWe9LvXjRpgxspvCGzQBQYw0CnwH4F7sH3AYxcT13WHa0JAfiAzBBOakM/+H0OiLImWC5+KnojAtYMq3jVjvb7orOLyV4FBe0R9Q/6xfwniV4VHcUmVEkkaMgWy8ELtYQP0B diff --git a/testdata/keys/test_id_rsa_q b/testdata/keys/test_id_rsa_q new file mode 100644 index 0000000..bfb7c0f --- /dev/null +++ b/testdata/keys/test_id_rsa_q @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA6ERzsQw8p5L++UkyREUnpNnjQ2jcg7UKozYfKEC+ytUWEYhuKpNa +AhjGz9D6axMxuT0toDfldB0mQEbgFZ0WIoUD2u2NVPukhdLPIwzWMHX6T4KlemCt2DiHBS +eHE4cNPUfLkajdKZf0oZwdWF0ADQ70uUIesg7XB7XY42Qpn7gGGyIBR/O3C54NlDrD/osI +nejC48Qycn8as0cOAgAWHrKXk4NYuJFkm778O53geYmnSBAWvK77IY+ma0DUCXf2Y1FZ2f +mvSTr3pvqFEDx6RottsLZlr6wHQii3BxMts5iwBitPuYHKWsrPQkZgrUqMgK8QCBCq0beK +dqOGPiV29wAAA7g9jI0vPYyNLwAAAAdzc2gtcnNhAAABAQDoRHOxDDynkv75STJERSek2e +NDaNyDtQqjNh8oQL7K1RYRiG4qk1oCGMbP0PprEzG5PS2gN+V0HSZARuAVnRYihQPa7Y1U ++6SF0s8jDNYwdfpPgqV6YK3YOIcFJ4cThw09R8uRqN0pl/ShnB1YXQANDvS5Qh6yDtcHtd +jjZCmfuAYbIgFH87cLng2UOsP+iwid6MLjxDJyfxqzRw4CABYespeTg1i4kWSbvvw7neB5 +iadIEBa8rvshj6ZrQNQJd/ZjUVnZ+a9JOvem+oUQPHpGi22wtmWvrAdCKLcHEy2zmLAGK0 ++5gcpays9CRmCtSoyArxAIEKrRt4p2o4Y+JXb3AAAAAwEAAQAAAQEArY5w0IwvSpx5sDb0 +u7TdIb66piVWGYiPcVPAxmhS6Od+/QzwHRpMOKLg9GrYpGgMULmZPU5nQ3OAoKkVSrE91/ ++C2PWHFUiQMHtM0okGB+Iwb3dVRXO2k4nxwnU95uaUiwT55VbEJA9q+dPYza14m0lgrons +u+qA3T8R2SCARdpk/RRy+BkwctAybLa4UYcdIk2YVaEi/v/yg3BpOPzYPHLKK/ghjSqzbC +PDV34QWEu0STGYL7WfGArNnHwAIWMid3gVxBst8V1YB904s1MnqzVQfC7aaVJZO5mC4nUh +THRzEL3fpb6d2jzAX/O4/QRtT7vc2veYYxaNs7UIp43tgQAAAIBj5ZT0hC0jRD4QebmloQ +T4vvSNeQPstQa12//35xcwNpT3mesmBhvkSI3oXfEMV+X8VOKqmo4Nh/3CNPllG6UIFZI+ +ZGT9ynxtHz9AiLX4w0KIlPs+zLU1OixK1ZXw0Fb5b0Uorc7/VA60XUhi+g5eadCP3g4vC3 +YaasnbmgowGgAAAIEA/DD2AfFdqQt5XQsTkFbiDfeFlkXOApJqeF5oQNJgiakG4bYah7oo +6B3sjlJkItXhaNdeHN4kvlJLhl3Bz5gVysQTqXh4c1+uuRHhCZPSD0Jl6OK2Df7Fd5uZz2 +X2kdyRk1S0xd453gfOOQuqWOf7LdvAsgZMYqqkKFxfPHlVWMEAAACBAOvGdb92+bXiZ8t6 +rAYocHh/IogpS5OJ2hZ0fgvCFDDJhrosdZmvNCZeLa9L24Ox4uz8CtUg1PzfcNgBMUT/Dg +nWlnZr8dClCJtK4y+MGWVPngV6o/knjBBD34xXGCVKfaEPUxqZkvZEji1QHUcGnw1SuKKR +Svv/tQQWn9+f90W3AAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_q.pub b/testdata/keys/test_id_rsa_q.pub new file mode 100644 index 0000000..7dc8ba4 --- /dev/null +++ b/testdata/keys/test_id_rsa_q.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDoRHOxDDynkv75STJERSek2eNDaNyDtQqjNh8oQL7K1RYRiG4qk1oCGMbP0PprEzG5PS2gN+V0HSZARuAVnRYihQPa7Y1U+6SF0s8jDNYwdfpPgqV6YK3YOIcFJ4cThw09R8uRqN0pl/ShnB1YXQANDvS5Qh6yDtcHtdjjZCmfuAYbIgFH87cLng2UOsP+iwid6MLjxDJyfxqzRw4CABYespeTg1i4kWSbvvw7neB5iadIEBa8rvshj6ZrQNQJd/ZjUVnZ+a9JOvem+oUQPHpGi22wtmWvrAdCKLcHEy2zmLAGK0+5gcpays9CRmCtSoyArxAIEKrRt4p2o4Y+JXb3 diff --git a/testdata/keys/test_id_rsa_r b/testdata/keys/test_id_rsa_r new file mode 100644 index 0000000..de8e3f3 --- /dev/null +++ b/testdata/keys/test_id_rsa_r @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA3ukJ1X4S8zFa4yafY7UyVvMA9HElw4Ch2Ws+KyMad+IblXz4hmDJ +RkbKx/LIxcMWtw3otqjIMRXz3ZX+M+TQX1KOFUVlzx+1e3+vIsDUNorcIEnbPUHzbXDu2j +CoxgKkwrdaGvAKmrUCx7E2j1BQQgFo7sKecSJFTc1J3LMaetxjXJlvy3aMz/35ZduzWwDW +Y218DPyKqzCP6wg9pQVLCQeJySRglfCoVYRaL1yOX73fuN6XBoSSekrlAqUXLlslj79UOM +/u0cLkmyl1ba6VsN6zpw1fAEQQ1ALPYlcxcS6VP7QUD2BH2dDRQOJoT5wsv3q4zvkmLe86 +xIilVU3ATwAAA7hNtG9RTbRvUQAAAAdzc2gtcnNhAAABAQDe6QnVfhLzMVrjJp9jtTJW8w +D0cSXDgKHZaz4rIxp34huVfPiGYMlGRsrH8sjFwxa3Dei2qMgxFfPdlf4z5NBfUo4VRWXP +H7V7f68iwNQ2itwgSds9QfNtcO7aMKjGAqTCt1oa8AqatQLHsTaPUFBCAWjuwp5xIkVNzU +ncsxp63GNcmW/LdozP/fll27NbANZjbXwM/IqrMI/rCD2lBUsJB4nJJGCV8KhVhFovXI5f +vd+43pcGhJJ6SuUCpRcuWyWPv1Q4z+7RwuSbKXVtrpWw3rOnDV8ARBDUAs9iVzFxLpU/tB +QPYEfZ0NFA4mhPnCy/erjO+SYt7zrEiKVVTcBPAAAAAwEAAQAAAQEA1JBHB2X87hmPgPLQ +pYUhZta9f1vyVfbw3+KrWvlTgSSFh0yvtXzQEorpTjhPin5Zx9+knaJfnKdBcHla+eNlbL +DNuqz9w8CKECWXPb3B3+0UslmbjlO4rRE3Q6uJFaFpuiCb9PoYMqUP7Jsp2woMLpUnmh7O +djXm/qUXnTTRuHtvFr2r/gVEqc9Emj6kcUjVIZdNNvluTpPUU+0d9X2O3Wpp/mgt/R2AmS +qBW7Bj+x4cAEP+aSKYd6OAOz6ZV22Ax8HirQKJwRwLMVPHW/WVup5yzJECTC4+uy92K2X/ +MsSZYnf/9PMMb2nDf9hxkTN3iIaHjVpGyF3d0caoj6SJUQAAAIEA5u9b17qIoAGddz5/vn +iA3vjinjEqGKnmm1UQ919eQdZjWCu9Gy99xgPKNuwSkuVgCQRTkjyCG4nBm2efwXYZLKqZ +2Z5MVEGO5Z6sebRujJ4YsX0Ng+3lxVoEQG+uq6Yak4rWZToVAM0qh/wwgCP/ZdoQykQ4YS +xMKMMK7iKSEIAAAACBAPZ/GJYXXWZEtfEHKJ8ZxZWHCnTo77D8/JDh74CTe8hTnOT1K+Lt +S+YYWKbwSQr38FrDgZOBC+ka8Jua+9GOW1gFcYDm4Vs0EFodDHXW3ywkBR6gmuqcfJk7is +Dx6Skx1dj/5vieHcgHoerGyPIrBrCClhilG1YT3lizLzuU6SgJAAAAgQDngSYD80ARJQAd +qiUKTRCLGM5Osszr/80ohzqbgkc8EU3j2tbpYPGMt7pyZYmlAW1RDnMDKhpLz7cPlyR3dO +s1tXO1689C27y08xWC97R410iYUY3VxmutUTEsDmM7WqyO0QsJr19S1l1fnXrL924ST5rW +kkpuOcK7igd7JsjLlwAAAAAB +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_r.pub b/testdata/keys/test_id_rsa_r.pub new file mode 100644 index 0000000..133693b --- /dev/null +++ b/testdata/keys/test_id_rsa_r.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDe6QnVfhLzMVrjJp9jtTJW8wD0cSXDgKHZaz4rIxp34huVfPiGYMlGRsrH8sjFwxa3Dei2qMgxFfPdlf4z5NBfUo4VRWXPH7V7f68iwNQ2itwgSds9QfNtcO7aMKjGAqTCt1oa8AqatQLHsTaPUFBCAWjuwp5xIkVNzUncsxp63GNcmW/LdozP/fll27NbANZjbXwM/IqrMI/rCD2lBUsJB4nJJGCV8KhVhFovXI5fvd+43pcGhJJ6SuUCpRcuWyWPv1Q4z+7RwuSbKXVtrpWw3rOnDV8ARBDUAs9iVzFxLpU/tBQPYEfZ0NFA4mhPnCy/erjO+SYt7zrEiKVVTcBP diff --git a/testdata/keys/test_id_rsa_s b/testdata/keys/test_id_rsa_s new file mode 100644 index 0000000..ec5b00f --- /dev/null +++ b/testdata/keys/test_id_rsa_s @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEArePtfHndKdPiavRusjylCJmxjy3kFutDieAtkGNEFR0Cl9n03hu5 +HhzCZhsX4tDzJSZFwBQ7gIup8Uj7XfobnLr4J10oHy/jSQx5CIZO9JaUPw3fSCaOwngSPh +gHKDj8SEa7gz7FBlvyEVgPtYBFxkG840K2GSApqp/+02gJ9QOms6Qq5d/1vQC0guBTjQde ++nVdL12XBMq2A8Rqb55DuZPMjwdmeUe+D4eA4Cc0q+9G6CkcTfN+AOhtP0PHq8krCmcRuE +pKuiIKHvmHMxAkoePgdfNP/g/zqbScm8be4fTUwdZ4uWqmA12k+thKaMBLgpI4otHZoli7 +Pykyc8yQFQAAA7j5yW5D+cluQwAAAAdzc2gtcnNhAAABAQCt4+18ed0p0+Jq9G6yPKUImb +GPLeQW60OJ4C2QY0QVHQKX2fTeG7keHMJmGxfi0PMlJkXAFDuAi6nxSPtd+hucuvgnXSgf +L+NJDHkIhk70lpQ/Dd9IJo7CeBI+GAcoOPxIRruDPsUGW/IRWA+1gEXGQbzjQrYZICmqn/ +7TaAn1A6azpCrl3/W9ALSC4FONB176dV0vXZcEyrYDxGpvnkO5k8yPB2Z5R74Ph4DgJzSr +70boKRxN834A6G0/Q8erySsKZxG4Skq6Igoe+YczECSh4+B180/+D/OptJybxt7h9NTB1n +i5aqYDXaT62EpowEuCkjii0dmiWLs/KTJzzJAVAAAAAwEAAQAAAQEAmjoD0lkLbSO+N3pg +C7l57izSrNjY6fLzzf+EqgFAyzEVsbTgV3ZjM2/ygzkYcFU+sf5F8tx/Ab3dlmFSgMFpaB +L0MYWQJkkE3y8u0f69XHWJOnuFQ6rfB2ATXftVrbkNGNWTTg6IFOHLytI97I6+U97ONwpe +E97Q1cJd8wbdfe6uYXFhGc33vY1c1QQJPVKS4NhJI3b92Xu16Nq3Tj8uulAqgumwxRrDtA +6DlfiLOZ+vwECBYBDw5hjEgsF/03pRV2ZbM/iKiPZ2Qftby2h5PXWixVvjpy320kc9LMgA +7KDImQske3F4o+2KxHMH9FE9/6UqD1AQdw+FHgAPb/7pKQAAAIA0RcjNjOoEpOtdb2PKgB +1s625ael5ng0tmjrY+N56pPE5yvBN2pDwmDDz65RBuigRrny9s6LDjk3Ro+htQsYHFSUqS +9Pm8d3yBCuvddP+Sp9DneyOcaPo29Rvw96hRWvxHaiGv48LOyRfUdQkvaZf96Yv5An6uAL +gKrS24B0da/QAAAIEA3XiSgF7x78DQLy8sWJAzKj3eEQnm9ymUq1dMK8xaPF5bJiGyGivn +vPQZOawDgg1Xu8WZtyzyOzvRuvvKbnBESgd5v8W4fR5al8vC2srq22HdHwWnCG5KUXs/P0 +6vA66WyYd1NgS0PMPLehemJGQ3OhUCHZNMG+coWudpsFSR7BMAAACBAMkATNI/es+T0nim +YlezCpana07gyIdHU/U2vfQ8wnhBp70YZHVzqtrf9saE1UVVEpdsTajB+BrGYo1Ssenzdx +3VxHa/cAeCNms7d7mQMk1a6ivSn7DscEeV4xN8ejr2neQTQ0LV4GaQfA4lQpnVhzwKEz0I +YNNuJnvRhQyQbMg3AAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_s.pub b/testdata/keys/test_id_rsa_s.pub new file mode 100644 index 0000000..be7f2c1 --- /dev/null +++ b/testdata/keys/test_id_rsa_s.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCt4+18ed0p0+Jq9G6yPKUImbGPLeQW60OJ4C2QY0QVHQKX2fTeG7keHMJmGxfi0PMlJkXAFDuAi6nxSPtd+hucuvgnXSgfL+NJDHkIhk70lpQ/Dd9IJo7CeBI+GAcoOPxIRruDPsUGW/IRWA+1gEXGQbzjQrYZICmqn/7TaAn1A6azpCrl3/W9ALSC4FONB176dV0vXZcEyrYDxGpvnkO5k8yPB2Z5R74Ph4DgJzSr70boKRxN834A6G0/Q8erySsKZxG4Skq6Igoe+YczECSh4+B180/+D/OptJybxt7h9NTB1ni5aqYDXaT62EpowEuCkjii0dmiWLs/KTJzzJAV diff --git a/testdata/keys/test_id_rsa_t b/testdata/keys/test_id_rsa_t new file mode 100644 index 0000000..c0f35cc --- /dev/null +++ b/testdata/keys/test_id_rsa_t @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAyIWisb7wmQicQaLT9eQLeOUc7bG4cd8H6XEC5ufkETjAp1fHtYM5 +U1TIFH4TN8CBnRol7ht/HuvCe9kcjgoGtqWFivu/M2b1YxiCi+eIJCokqOs3l0dPEEUaQG +8KK2UGMYgkIL1TNoh0cTy5CGy/CMDMZXnykRF2oM5KaBcecZ4qos71gfnc+ukldER2BFZj +R3k7/UKPDN8PZA1cUXM3gnTQv6CBVKxXsPzDOGONyEF8gvhX0RzxrXcFA+uPYRw45NvdPr +9OWmqMW/nNyl/B/AVTm3wHYH7T4HdCYGs5KBssxIfIFmdP5/j0NlrEz9m3xZ6hlNC1fJFQ +hojHQALJtwAAA7iHO9eHhzvXhwAAAAdzc2gtcnNhAAABAQDIhaKxvvCZCJxBotP15At45R +ztsbhx3wfpcQLm5+QROMCnV8e1gzlTVMgUfhM3wIGdGiXuG38e68J72RyOCga2pYWK+78z +ZvVjGIKL54gkKiSo6zeXR08QRRpAbworZQYxiCQgvVM2iHRxPLkIbL8IwMxlefKREXagzk +poFx5xniqizvWB+dz66SV0RHYEVmNHeTv9Qo8M3w9kDVxRczeCdNC/oIFUrFew/MM4Y43I +QXyC+FfRHPGtdwUD649hHDjk290+v05aaoxb+c3KX8H8BVObfAdgftPgd0JgazkoGyzEh8 +gWZ0/n+PQ2WsTP2bfFnqGU0LV8kVCGiMdAAsm3AAAAAwEAAQAAAQEAqke5WiGgJQSH1MEW +J/goudguEbdavvA7wsQiR71iEjHUgAbt47j0pEwZwDUz0qNaLfP3g9S7xaFFPXCobAIdo1 +q6JcUUn+ztsuIscewC01sqWHuucHuwq0tTOKZSx3j5onRO4bKEiImmAEclhf761s8Kg53k +G8btqy6jRvNi+e1qzzbdv5SRVk4Xf+IYSQkjl90bII+5WArlA8EAI0aypGzQwLteVIfvcY +UFBvH/rySddpAuPHFIO4lpaaccORcbbP5DTFs18CDxc86OQjsgs3e4ySzYl9vSM797AJLq +w4TeSmSWhjRagLt2HRimyzYxdcUHD1qBvl5kp6JY8Sg26QAAAIEAxswIdLXA1x/sD/jtbW +8O95ppplMQfBLrMU5YUIMugAxlQBrqFFiIO+8XMCbNvo2LyZ+pq25nwFkpYDN8XA2+BI8f +GWhxYHRui3LRmFLMKpNO7K9jVF+wgxjzN0YX5Nnyj1nuLiqXu06H9s6Lv0f5Z3ib4v5gr0 +IMT/IfkGA/7DIAAACBAOMtTtju1XOeIxAo/p+JAdAxjF+r0PPVtkL6LEM5eu2HJyrGWEeo +pvm4N6dvsJrZgYhbHSrjkoJXPcnojgXIe+BSz88gbe+q0h1fDJoOONX4Z1cn36vOV1Qh9e +MOEkL7MtO/YSJolghqirX9O20phipZD+0EkCK0/mPo1i6LJSQ7AAAAgQDh9pN1h+J+s7qZ +GcB2Rzc+FDJxnqGdwXW2tBqNOhHm227RT9HxPezLWyWFRRAKGz5baNdNo8Ycf1pqPyew9i +Q1uJBR+6QnSr9BGahffZF058WUyI4HrJMLjUdCCcsMLfLqJr/aVwbL1GDzMOrPyWj9WFyL +OgwCbgDgV+ARm9nEtQAAAAAB +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_t.pub b/testdata/keys/test_id_rsa_t.pub new file mode 100644 index 0000000..c67d86d --- /dev/null +++ b/testdata/keys/test_id_rsa_t.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDIhaKxvvCZCJxBotP15At45Rztsbhx3wfpcQLm5+QROMCnV8e1gzlTVMgUfhM3wIGdGiXuG38e68J72RyOCga2pYWK+78zZvVjGIKL54gkKiSo6zeXR08QRRpAbworZQYxiCQgvVM2iHRxPLkIbL8IwMxlefKREXagzkpoFx5xniqizvWB+dz66SV0RHYEVmNHeTv9Qo8M3w9kDVxRczeCdNC/oIFUrFew/MM4Y43IQXyC+FfRHPGtdwUD649hHDjk290+v05aaoxb+c3KX8H8BVObfAdgftPgd0JgazkoGyzEh8gWZ0/n+PQ2WsTP2bfFnqGU0LV8kVCGiMdAAsm3 diff --git a/testdata/keys/test_id_rsa_u b/testdata/keys/test_id_rsa_u new file mode 100644 index 0000000..d27a742 --- /dev/null +++ b/testdata/keys/test_id_rsa_u @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAuoZ8FV9TCayl9VN75C31BGAMonxYdX1zw3sRDh32lJhR13ttunfU +IAzXFa6egzKp7MeeHtlEKz8B/gMcZ3PibM4T3nmpwWJb3d/KS2XWHBL5itLyxFs33kyvsM +6gi8fkkHl+0Ikk+OegQDBUHTRgvawtbUNBKsb+yv9e/QuO2B5hV507RsIgXWiRYb1vhiDg +fogT9iiB1yVmL0mdJEFQCZZhEVSsMS5nxouei0E3WmCFHMne/j8j6AMw9H18rVJJ1ASFnd +zjIr7nW8vBSZlVpQSaZOnTa3Q8fC1Z7x31uHnLjQaRgYxDK6zmf6s/bdOq2dpnBSsHDViU +qpFXgqgqpQAAA7jV07JJ1dOySQAAAAdzc2gtcnNhAAABAQC6hnwVX1MJrKX1U3vkLfUEYA +yifFh1fXPDexEOHfaUmFHXe226d9QgDNcVrp6DMqnsx54e2UQrPwH+Axxnc+JszhPeeanB +Ylvd38pLZdYcEvmK0vLEWzfeTK+wzqCLx+SQeX7QiST456BAMFQdNGC9rC1tQ0Eqxv7K/1 +79C47YHmFXnTtGwiBdaJFhvW+GIOB+iBP2KIHXJWYvSZ0kQVAJlmERVKwxLmfGi56LQTda +YIUcyd7+PyPoAzD0fXytUknUBIWd3OMivudby8FJmVWlBJpk6dNrdDx8LVnvHfW4ecuNBp +GBjEMrrOZ/qz9t06rZ2mcFKwcNWJSqkVeCqCqlAAAAAwEAAQAAAQAVxdECJahpvq94nZAe +qIarRhLzfJRO0qAKt3jcLv8zhTw4bP34ADjQrO8GZuW15toS+5Ing7EDY012OyK7qLlLfh +qvo3//RN34XkEc3/xINqbOX6Va6lLxLf5sIxJ2fpTdkHEPIpMgQtlf5MRMzGpmeYyr7mfS ++HuRcpHB8/FupJnlzS7TsxgM7U2CxIHLvB6Vi34Koomvf5Bbby7xjxLqjSqW4ieQ9daQs6 +Sevhj9YDuHvz7gDvmYzK2UZ1rtjGaLcZdAjei5d9oU+LOaR7T5BGENBId59bavTUB1vtDI +0F43WiWewWJW7LDwg8vuCKZ3TUu9Hm1tKDcc/cpb1h/ZAAAAgQCOAu4Wy6JCdFzbCx5as+ +yXvjWTBu4g7Yr6PEJIJo3QpzaHfRya32xtVPswjSWBzanCP0WsId9VR2zFfJd1EJO5nIKo +Xf9PuYO8c6HY8JlrXxTXXd46ecBgRc48yhHOlf5cGZxHA2I1ccasRTqNo8RIHjxm3qefi5 +efF6z5pVGs3QAAAIEA8yWFEUCnd7mO7mGAh9U7cGp2lyszHrO1TUurQCjBSYeVPWHmmYFx +tCErsqF3FYlctEVQ3wrivI7Qg8sc9Qi0hRVY6M/u9akxRO7fi7Pyf1PlotBWe8dNAHuwhe +lpgP+Xig56n2vqBjI76nLl2Z8qmlnedqihWSALe6Wm8HdiG0cAAACBAMRitwfLRugWUZ/J +OO/7Y9q0wpfT1zEkVUOOTPdLNfyuk2fzRSo1NjEvX5wOG4aPldKVHvXvSImfh7q2OrzjN2 +6uM5ZOuiZALTTFCnENAfOKTJwcHngm5BV7+NqdY2NmdO2CSFDTEtdvIiXW765uEFFa2NVx +R6uIDQnaYtPy1CizAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_u.pub b/testdata/keys/test_id_rsa_u.pub new file mode 100644 index 0000000..c9de27e --- /dev/null +++ b/testdata/keys/test_id_rsa_u.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC6hnwVX1MJrKX1U3vkLfUEYAyifFh1fXPDexEOHfaUmFHXe226d9QgDNcVrp6DMqnsx54e2UQrPwH+Axxnc+JszhPeeanBYlvd38pLZdYcEvmK0vLEWzfeTK+wzqCLx+SQeX7QiST456BAMFQdNGC9rC1tQ0Eqxv7K/179C47YHmFXnTtGwiBdaJFhvW+GIOB+iBP2KIHXJWYvSZ0kQVAJlmERVKwxLmfGi56LQTdaYIUcyd7+PyPoAzD0fXytUknUBIWd3OMivudby8FJmVWlBJpk6dNrdDx8LVnvHfW4ecuNBpGBjEMrrOZ/qz9t06rZ2mcFKwcNWJSqkVeCqCql diff --git a/testdata/keys/test_id_rsa_v b/testdata/keys/test_id_rsa_v new file mode 100644 index 0000000..1cfd4ca --- /dev/null +++ b/testdata/keys/test_id_rsa_v @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA1QKwB10whGNEod1GzCD6bYGak7cxy0G59eFHIk+yZVZAgMMSw6Ei +0uVHrE9vpdYxZ9ygaRYoViIKbdwGpXoKZ+XapjNaOLs6li7swaGyef9XApbFtatCsiGk9N +84JuF/GD2w70cM08eZwkmE7bHb6OBHEakhogToDdghJmb9BdFzxJsqMFA+QavvlI2W5rjW +qv8eDvGUoHJ+Li4v+rOcbHC3Bc7/B9MEDf+4Ls9Wr6VVVlAOjzRf/gIbD6fA8jjN+mE+j4 +dv0jBs9YwM8/RpgXYPEkFITWyX5JFdinlBLZA6xrt80U1hDFusmmrcXVlKyr8LM2AlvFps +7rfgFoKm7QAAA7jllPoh5ZT6IQAAAAdzc2gtcnNhAAABAQDVArAHXTCEY0Sh3UbMIPptgZ +qTtzHLQbn14UciT7JlVkCAwxLDoSLS5UesT2+l1jFn3KBpFihWIgpt3Aalegpn5dqmM1o4 +uzqWLuzBobJ5/1cClsW1q0KyIaT03zgm4X8YPbDvRwzTx5nCSYTtsdvo4EcRqSGiBOgN2C +EmZv0F0XPEmyowUD5Bq++UjZbmuNaq/x4O8ZSgcn4uLi/6s5xscLcFzv8H0wQN/7guz1av +pVVWUA6PNF/+AhsPp8DyOM36YT6Ph2/SMGz1jAzz9GmBdg8SQUhNbJfkkV2KeUEtkDrGu3 +zRTWEMW6yaatxdWUrKvwszYCW8Wmzut+AWgqbtAAAAAwEAAQAAAQBG4zgVm0u0Ix0KCQuw +jyEIJGN+2S53CA6M50W3A3LJCqU4tLKf7BYcvTTXATCUre2PNPslPbx6vaVe3iRQGYcxAG +t9eAWEb7qVlFQyNE+jDfi04dw2EyKFCXFOFKMpPU4AkTc6yhhYukpPQAw+1jvrCghctj59 +HsjjIpQVpZx6vJeOe9WqtJRDdFksBcWj327vAtgbYyzoPvBOh1rKVl6JQG0LQwQpje86y+ +cE1DObmDl4QrykCvDVjh8iMSzMv2hkii08HRuRB9igllVrtmjnJ4CDzfJjs6oTdrujbjz/ +YHH11RqNHfouLsBfkb2ARO0PSW1zNLpxIMU4m/bm8Yq5AAAAgQCO6WdwlQ8i2oqXUFOYe6 +Dxp11fjt0kF133adIllPL6TcKFWbG9ajWbhu3HVhMGboQ1WQywzABIAjBCcCYieK0/O/Y6 +bpX+rwKUabLwmTntpXHXuS8anBvaJXubu1HZFKuQsyUejdS6T5a9WhmGCoPgyq7TMClGwd +rZEVQSO9B8qAAAAIEA/3UWoBDc5P8NjnBHNfNNaN90g2B/TlGkn25ZVCbcIY0xOYBEMDZg +InlxOfjmvxbSa1jCD1PM15Wqmk4Cnqj0tg1CLMqeRMt5H4q+21M9gpBxcqMt8q6z4YKNJ+ +eePw2lcZNl40HklcgcAmJ/OJ49rlrWsj1BRxoZV2YWF2ofJLsAAACBANV2hIOpJ6EAOQ1O +cwJxWlpOI1iSYM4wzekbt+vjanMbK2pSh4Etlr4TqboKfPO3lCbPSDeia3FlU2L8YIbnKN +k5UOkVMWHjBEhRY8n1ppRWH0J15saWjSsE7TzRVqAGc+n4Ed5dPG8BkUJV8bsOu0ySMxWy +XkfySpZr3FBf2nx3AAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_v.pub b/testdata/keys/test_id_rsa_v.pub new file mode 100644 index 0000000..373eb74 --- /dev/null +++ b/testdata/keys/test_id_rsa_v.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDVArAHXTCEY0Sh3UbMIPptgZqTtzHLQbn14UciT7JlVkCAwxLDoSLS5UesT2+l1jFn3KBpFihWIgpt3Aalegpn5dqmM1o4uzqWLuzBobJ5/1cClsW1q0KyIaT03zgm4X8YPbDvRwzTx5nCSYTtsdvo4EcRqSGiBOgN2CEmZv0F0XPEmyowUD5Bq++UjZbmuNaq/x4O8ZSgcn4uLi/6s5xscLcFzv8H0wQN/7guz1avpVVWUA6PNF/+AhsPp8DyOM36YT6Ph2/SMGz1jAzz9GmBdg8SQUhNbJfkkV2KeUEtkDrGu3zRTWEMW6yaatxdWUrKvwszYCW8Wmzut+AWgqbt diff --git a/testdata/keys/test_id_rsa_w b/testdata/keys/test_id_rsa_w new file mode 100644 index 0000000..fde48b0 --- /dev/null +++ b/testdata/keys/test_id_rsa_w @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAy0AgoRj6ODCMAgjVI0trC+GuPmoikefgOoCoBFsXLLHHdbrVPmqr +sY0KOF0TQeaObohYR3jrVYyj9AAyFW4Yptmv4tDsgQqrhoVjv7yhYlUx/D+21GPBIaJ1L0 +Y4ObAU+o7lIRqk07RBj2VNODpF72kLqNRy6jloRQJArzxCsS1gOzWgIugXQNfqnYBpp/GU +UuNKQwN2IBUWGOrYALvYmQa/Ostdqy+3h1fWNNFGGjshBtuovPrsRPrJpwLl2mBj/hSSzO +vWU0G5fnwXsJePMMUDwLNNACeZnyntdUVxrXMXUBNFT5OKEHfSVyBTtuZ37Neirjsij5wz +isiQOiyR4wAAA7h0TnZZdE52WQAAAAdzc2gtcnNhAAABAQDLQCChGPo4MIwCCNUjS2sL4a +4+aiKR5+A6gKgEWxcsscd1utU+aquxjQo4XRNB5o5uiFhHeOtVjKP0ADIVbhim2a/i0OyB +CquGhWO/vKFiVTH8P7bUY8EhonUvRjg5sBT6juUhGqTTtEGPZU04OkXvaQuo1HLqOWhFAk +CvPEKxLWA7NaAi6BdA1+qdgGmn8ZRS40pDA3YgFRYY6tgAu9iZBr86y12rL7eHV9Y00UYa +OyEG26i8+uxE+smnAuXaYGP+FJLM69ZTQbl+fBewl48wxQPAs00AJ5mfKe11RXGtcxdQE0 +VPk4oQd9JXIFO25nfs16KuOyKPnDOKyJA6LJHjAAAAAwEAAQAAAQB6QOxfeiKnwau1XZK2 +VBrjXq/sIvMASDT7e3+UfyYFYDnVerfhf1Aez/Mhy/pVXVS275QKCZWkM6A6mBOICF0Tn0 +TIbVqUk9Jwa7OAFPoNp6kVTU2vmwEENi++a8JOELfNrj3sF5alMHtuwUQdWg/O3aSBvKgS +cs1hFOXLgber0cgl7HC4LZ0YEqfhC/jOWI0O3gNniJpp4uNY1HC6CL3B0n4CqAB93+bcck +SkuKOR5xbK2gXd//gxCx+FTMk6ifd5qy8lDPfwVdwzmNkCQ682hf0NySeyd5g8mXpiZTcF +dSAfkXH/8wwgwXop0U5Yb+rD3P6M9wRIoNmZS6pyvdQBAAAAgQDrflAlm7OnNBtNpeJ+Bg +RrvlVXLha7LenbudF++dIIpk+A6tedUuHK6GH2Sc5CCylZz743HfHoG3eu3ry88u5IsYEf +NYXDMfuX0uRjmFP6/HnqWBKFQtgzr2mt4wozfVM7uzJBie8EiTUVBIvwIxuc6k9MhEAenr +IdKVg9ZwL/agAAAIEA9jZDaDIJErNEwfRpiooPOc10l3FoFgFmuNn8gmZQU/7Go4Y41FI8 +QGdQNn25EnT3Bs55e0nObIvHODsPT/FO7q+rPaOi45wjuWk/VgyywkPEg5l1kJzuGgs+DC +XTKjVPbRVgNzN6Sr5HkoDlvAHmxhNKliJROOJhJo1wgT+BnYEAAACBANNUo4Set8I/5/+5 +JNXABY5L5m+ouDkPjeKp7iR3G7Fr7rLN3FDWBq7rtJhsHhi3nT/zpNretIzpuvRqfqyXqZ +3OaDyCBT8arI6HjcYP382qLqNNGczv6zaOYJLp4VifXPWOnq3iFf/wdC9zPDpjNadHWxsv +RVEKRY6MGn7YUyljAAAAAAEC +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_w.pub b/testdata/keys/test_id_rsa_w.pub new file mode 100644 index 0000000..939effd --- /dev/null +++ b/testdata/keys/test_id_rsa_w.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDLQCChGPo4MIwCCNUjS2sL4a4+aiKR5+A6gKgEWxcsscd1utU+aquxjQo4XRNB5o5uiFhHeOtVjKP0ADIVbhim2a/i0OyBCquGhWO/vKFiVTH8P7bUY8EhonUvRjg5sBT6juUhGqTTtEGPZU04OkXvaQuo1HLqOWhFAkCvPEKxLWA7NaAi6BdA1+qdgGmn8ZRS40pDA3YgFRYY6tgAu9iZBr86y12rL7eHV9Y00UYaOyEG26i8+uxE+smnAuXaYGP+FJLM69ZTQbl+fBewl48wxQPAs00AJ5mfKe11RXGtcxdQE0VPk4oQd9JXIFO25nfs16KuOyKPnDOKyJA6LJHj diff --git a/testdata/keys/test_id_rsa_x b/testdata/keys/test_id_rsa_x new file mode 100644 index 0000000..647d8f7 --- /dev/null +++ b/testdata/keys/test_id_rsa_x @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAv8grICaeONoEwZs7JFG/SEJeNIEapNF9WZObKz4l2zXWTU76WOPV +zHihWScdaZRXjw+jKrnTCt54KwcD0OC/Gm08h1j7dbz6OImqkC0IheG19qTvb4uu/mLU6x +Xemzzp0Nfyhkf2IPbSils99ilZrfg0ewiTV6Ny6l/cuFr+tELAcSRtI48iggRd3cI8riJl +LEYiu+cVJNeduFVAARLmhUrW/DXpp/jJNGl06AKuiDvimzx/tuoavLJHycmZjibi1W92PF +qgmV4qt0fOt5EwPfIeJc5bXjhUe/906ecuhyk4AT5W9yOa0jAajJE+QimX5TfAtW7od49v +u04cihei9QAAA7jiEXwS4hF8EgAAAAdzc2gtcnNhAAABAQC/yCsgJp442gTBmzskUb9IQl +40gRqk0X1Zk5srPiXbNdZNTvpY49XMeKFZJx1plFePD6MqudMK3ngrBwPQ4L8abTyHWPt1 +vPo4iaqQLQiF4bX2pO9vi67+YtTrFd6bPOnQ1/KGR/Yg9tKKWz32KVmt+DR7CJNXo3LqX9 +y4Wv60QsBxJG0jjyKCBF3dwjyuImUsRiK75xUk1524VUABEuaFStb8Nemn+Mk0aXToAq6I +O+KbPH+26hq8skfJyZmOJuLVb3Y8WqCZXiq3R863kTA98h4lzlteOFR7/3Tp5y6HKTgBPl +b3I5rSMBqMkT5CKZflN8C1buh3j2+7ThyKF6L1AAAAAwEAAQAAAQASPV5ko2ee7pVPOI1K +af/wRmehRLoc/NOYJmK2nSWKkLc+CQnkVF7sIHjv+rlgZjxhndId6Sh6Oyhgpz0kq4thXw +xHrx3TV9LQd79iEPZjjVD6ETu4dyeAiDPJ2/2c9BEkzLUDMmrlPbROdlkRRWODGT0cindA ++YmfYtiiK/1XdWueqURR2Wx7CesxleR5CbqU/7RQHyAgIR2Sf+0TmSWnA1/WxqONHOcYj7 +8uVescILJa/o+uJckW/Kx8X+i5Mgcj9Lwp3l/Ipu7XJGiYxnuX+MtRL/RrhJAzeeonWtx8 +72GYOPJQ0Lb6BdcdDnIs8j0rkbWsPFilNxXr2/CmRdtBAAAAgGS+al8SFOW48WSX4TMVpA +bVkR4sYwZqjktfLZSCcTxTWUicfFGYlOLFzc4cYNUanPlKQtbJSvamBidSLeTxnUozD64p +X7vtAKPvUBJmohjpnLRXsQmCGrhd9rNH7xD9v3ZlRciT8wOjFKuO8JIUrFPzW1W/paM/Jk +ETAY3HC7LAAAAAgQDk499mVf/ejX21vewKCUCnDBeZhSW4dKvaYVXbJw6YX3UrHh2iyzTJ +x+VpGmtKfpPDVaWHX0/hoQOtAVID5llG0xMqX4gtoYSrG9l3u4cR2g8bbq8//UKAWwvdzP +VhfJyNp8QL9YBW/zdHk3QtU7v74II5SVAvUD8lZVz4GERR0QAAAIEA1n8lR2dswGyvePQM +/C+SnPNxY0/VBnfaCucOhpdOooCko0cDcKR6jY3U2EE0RDSLJZGI5VaD89RZph3r0EKx30 +kM0VIkUb11DtI14HwuFRbcgmh1QB5Xts1In7ogGGAGa9rKUdWGhfXZcJHLkgDvOhx+my4Z +1zHr0sc5M8v1A+UAAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_x.pub b/testdata/keys/test_id_rsa_x.pub new file mode 100644 index 0000000..2cb5865 --- /dev/null +++ b/testdata/keys/test_id_rsa_x.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/yCsgJp442gTBmzskUb9IQl40gRqk0X1Zk5srPiXbNdZNTvpY49XMeKFZJx1plFePD6MqudMK3ngrBwPQ4L8abTyHWPt1vPo4iaqQLQiF4bX2pO9vi67+YtTrFd6bPOnQ1/KGR/Yg9tKKWz32KVmt+DR7CJNXo3LqX9y4Wv60QsBxJG0jjyKCBF3dwjyuImUsRiK75xUk1524VUABEuaFStb8Nemn+Mk0aXToAq6IO+KbPH+26hq8skfJyZmOJuLVb3Y8WqCZXiq3R863kTA98h4lzlteOFR7/3Tp5y6HKTgBPlb3I5rSMBqMkT5CKZflN8C1buh3j2+7ThyKF6L1 diff --git a/testdata/keys/test_id_rsa_y b/testdata/keys/test_id_rsa_y new file mode 100644 index 0000000..369ee7e --- /dev/null +++ b/testdata/keys/test_id_rsa_y @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAwtH2W6HgeE0R50NSTWZFwUdRP6VwkGnvpvQ9u9ekLGe3NF/ldpQ+ +XlOUznYpjOsPX9YTGMihSV2YFytXx751P4mvX9Eib4hPwm1k6YK7od1R8KRu4A61HEkEMq +au9q305iJ7X+hKwntQxV6aVQQdx9d3/hsho/Fmin9PifRvOqipJaUmE4z409DFYmkJR558 +69NoMk5rQ4Pkr580js3TNpnEnbWOM3RvmoRQEbWDaaSVMvZMbcPFHXx3ophSbltjZxm6G3 +O6uIVypzVgvKes8Wu8w1qhz2SyXLAympDoIMT9c4kHATMLcZ1/KHS66sr4ldwu11CHf0Jd +yRZ1WsSemwAAA7gMjR8WDI0fFgAAAAdzc2gtcnNhAAABAQDC0fZboeB4TRHnQ1JNZkXBR1 +E/pXCQae+m9D2716QsZ7c0X+V2lD5eU5TOdimM6w9f1hMYyKFJXZgXK1fHvnU/ia9f0SJv +iE/CbWTpgruh3VHwpG7gDrUcSQQypq72rfTmIntf6ErCe1DFXppVBB3H13f+GyGj8WaKf0 ++J9G86qKklpSYTjPjT0MViaQlHnnzr02gyTmtDg+SvnzSOzdM2mcSdtY4zdG+ahFARtYNp +pJUy9kxtw8UdfHeimFJuW2NnGbobc7q4hXKnNWC8p6zxa7zDWqHPZLJcsDKakOggxP1ziQ +cBMwtxnX8odLrqyviV3C7XUId/Ql3JFnVaxJ6bAAAAAwEAAQAAAQBaiq4OJAtonZjme/ky +E4fzratrAv5vf2xrkTNnq26XXJReePrxwy/BMuMDmRLydQQuLBWv/P5IVIEss24WXMOtBd +ZFl7KDVzyKRcESbTm6nltIlYMaCnvNa3nR+Hl0F6Crm/buucyU2i7Nq4vaWxFkIRWZOQ7H +XJelYkma+cj/3zc4derAgKez7/FlLnr+H1MEfhRKi8v0+lN/E3SEZzamkUITuUK7h+98iK +L+Ig1mAylBnTh2zT1LNNlNTuFfKnXtyzj2S/LU8/AazwdrOH4AShYjJB5tuWaUADq64oBp +ZFg53Eb+QX1fI6rTQDG/mST5vs+RVzlbM9I5DAbbcEsxAAAAgBpnSqmA7FOmqPWeoATLAH +CK7NKbxDtvsTOEvpUIldFDZHc8qW2LVhmSJ9HZZURAipaYXKfJvsAF1jWQtt1Wo2XPYfKG +M3tojsqAYdr1I8ERVloWhYdn86luXCaYT/uBJT+QLu81qKNisD8oUB+NxSBtGNad9i2v0Q +lOALaENQwJAAAAgQDfjDfxOlBZmp9rWeeS/hygvK/monQoGpoB1rWaDdBEeqXyl4H7kCM4 +zvW38d70uQYBdIQLH32fvMHq4kc7PoAIYcxYjaovSo+z8opY2jNCeFTfYnIOzeW4s+7+j4 +sr9i+bP+D1rAKuSpMronxNk2FDVcyMFHy0eFQB++lt4ADnhQAAAIEA3xohpuawl7idpcUQ +nWYmHj/FeyKeyMJro4do00XFXpvhMIFS3odSqv+7/D0LkDgWOGRF9cLjQHR/ASAzHNFiyO +fgAjYdhKF1nCejoemvDkwJpgNGQprSXgUnFS/xlfDzzwuG2AopZA5fDJPHmjWMzXL/TV1j +mD0iMFLL+Aked58AAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_y.pub b/testdata/keys/test_id_rsa_y.pub new file mode 100644 index 0000000..912c1b8 --- /dev/null +++ b/testdata/keys/test_id_rsa_y.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDC0fZboeB4TRHnQ1JNZkXBR1E/pXCQae+m9D2716QsZ7c0X+V2lD5eU5TOdimM6w9f1hMYyKFJXZgXK1fHvnU/ia9f0SJviE/CbWTpgruh3VHwpG7gDrUcSQQypq72rfTmIntf6ErCe1DFXppVBB3H13f+GyGj8WaKf0+J9G86qKklpSYTjPjT0MViaQlHnnzr02gyTmtDg+SvnzSOzdM2mcSdtY4zdG+ahFARtYNppJUy9kxtw8UdfHeimFJuW2NnGbobc7q4hXKnNWC8p6zxa7zDWqHPZLJcsDKakOggxP1ziQcBMwtxnX8odLrqyviV3C7XUId/Ql3JFnVaxJ6b diff --git a/testdata/keys/test_id_rsa_z b/testdata/keys/test_id_rsa_z new file mode 100644 index 0000000..3cb07b2 --- /dev/null +++ b/testdata/keys/test_id_rsa_z @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEAqkwzCaSbe/UROmuwCHLtfRT5umZ+2Fvrkrkar/0KrYzw45f9d4yu +J5qs2x8eUUIuVJsGmMAM/nNXxL05dl1rsl2Qorxz2qyb16CDcIATysUh9MK3KZKnzXZtkM +LlKiBmmKluVhuEHOxsyc/lLew5NVg1KWa8iCEThYM49gYo+3sSiZnKlkj9BI5Z6HdSJk74 +KqGXqO9THxzGKsg6qSYYaJ58KEIVzzPonEQyPGTBWXvYgk5/fcLxsEnvHzgb/iyKmaZh/f +kt2flCNkl6+H3SFoKAmSBdddoFlDJI2pslI5gHnHNOUzRx6AQ/SLYI9C9A7epWWwjISu9A +Gcz15P9QpQAAA7g4MpaeODKWngAAAAdzc2gtcnNhAAABAQCqTDMJpJt79RE6a7AIcu19FP +m6Zn7YW+uSuRqv/QqtjPDjl/13jK4nmqzbHx5RQi5UmwaYwAz+c1fEvTl2XWuyXZCivHPa +rJvXoINwgBPKxSH0wrcpkqfNdm2QwuUqIGaYqW5WG4Qc7GzJz+Ut7Dk1WDUpZryIIROFgz +j2Bij7exKJmcqWSP0Ejlnod1ImTvgqoZeo71MfHMYqyDqpJhhonnwoQhXPM+icRDI8ZMFZ +e9iCTn99wvGwSe8fOBv+LIqZpmH9+S3Z+UI2SXr4fdIWgoCZIF112gWUMkjamyUjmAecc0 +5TNHHoBD9Itgj0L0Dt6lZbCMhK70AZzPXk/1ClAAAAAwEAAQAAAQBkaJM7H0vxtVgYGwBq +quCt/F0V83LcvK3UD8HVfBv8ofoyJx5yi4bvoPpEJD/q51uL1O/5xlqi+H0KagtkZ8alHx +e1a+nfSCCG7YQyI8IHr13LA7edEnRP54DIdrFJulrjrhp5OLYXm6q0/xgAkKRWeJC3vXRx +yxbIhMtj+9PUGhFjlg1HuwkKBYNfYM1jJqbDLkbgTdnHNUuoIzVLhDrH5ton2uSb/SZE50 +kRaxmA8o9U5njUNMnS8hkm0R3K/iWVshGKp8KHL8y0QTVvFAHB6ei3OpYwjCA0LbO1Fcuc +blGfiOD36J7vM4R66Y+hYKAeVcUOTmKct0kWLFhlE8ahAAAAgBaXvX5gnVCW7Dvoj+kpMk +KY2ZoEiX3gmogUW6X21cv33IHhZjWbs7Wb+vwZLId4z8GvGEffn/yh3/V6aKk+V5RxmYFR +/GuRtJ8EkxJJI7ZhPzUbz/Vghu4owoGREYaf/Fd/+MuY25GlHhgooC7NIP6a9/ghVb/DWy +AKhsujq4XJAAAAgQDS6F9ydMqArM2R7lgDM4FcyIGatuHyJB/VR1w2uA4T8tEZi/WyMInH +RpJfYbAwzEYYrJyQ52LsGviq9kfp58XfSbhJPTuQOHDFPtsapkVRn321kzeise3otoy4Ex +7IZ9Rjn9fUYZ0be55FJ2D/1hP28tpSYLGTlvwBaeh62FXzOQAAAIEAzrUdDj4gKMRVpqCH +s5IR6zd6oCF9XLT0KeNDijUmXYpLzgMDb+4TEDH+tvArZIp0GKCzyDylvniqGfVreKModm +D6jN9kmZBEGRzj1aEzu5uLh6iFFeEXKgqE4n6WLPOqy8+latHuvlAG18oehwVrectSqv7v +f06vt3dMUzyL7M0AAAAAAQID +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/keys/test_id_rsa_z.pub b/testdata/keys/test_id_rsa_z.pub new file mode 100644 index 0000000..66009b1 --- /dev/null +++ b/testdata/keys/test_id_rsa_z.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqTDMJpJt79RE6a7AIcu19FPm6Zn7YW+uSuRqv/QqtjPDjl/13jK4nmqzbHx5RQi5UmwaYwAz+c1fEvTl2XWuyXZCivHParJvXoINwgBPKxSH0wrcpkqfNdm2QwuUqIGaYqW5WG4Qc7GzJz+Ut7Dk1WDUpZryIIROFgzj2Bij7exKJmcqWSP0Ejlnod1ImTvgqoZeo71MfHMYqyDqpJhhonnwoQhXPM+icRDI8ZMFZe9iCTn99wvGwSe8fOBv+LIqZpmH9+S3Z+UI2SXr4fdIWgoCZIF112gWUMkjamyUjmAecc05TNHHoBD9Itgj0L0Dt6lZbCMhK70AZzPXk/1Cl diff --git a/testdata/policy/auth.rego b/testdata/policy/auth.rego new file mode 100644 index 0000000..2011149 --- /dev/null +++ b/testdata/policy/auth.rego @@ -0,0 +1,62 @@ +package conduit + +import rego.v1 + +default permit_certificate := false + +# Accept user certificate if no principals have been offered. +permit_certificate if { + count(input.principals) == 0 +} + +permit_certificate if { + _token_is_valid +} + +default permit_password := false + +# Accept user password if no principals have been offered. +permit_password if { + count(input.principals) == 0 +} + +permit_password if { + _token_is_valid +} + +# Accept user token as second factor if a valid certificate was offered. +permit_token if { + _certificate_is_valid +} + +# Accept user password as second factor if a valid certificate was offered. +permit_token if { + _password_is_valid +} + +default permit := false + +# Accept certificate + token +permit if { + _certificate_is_valid + _token_is_valid +} + +# Accept token + password +permit if { + _password_is_valid + _token_is_valid +} + +_certificate_is_valid if { + some principal in input.principals + principal.type == "certificate" +} + +_password_is_valid if { + input.principals[_].type == "password" +} + +_token_is_valid if { + input.principals[_].type == "token" +} diff --git a/testdata/policy/conduit/auth/mfa.rego b/testdata/policy/conduit/auth/mfa.rego new file mode 100644 index 0000000..f132bcf --- /dev/null +++ b/testdata/policy/conduit/auth/mfa.rego @@ -0,0 +1,76 @@ +package conduit.auth.mfa + +import rego.v1 + +default permit_certificate := false + +default permit_publickey := false + +default permit_password := false + +default permit_token := false + +default permit := false + +certificate_valid_for_user(name) if { + some principal in input.principals + principal.type == "certificate" + principal.identity == name +} + +certificate_valid_for_user(name) if { + some principal in input.principals + principal.type == "certificate" + name in principal.attr.principals +} + +# Accept user certificate if no principals have been offered. +permit_certificate if { + count(input.principals) == 0 +} + +permit_certificate if { + _token_is_valid + not _certificate_is_valid +} + +# Accept user token if no principals have been offered. +permit_token if { + count(input.principals) == 0 +} + +# Accept user password as second factor if a valid certificate was offered. +permit_token if { + _certificate_is_valid + not _token_is_valid +} + +# Accept if user passed multiple factors: certificate and token. +permit if { + _certificate_is_valid + _token_is_valid +} + +# Certificate is valid if we have a valid certificate principal and the key-id matches the target user. +_certificate_is_valid if { + certificate_valid_for_user(input.conn.user) +} + +# Password is valid if we have a valid password principal +_password_is_valid if { + some principal in input.principals + principal.type == "password" +} + +# Token is valid if we have a valid token principal +_token_is_valid if { + some principal in input.principals + principal.type == "token" +} + +# Skip token validation if we have a valid certificate with the "permit-skip-mfa" extension. +_token_is_valid if { + certificate_valid_for_user(input.conn.user) + some principal in input.principals + principal.attr.extensions["permit-skip-mfa"] == "" +} diff --git a/testdata/policy/conduit/auth/user_certificate.rego b/testdata/policy/conduit/auth/user_certificate.rego new file mode 100644 index 0000000..a81c9e4 --- /dev/null +++ b/testdata/policy/conduit/auth/user_certificate.rego @@ -0,0 +1,11 @@ +package conduit.auth.user_certificate + +default permit := false + +permit if { + input.principal.identity == input.conn.user +} + +permit if { + input.conn.user in input.principal.attr.principals +} \ No newline at end of file diff --git a/testdata/policy/conduit/session/env.rego b/testdata/policy/conduit/session/env.rego new file mode 100644 index 0000000..41eb7ac --- /dev/null +++ b/testdata/policy/conduit/session/env.rego @@ -0,0 +1,11 @@ +package conduit.session.env + +default permit := false + +permit if { + input.key == "LANG" +} + +permit if { + startswith(input.key, "LC_") +} \ No newline at end of file diff --git a/testdata/skip-mfa.ed25519 b/testdata/skip-mfa.ed25519 new file mode 100644 index 0000000..f64228c --- /dev/null +++ b/testdata/skip-mfa.ed25519 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDuP/L6ga7Ow1na/2vzTA3woAGn05fEWesaFWVrvr2b7wAAAIhv2tJFb9rS +RQAAAAtzc2gtZWQyNTUxOQAAACDuP/L6ga7Ow1na/2vzTA3woAGn05fEWesaFWVrvr2b7w +AAAEA+6g5Ycw0qyl60LDzLrnZMSWYFN/hjy8vdQidhG/mkyu4/8vqBrs7DWdr/a/NMDfCg +AafTl8RZ6xoVZWu+vZvvAAAAAAECAwQF +-----END OPENSSH PRIVATE KEY----- diff --git a/testdata/skip-mfa.ed25519-cert.pub b/testdata/skip-mfa.ed25519-cert.pub new file mode 100644 index 0000000..a7fb17d --- /dev/null +++ b/testdata/skip-mfa.ed25519-cert.pub @@ -0,0 +1 @@ +ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIE8PwSb7Vctzv5L85sCYi2pkub+w9P/E39TmXmX1ic4PAAAAIO4/8vqBrs7DWdr/a/NMDfCgAafTl8RZ6xoVZWu+vZvvAAAAAAAAAAAAAAABAAAAB2V4YW1wbGUAAAATAAAAB2V4YW1wbGUAAAAEZGVtbwAAAAAAAAAA//////////8AAAAAAAAAmQAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA9wZXJtaXQtc2tpcC1tZmEAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCufO7OUqeLZkUX7qGatOk79nZTQGqCbHxTp6z8Nb+52HJiRDXgLfY/3zLX/3kOdjVrQujwEEfbD6IVOjF3gnDkgYvjnJROXEiv3k2UApYzrbJebFohcFPrrk3WqbzOeMQXciEuSNyJV33FMYBnZ8Y+Yrf5a4x5R0pxbmdCmxSOhihOIZYlKNjPq3UVfXwth/NW4KiHUkmuH6d4x4D3OMJ+xKeK9Eu05szBWwRHY3vplf0SYiwDd3xPlFalPG2UzA3j/+kdtQf0qNJGyWpjRjHv9BvJMP/G+Y2CckvygetYBfcvX9JGb/p8G1JyU55ODD5maxDrCSFm8aqDbvCLmeAbAAABFAAAAAxyc2Etc2hhMi01MTIAAAEApv/KdGNgijoQr8zW02zSgslpiJezw5i81wXLxBNoNSQUFbzEShbGBaaGK+742NKZ9Uc8vUNZ50mK6bTPzLFNmKkzAmTyZVR7amFB7Lgk1O+rz5I3ZIi50JYLYDIqCkt7Nt83DwzTAEHwEA908TIy7RJD29jklso4F3WNPkWy4GlUgenB8nqTleFuktChpyKtbI/ejcO+faQ7srBP4kSlmWNswJe0b0a42ZoCqA0i4NXnp4MBpPB7IbyfFX7BIGBFqAxDOGB+xKWmAZ54j+j8qrhUYo9liZLKMs2RF0CFPprbTO7tYtUWsYQwUErvQBWvoIOzLsoCsYIcm7oVa1+BGw== skip-mfa.ed25519.pub diff --git a/testdata/skip-mfa.ed25519.pub b/testdata/skip-mfa.ed25519.pub new file mode 100644 index 0000000..9b5a496 --- /dev/null +++ b/testdata/skip-mfa.ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO4/8vqBrs7DWdr/a/NMDfCgAafTl8RZ6xoVZWu+vZvv