github.com/devops-filetransfer/sshego@v7.0.4+incompatible/server.go (about) 1 package sshego 2 3 import ( 4 "bytes" 5 "context" 6 "errors" 7 "fmt" 8 "image/png" 9 "io/ioutil" 10 "log" 11 "net" 12 "os" 13 "sync" 14 "time" 15 16 "github.com/glycerine/greenpack/msgp" 17 "github.com/pquerna/otp" 18 "github.com/pquerna/otp/totp" 19 20 ssh "github.com/glycerine/sshego/xendor/github.com/glycerine/xcryptossh" 21 ) 22 23 // Esshd is our embedded sshd server, 24 // running from inside this libary. 25 type Esshd struct { 26 cfg *SshegoConfig 27 Halt ssh.Halter 28 addUserToDatabase chan *User 29 replyWithCreatedUser chan *User 30 31 delUserReq chan *User 32 replyWithDeletedDone chan bool 33 34 updateHostKey chan ssh.Signer 35 36 mut sync.Mutex 37 38 cr *CommandRecv 39 } 40 41 func (e *Esshd) Stop() error { 42 e.Halt.RequestStop() 43 <-e.Halt.DoneChan() 44 45 if -1 == WaitUntilAddrAvailable(e.cfg.EmbeddedSSHd.Addr, 100*time.Millisecond, 100) { 46 return fmt.Errorf("esshd never stopped; after 10 seconds of waits") 47 } 48 49 // gotta wait for xport to unbind as well... 50 xport := fmt.Sprintf("127.0.0.1:%v", 51 e.cfg.SshegoSystemMutexPort) 52 if -1 == WaitUntilAddrAvailable(xport, 100*time.Millisecond, 100) { 53 return fmt.Errorf("xport bound never stopped from old esshd; after 10 seconds of waits") 54 } 55 56 return nil 57 } 58 59 // NewEsshd sets cfg.Esshd with a newly 60 // constructed Esshd. does NewHostDb() 61 // internally. 62 func (cfg *SshegoConfig) NewEsshd() *Esshd { 63 p("top of SshegoConfig.NewEsshd()...") 64 srv := &Esshd{ 65 cfg: cfg, 66 Halt: *ssh.NewHalter(), 67 addUserToDatabase: make(chan *User), 68 replyWithCreatedUser: make(chan *User), 69 delUserReq: make(chan *User), 70 replyWithDeletedDone: make(chan bool), 71 updateHostKey: make(chan ssh.Signer), 72 } 73 if srv.cfg.HostDb == nil { 74 err := srv.cfg.NewHostDb() 75 panicOn(err) 76 } 77 cfg.Esshd = srv 78 return srv 79 } 80 81 // CustomChannelHandlerCB is a callback that 82 // is configured in the cfg.CustomChannelHandlers map. 83 // Each will be called on its own goroutine already. 84 // For example, "custom-inproc-stream" might 85 // serve in-process streaming. 86 type CustomChannelHandlerCB func(nc ssh.NewChannel, sshconn ssh.Conn, ca *ConnectionAlert) 87 88 // PerAttempt holds the auth state 89 // that should be reset anew on each 90 // login attempt; plus a pointer to 91 // the invariant State. 92 type PerAttempt struct { 93 PublicKeyOK bool 94 OneTimeOK bool 95 96 User *User 97 State *AuthState 98 Config *ssh.ServerConfig 99 100 cfg *SshegoConfig 101 } 102 103 func NewPerAttempt(s *AuthState, cfg *SshegoConfig) *PerAttempt { 104 pa := &PerAttempt{State: s} 105 pa.cfg = cfg 106 return pa 107 } 108 109 // AuthState holds the authorization information 110 // that doesn't change after startup; each fresh 111 // PerAttempt gets a pointer to one of these. 112 // Currently assumes only one user. 113 type AuthState struct { 114 HostKey ssh.Signer 115 OneTime *TOTP 116 117 AuthorizedKeysMap map[string]bool 118 119 PrivateKeys map[string]interface{} 120 Signers map[string]ssh.Signer 121 PublicKeys map[string]ssh.PublicKey 122 123 Cert *ssh.Certificate 124 } 125 126 func NewAuthState(w *TOTP) *AuthState { 127 if w == nil { 128 w = &TOTP{} 129 } 130 return &AuthState{ 131 OneTime: w, 132 AuthorizedKeysMap: map[string]bool{}, 133 } 134 } 135 136 type CommandRecv struct { 137 userTcp TcpPort 138 esshd *Esshd 139 cfg *SshegoConfig 140 141 addUserReq chan *User 142 replyWithCreatedUser chan *User 143 144 delUserReq chan *User 145 replyWithDeletedDone chan bool 146 147 reqStop chan bool 148 Done chan bool 149 } 150 151 var NewUserCmd = []byte("00NEWUSER___") 152 var NewUserCmdStr = string(NewUserCmd) 153 var NewUserReply = []byte("00REPLY_____") 154 155 var DelUserCmd = []byte("01DELUSER___") 156 var DelUserCmdStr = string(DelUserCmd) 157 var DelUserReplyOK = []byte("01REPLY_OK__") 158 var DelUserReplyFailed = []byte("01REPLY_FAIL") 159 160 func (e *Esshd) NewCommandRecv() *CommandRecv { 161 return &CommandRecv{ 162 userTcp: TcpPort{Port: e.cfg.SshegoSystemMutexPort}, 163 esshd: e, 164 cfg: e.cfg, 165 addUserReq: e.addUserToDatabase, 166 reqStop: make(chan bool), 167 Done: make(chan bool), 168 replyWithCreatedUser: e.replyWithCreatedUser, 169 delUserReq: e.delUserReq, 170 replyWithDeletedDone: e.replyWithDeletedDone, 171 } 172 } 173 174 func (cr *CommandRecv) Start(ctx context.Context) error { 175 176 msecLimit := 100 177 err := cr.userTcp.Lock(msecLimit) 178 if err != nil { 179 return err 180 } 181 go func() { 182 // basically, always hold the lock while we are up 183 defer cr.userTcp.Unlock() 184 tcpLsn := cr.userTcp.Lsn.(*net.TCPListener) 185 var nConn net.Conn 186 187 defer func() { 188 close(cr.Done) 189 }() 190 191 mainloop: 192 for { 193 timeoutMillisec := 500 194 err = tcpLsn.SetDeadline(time.Now().Add(time.Duration(timeoutMillisec) * time.Millisecond)) 195 panicOn(err) 196 nConn, err = tcpLsn.Accept() 197 if err != nil { 198 // simple timeout, check if stop requested 199 // 'accept tcp 127.0.0.1:54796: i/o timeout' 200 // p("simple timeout err: '%v'", err) 201 select { 202 case <-ctx.Done(): 203 return 204 case <-cr.reqStop: 205 return 206 default: 207 // no stop request, keep looping 208 } 209 continue 210 } else { 211 // not error, but connection 212 213 // read from it 214 err = nConn.SetReadDeadline(time.Now().Add(time.Second)) 215 if err != nil { 216 log.Printf("warning: CommandRecv: nConn.Read ignoring "+ 217 "SetReadDeadline error %v", err) 218 nConn.Close() 219 continue mainloop 220 } 221 222 by := make([]byte, len(NewUserCmd)) 223 _, err := nConn.Read(by) 224 if err != nil { 225 log.Printf("warning: CommandRecv: nConn.Read ignoring "+ 226 "Read error '%v'; could be timeout.", err) 227 nConn.Close() 228 continue mainloop 229 } 230 cmd := string(by) 231 switch cmd { 232 case NewUserCmdStr: 233 log.Printf("CommandRecv: we got a NEWUSER command") 234 case DelUserCmdStr: 235 log.Printf("CommandRecv: we got a DELUSER command") 236 default: 237 log.Printf("warning: CommandRecv: nConn.Read ignoring "+ 238 "unrecognized command '%v'", cmd) 239 nConn.Close() 240 continue mainloop 241 } 242 243 // unmarshal into a User structure 244 newUser := NewUser() 245 reader := msgp.NewReader(nConn) 246 err = newUser.DecodeMsg(reader) 247 if err != nil { 248 log.Printf("warning: saw NEWUSER/DELUSER preamble but got"+ 249 " error reading the User data: %v", err) 250 nConn.Close() 251 continue mainloop 252 } 253 log.Printf("CommandRecv: %s '%v' with email '%v'", cmd, newUser.MyLogin, newUser.MyEmail) 254 255 if cmd == DelUserCmdStr { 256 // make the delete request 257 select { 258 case cr.delUserReq <- newUser: 259 case <-time.After(10 * time.Second): 260 log.Printf("warning: unable to deliver delUser request " + 261 "after 10 seconds") 262 case <-cr.reqStop: 263 return 264 case <-ctx.Done(): 265 return 266 } 267 // ack back 268 select { 269 case ok := <-cr.replyWithDeletedDone: 270 err := nConn.SetWriteDeadline(time.Now().Add(time.Second * 5)) 271 panicOn(err) 272 if ok { 273 _, err = nConn.Write(DelUserReplyOK) 274 } else { 275 _, err = nConn.Write(DelUserReplyFailed) 276 } 277 panicOn(err) 278 nConn.Close() 279 280 case <-cr.reqStop: 281 return 282 case <-ctx.Done(): 283 return 284 } 285 } 286 287 if cmd == NewUserCmdStr { 288 // make the add request 289 select { 290 case cr.addUserReq <- newUser: 291 case <-time.After(10 * time.Second): 292 log.Printf("warning: unable to deliver newUser request" + 293 "after 10 seconds") 294 case <-cr.reqStop: 295 return 296 case <-ctx.Done(): 297 return 298 } 299 // send remote client a reply, also a User 300 // but now with fields filled in. 301 select { 302 case goback := <-cr.replyWithCreatedUser: 303 //p("goback received!") 304 writeBackHelper(goback, nConn) 305 case <-cr.reqStop: 306 return 307 case <-ctx.Done(): 308 return 309 } 310 } 311 } 312 } 313 }() 314 return nil 315 } 316 317 func (e *Esshd) Start(ctx context.Context) { 318 p("Start for Esshd called.") 319 320 if !e.cfg.SkipCommandRecv { 321 e.cr = e.NewCommandRecv() 322 err := e.cr.Start(ctx) 323 if err != nil { 324 panic(err) // -xport error: could not acquire our -xport before the deadline, for -xport 127.0.0.1:55418 325 } 326 } 327 328 go func() { 329 p("%s Esshd.Start() called, for binding '%s'. %s", 330 e.cfg.Nickname, e.cfg.EmbeddedSSHd.Addr, SourceVersion()) 331 332 // most of the auth state is per user, so it has 333 // to wait until we have a login and a 334 // username at hand. 335 a := NewAuthState(nil) 336 337 // we copy the host key here to avoid a data race later. 338 e.cfg.Mut.Lock() 339 e.cfg.HostDb.saveMut.Lock() 340 a.HostKey = e.cfg.HostDb.HostSshSigner // race unless we lock saveMut too. 341 e.cfg.HostDb.saveMut.Unlock() 342 e.cfg.Mut.Unlock() 343 344 p("about to listen on %v", e.cfg.EmbeddedSSHd.Addr) 345 // Once a ServerConfig has been configured, connections can be 346 // accepted. 347 domain := "tcp" 348 if e.cfg.EmbeddedSSHd.UnixDomainPath != "" { 349 domain = "unix" 350 } 351 listener, err := net.Listen(domain, e.cfg.EmbeddedSSHd.Addr) 352 if err != nil { 353 msg := fmt.Sprintf("failed to listen for connection on %v: %v", 354 e.cfg.EmbeddedSSHd.Addr, err) 355 log.Printf(msg) 356 //panic(msg) 357 return 358 } 359 360 // cleanup, any which way we return 361 defer func() { 362 if e.cr != nil { 363 close(e.cr.reqStop) 364 } 365 if listener != nil { 366 listener.Close() 367 } 368 e.Halt.MarkDone() 369 }() 370 371 p("info: Essh.Start() in server.go: listening on "+ 372 "domain '%s', addr: '%s'", domain, e.cfg.EmbeddedSSHd.Addr) 373 for { 374 // TODO: fail2ban: notice bad login IPs and if too many, block the IP. 375 376 timeoutMillisec := 1000 377 err = listener.(*net.TCPListener).SetDeadline(time.Now().Add(time.Duration(timeoutMillisec) * time.Millisecond)) 378 panicOn(err) 379 nConn, err := listener.Accept() 380 if err != nil { 381 // simple timeout, check if stop requested 382 // 'accept tcp 127.0.0.1:54796: i/o timeout' 383 // p("simple timeout err: '%v'", err) 384 select { 385 case <-ctx.Done(): 386 return 387 case <-e.Halt.ReqStopChan(): 388 return 389 case u := <-e.addUserToDatabase: 390 p("received on e.addUserToDatabase, calling finishUserBuildout with supplied *User u: '%#v'", u) 391 _, _, _, err = e.cfg.HostDb.finishUserBuildout(u) 392 panicOn(err) 393 select { 394 case e.replyWithCreatedUser <- u: 395 //p("sent: e.replyWithCreatedUser <- u") 396 case <-e.Halt.ReqStopChan(): 397 return 398 } 399 400 case u := <-e.delUserReq: 401 //p("received on e.delUserReq: '%v'", u.MyLogin) 402 err = e.cfg.HostDb.DelUser(u.MyLogin) 403 ok := (err == nil) 404 405 select { 406 case e.replyWithDeletedDone <- ok: 407 case <-e.Halt.ReqStopChan(): 408 e.Halt.MarkDone() 409 return 410 } 411 412 case newSigner := <-e.updateHostKey: 413 //p("we got newSigner") 414 a.HostKey = newSigner 415 416 default: 417 // no stop request, keep looping 418 } 419 continue 420 } 421 p("info: Essh.Start() in server.go: accepted new connection on "+ 422 "domain '%s', addr: '%s'", domain, e.cfg.EmbeddedSSHd.Addr) 423 424 attempt := NewPerAttempt(a, e.cfg) 425 attempt.SetupAuthRequirements() 426 427 // We explicitly do not use a go routine here. 428 // We *want* and require serializing all authentication 429 // attempts, so that we don't get our user database 430 // into an inconsistent state by having multiple 431 // writers at once. This library is intended 432 // for light use (one user is the common case) anyway, so 433 // correctness and lack of corruption is much more 434 // important than concurrency of login processing. 435 // After login we let connections proceed freely 436 // and in parallel. 437 p("PRE attempt.PerConnection, server %v", e.cfg.EmbeddedSSHd.Addr) 438 attempt.PerConnection(ctx, nConn, nil) 439 p("POST attempt.PerConnection, server %v", e.cfg.EmbeddedSSHd.Addr) 440 } 441 }() 442 } 443 444 func (a *PerAttempt) PerConnection(ctx context.Context, nConn net.Conn, ca *ConnectionAlert) error { 445 446 loc := a.cfg.EmbeddedSSHd.Addr 447 p("%v Accept has returned an nConn... sshego PerConnection(). doing handshake. This is where the server handshake transport and kexLoop are started: ssh.NewServerConn().", loc) 448 449 // Before use, a handshake must be performed on the incoming 450 // net.Conn. 451 452 sshConn, chans, reqs, err := ssh.NewServerConn(ctx, nConn, a.Config) 453 if err != nil { 454 msg := fmt.Errorf("%v sshego PerAttempt.PerConnection() did not handshake: %v", loc, err) 455 p(msg.Error()) 456 return msg 457 } 458 459 p("%s done with handshake. handlers in force: '%s'", loc, a.cfg.ChannelHandlerSummary()) 460 461 p("server %s sees new SSH connection from %s (%s)", sshConn.LocalAddr(), sshConn.RemoteAddr(), sshConn.ClientVersion()) 462 463 // The incoming Request channel must be serviced. 464 // Discard all global out-of-band Requests, except for keepalives. 465 go DiscardRequestsExceptKeepalives(ctx, reqs, a.cfg.Esshd.Halt.ReqStopChan()) 466 // Accept all channels 467 go a.cfg.handleChannels(ctx, chans, sshConn, ca) 468 469 return nil 470 } 471 472 // DiscardRequestsExceptKeepalives accepts and responds 473 // to requests of type "keepalive@sshego.glycerine.github.com" 474 // that want reply; these are used as ping/pong messages 475 // to detect ssh connection failure. 476 func DiscardRequestsExceptKeepalives(ctx context.Context, in <-chan *ssh.Request, reqStop chan struct{}) { 477 478 for { 479 select { 480 case req, stillOpen := <-in: 481 if !stillOpen { 482 return 483 } 484 if req != nil && req.WantReply { 485 if req.Type != "keepalive@sshego.glycerine.github.com" || len(req.Payload) == 0 { 486 req.Reply(false, nil) 487 continue 488 } 489 // respond to keepalive pings 490 var ping KeepAlivePing 491 _, err := ping.UnmarshalMsg(req.Payload) 492 if err != nil { 493 req.Reply(false, nil) 494 continue 495 } 496 497 now := time.Now() 498 //p("sshego server.go: discardRequestsExceptKeepalives sees keepalive %v! ping.Sent: '%v'. setting replied to now='%v'", ping.Serial, ping.Sent, now) 499 500 ping.Replied = now 501 pingReplyBy, err := ping.MarshalMsg(nil) 502 panicOn(err) 503 req.Reply(true, pingReplyBy) 504 } 505 case <-reqStop: 506 return 507 case <-ctx.Done(): 508 return 509 } 510 } 511 } 512 513 type TOTP struct { 514 UserEmail string 515 Issuer string 516 Key *otp.Key 517 QRcodePng []byte 518 } 519 520 func (w *TOTP) String() string { 521 return w.Key.String() 522 } 523 524 func (w *TOTP) SaveToFile(path string) (secretPath, qrPath string, err error) { 525 secretPath = path 526 var fd *os.File 527 fd, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 528 if err != nil { 529 return 530 } 531 defer fd.Close() 532 _, err = fmt.Fprintf(fd, "%v\n", w.Key.String()) 533 if err != nil { 534 return 535 } 536 537 // serialize qr-code too 538 if len(w.QRcodePng) > 0 { 539 qrPath = path + "-qrcode.png" 540 var qr *os.File 541 qr, err = os.OpenFile(qrPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) 542 if err != nil { 543 return 544 } 545 defer qr.Close() 546 _, err = qr.Write(w.QRcodePng) 547 if err != nil { 548 return 549 } 550 } 551 return 552 } 553 554 func (w *TOTP) LoadFromFile(path string) error { 555 fd, err := os.Open(path) 556 if err != nil { 557 return err 558 } 559 defer fd.Close() 560 var orig string 561 _, err = fmt.Fscanf(fd, "%s", &orig) 562 if err != nil { 563 return err 564 } 565 w.Key, err = otp.NewKeyFromURL(orig) 566 return err 567 } 568 569 func (w *TOTP) IsValid(passcode string, mylogin string) bool { 570 valid := totp.Validate(passcode, w.Key.Secret()) 571 572 if valid { 573 p("Login '%s' successfully used their "+ 574 "Time-based-One-Time-Password!", 575 mylogin) 576 } else { 577 p("Login '%s' failed at Time-based-One-"+ 578 "Time-Password attempt", 579 mylogin) 580 } 581 return valid 582 } 583 584 func NewTOTP(userEmail, issuer string) (w *TOTP, err error) { 585 586 key, err := totp.Generate(totp.GenerateOpts{ 587 Issuer: issuer, 588 AccountName: userEmail, 589 }) 590 if err != nil { 591 return nil, err 592 } 593 594 w = &TOTP{ 595 UserEmail: userEmail, 596 Issuer: issuer, 597 Key: key, 598 } 599 600 // Convert TOTP key into a QR code encoded as a PNG image. 601 var buf bytes.Buffer 602 img, err := key.Image(200, 200) 603 png.Encode(&buf, img) 604 w.QRcodePng = buf.Bytes() 605 return w, err 606 } 607 608 var keyFail = errors.New("keyboard-interactive failed") 609 610 const passwordChallenge = "password: " 611 const gauthChallenge = "google-authenticator-code: " 612 613 func (a *PerAttempt) KeyboardInteractiveCallback(ctx context.Context, conn ssh.ConnMetadata, challenge ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) { 614 //p("KeyboardInteractiveCallback top: a.PublicKeyOK=%v, a.OneTimeOK=%v", a.PublicKeyOK, a.OneTimeOK) 615 616 // no matter what happens, temper DDOS/many fast login attemps by 617 // waiting 1-2 seconds before replying. 618 defer wait() 619 620 mylogin := conn.User() 621 now := time.Now().UTC() 622 remoteAddr := conn.RemoteAddr() 623 624 user, knownUser := a.cfg.HostDb.Persist.Users.Get2(mylogin) 625 626 // don't reveal that the user is unknown by 627 // failing early without a challenge. 628 629 // Unless, of course, we have no call for 630 // interactive challenge at all... in which 631 // case, why are we in this routine? We 632 // should not be! 633 if a.cfg.SkipPassphrase && a.cfg.SkipTOTP { 634 panic("should not be in the KeyboardInteractiveCallback at all!") 635 } 636 637 firstPassOK := false 638 timeOK := false 639 640 var totpIdx int // where in the arrays the totp info is located 641 var chal []string 642 var echoAnswers []bool 643 if !a.cfg.SkipPassphrase { 644 chal = append(chal, passwordChallenge) 645 echoAnswers = append(echoAnswers, false) 646 totpIdx++ 647 } 648 if !a.cfg.SkipTOTP { 649 chal = append(chal, gauthChallenge) 650 echoAnswers = append(echoAnswers, true) 651 } 652 653 ans, err := challenge(ctx, mylogin, 654 fmt.Sprintf("login for %s:", mylogin), 655 chal, 656 echoAnswers) 657 if err != nil { 658 p("actuall err is '%s', but we always return keyFail", err) 659 return nil, keyFail 660 } 661 662 if !knownUser { 663 log.Printf("unrecognized login '%s' from remoteAddr '%s' at %v", 664 mylogin, remoteAddr, now) 665 return nil, keyFail 666 } 667 668 p("KeyboardInteractiveCallback sees login "+ 669 "attempt for recognized user '%v'", user.MyLogin) 670 671 if a.cfg.SkipPassphrase || user.MatchingHashAndPw(ans[0]) { 672 firstPassOK = true 673 } 674 p("KeyboardInteractiveCallback, first pass-phrase accepted: %v; ans[0] was user-attempting-login provided this cleartext: '%s'; our stored scrypted pw is: '%s'", firstPassOK, ans[0], user.ScryptedPassword) 675 user.RestoreTotp() 676 677 if a.cfg.SkipTOTP || (len(ans[totpIdx]) > 0 && user.oneTime.IsValid(ans[totpIdx], mylogin)) { 678 timeOK = true 679 } 680 681 ok := firstPassOK && timeOK 682 if ok { 683 a.OneTimeOK = true 684 if !a.PublicKeyOK { 685 p("keyboard interactive succeeded however public-key did not!, and we want to enforce *both*. Note that earlier we will have told the client that the public-key failed so that it will also do the keyboard-interactive which lets us do the 2FA/TOTP one-time-password/google-authenticator here.") 686 // must also be true 687 return nil, keyFail 688 } 689 prev := fmt.Sprintf("last login was at %v, from '%s'", 690 user.LastLoginTime.UTC(), user.LastLoginAddr) 691 challenge(ctx, fmt.Sprintf("user '%s' succesfully logged in", mylogin), 692 prev, nil, nil) 693 a.NoteLogin(user, now, conn) 694 return nil, nil 695 } 696 return nil, keyFail 697 } 698 699 func (a *PerAttempt) NoteLogin(user *User, now time.Time, conn ssh.ConnMetadata) { 700 user.LastLoginTime = now 701 user.LastLoginAddr = conn.RemoteAddr().String() 702 a.cfg.HostDb.save(lockit) 703 } 704 705 func (a *PerAttempt) AuthLogCallback(conn ssh.ConnMetadata, method string, err error) { 706 p("AuthLogCallback top: a.PublicKeyOK=%v, a.OneTimeOK=%v", a.PublicKeyOK, a.OneTimeOK) 707 708 if err == nil { 709 p("login success! auth-log-callback: user %q, method %q: %v", 710 conn.User(), method, err) 711 switch method { 712 case "keyboard-interactive": 713 a.OneTimeOK = true 714 case "publickey": 715 a.PublicKeyOK = true 716 } 717 } else { 718 p("login failure! auth-log-callback: user %q, method %q: %v", 719 conn.User(), method, err) 720 } 721 } 722 723 func (a *PerAttempt) PublicKeyCallback(c ssh.ConnMetadata, providedPubKey ssh.PublicKey) (perm *ssh.Permissions, rerr error) { 724 p("PublicKeyCallback top: a.PublicKeyOK=%v, a.OneTimeOK=%v", a.PublicKeyOK, a.OneTimeOK) 725 726 unknown := fmt.Errorf("unknown public key for %q", c.User()) 727 728 // if a.PublicKeyOK && !a.OneTimeOK { 729 // p("already validated public key, skipping on 2nd round") 730 // return nil, unknown 731 // } 732 733 mylogin := c.User() 734 735 valid, err := a.cfg.HostDb.ValidLogin(mylogin) 736 if !valid { 737 return nil, err 738 } 739 740 remoteAddr := c.RemoteAddr() 741 now := time.Now().UTC() 742 743 user, foundUser := a.cfg.HostDb.Persist.Users.Get2(mylogin) 744 if !foundUser { 745 log.Printf("unrecognized user '%s' from remoteAddr '%s' at %v", 746 mylogin, remoteAddr, now) 747 log.Printf("debug: my userdb is = '%s'\n", a.cfg.HostDb) 748 return nil, unknown 749 } 750 p("PublicKeyCallback sees login attempt for recognized user '%v'", user.MyLogin) 751 752 // update user.FirstLoginTm / LastLoginTm 753 754 providedPubKeyStr := string(providedPubKey.Marshal()) 755 providedPubKeyFinger := Fingerprint(providedPubKey) 756 757 // save the public key and when we saw it 758 loginRecord, already := user.SeenPubKey[providedPubKeyStr] 759 p("PublicKeyCallback: checking providedPubKey with fingerprint '%s'... already: %v, loginRecord: %s", 760 providedPubKeyFinger, already, loginRecord) 761 updated := loginRecord 762 updated.LastTm = now 763 if loginRecord.FirstTm.IsZero() { 764 updated.FirstTm = now 765 } 766 updated.SeenCount++ 767 // defer so we can set updated.AcceptedCount below before saving... 768 defer func() { 769 if foundUser && user != nil { 770 if user.SeenPubKey == nil { 771 user.SeenPubKey = make(map[string]LoginRecord) 772 } 773 user.SeenPubKey[providedPubKeyStr] = updated 774 // TODO: save() re-saves the whole database. Could be 775 // slow if the db gets big, but for one-two users, 776 // this won't take up more than a page anyway. 777 a.cfg.HostDb.save(lockit) // save the SeenPubKey update. 778 } 779 780 // check if we are actually okay now, because we saw 781 // the right key in the past; hence we have to reply 782 // okay now to actually accept the login when 783 784 if a.PublicKeyOK && a.OneTimeOK { 785 perm = nil 786 rerr = nil 787 p("PublicKeyCallback: defer sees pub-key and one-time okay, authorizing login") 788 } 789 }() 790 791 // load up the public key 792 p("loading public key from '%s'", user.PublicKeyPath) 793 onfilePubKey, err := LoadRSAPublicKey(user.PublicKeyPath) 794 if err != nil { 795 return nil, unknown 796 } 797 onfilePubKeyFinger := Fingerprint(onfilePubKey) 798 p("ok: successful load of public key from '%s'... pub fingerprint = '%s'", 799 user.PublicKeyPath, onfilePubKeyFinger) 800 801 // if a.State.AuthorizedKeysMap[string(providedPubKey.Marshal())] { 802 onfilePubKeyStr := string(onfilePubKey.Marshal()) 803 if onfilePubKeyStr == providedPubKeyStr { 804 p("we have a public key match for user '%s', key fingerprint = '%s'", mylogin, onfilePubKeyFinger) 805 updated.AcceptedCount++ 806 a.PublicKeyOK = true 807 // although we note this, we don't reveal this to the client. 808 if !a.OneTimeOK { 809 p("public-key succeeded however keyboard interactive did not (yet).") 810 return nil, unknown 811 } 812 return nil, nil 813 } else { 814 p("public key mismatch; onfilePubKey (%s) did not match providedPubKey (%s)", 815 onfilePubKeyFinger, Fingerprint(providedPubKey)) 816 } 817 return nil, unknown 818 } 819 820 func (a *AuthState) LoadPublicKeys(authorizedKeysPath string) error { 821 // Public key authentication is done by comparing 822 // the public key of a received connection 823 // with the entries in the authorized_keys file. 824 authorizedKeysBytes, err := ioutil.ReadFile(authorizedKeysPath) 825 if err != nil { 826 return fmt.Errorf("Failed to load authorized_keys, err: %v", err) 827 } 828 829 for len(authorizedKeysBytes) > 0 { 830 pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes) 831 if err != nil { 832 return fmt.Errorf("failed Parsing public keys: %v", err) 833 } 834 835 a.AuthorizedKeysMap[string(pubKey.Marshal())] = true 836 authorizedKeysBytes = rest 837 } 838 return nil 839 } 840 841 func (a *PerAttempt) SetupAuthRequirements() { 842 a.cfg.Mut.Lock() 843 defer a.cfg.Mut.Unlock() 844 a.SetTripleConfig() 845 if a.cfg.SkipRSA { 846 a.Config.PublicKeyCallback = nil 847 a.PublicKeyOK = true 848 } 849 if a.cfg.SkipPassphrase && a.cfg.SkipTOTP { 850 a.Config.KeyboardInteractiveCallback = nil 851 a.OneTimeOK = true 852 } 853 } 854 855 // see vendor/github.com/glycerine/xcryptossh/kex.go 856 const ( 857 kexAlgoCurve25519SHA256 = "curve25519-sha256@libssh.org" 858 ) 859 860 // SetTripleConfig establishes an a.State.Config that requires 861 // *both* public key and one-time password validation. 862 func (a *PerAttempt) SetTripleConfig() { 863 a.Config = &ssh.ServerConfig{ 864 PublicKeyCallback: a.PublicKeyCallback, 865 KeyboardInteractiveCallback: a.KeyboardInteractiveCallback, 866 AuthLogCallback: a.AuthLogCallback, 867 Config: ssh.Config{ 868 Ciphers: getCiphers(), 869 KeyExchanges: []string{kexAlgoCurve25519SHA256}, 870 Halt: a.cfg.Halt, 871 }, 872 ServerVersion: "SSH-2.0-OpenSSH_6.9", 873 } 874 a.Config.AddHostKey(a.State.HostKey) 875 } 876 877 //func StartServer() { 878 // //go newServer(c1, serverConfig) 879 //} 880 881 func (a *AuthState) LoadHostKey(path string) error { 882 883 //a.Config.AddHostKey(a.Signers["rsa"]) 884 885 privateBytes, err := ioutil.ReadFile(path) 886 if err != nil { 887 return fmt.Errorf("Failed to load private key from path '%s': %s", 888 path, err) 889 } 890 891 private, err := ssh.ParsePrivateKey(privateBytes) 892 if err != nil { 893 return fmt.Errorf("Failed to parse private key '%s': %s", 894 path, err) 895 } 896 897 a.HostKey = private 898 return nil 899 } 900 901 // wait between 1-2 seconds 902 func wait() { 903 // 1000 - 2000 millisecond 904 n := 1000 + CryptoRandNonNegInt(1000) 905 time.Sleep(time.Millisecond * time.Duration(n)) 906 } 907 908 // write NewUserReply + MarshalMsg(goback) back to our remote client 909 func writeBackHelper(goback *User, nConn net.Conn) error { 910 //p("top of writeBackHelper") 911 err := nConn.SetWriteDeadline(time.Now().Add(time.Second * 5)) 912 panicOn(err) 913 914 _, err = nConn.Write(NewUserReply) 915 panicOn(err) 916 917 err = nConn.SetWriteDeadline(time.Now().Add(time.Second * 5)) 918 panicOn(err) 919 920 wri := msgp.NewWriter(nConn) 921 err = goback.EncodeMsg(wri) 922 panicOn(err) 923 924 p("end of writeBackHelper") 925 wri.Flush() 926 nConn.Close() 927 return nil 928 }