@@ -10,6 +10,7 @@ import (
1010 "io"
1111 "os"
1212 "path/filepath"
13+ "regexp"
1314 "strings"
1415 "sync"
1516
@@ -50,13 +51,28 @@ func WriteAuthorizedStringForValidKey(key *PublicKey, w io.Writer) error {
5051 return err
5152}
5253
54+ // principalRegexp expresses whether a principal is considered valid.
55+ // This reverse engineers how sshd parses the authorized keys file,
56+ // see e.g. https://github.com/openssh/openssh-portable/blob/32deb00b38b4ee2b3302f261ea1e68c04e020a08/auth2-pubkeyfile.c#L221-L256
57+ // Any newline or # comment will be stripped when parsing, so don't allow
58+ // those. Also, if any space or tab is present in the principal, the part
59+ // proceeding this would be parsed as an option, so just avoid any whitespace
60+ // altogether.
61+ var principalRegexp = regexp .MustCompile (`^[^\s#]+$` )
62+
5363func writeAuthorizedStringForKey (key * PublicKey , w io.Writer ) (keyValid bool , err error ) {
5464 const tpl = AuthorizedStringCommentPrefix + "\n " + `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s` + "\n "
5565
5666 var sshKey string
5767
5868 if key .Type == KeyTypePrincipal {
59- sshKey = fmt .Sprintf ("%s # user-%d" , key .Content , key .OwnerID )
69+ principal := strings .TrimSpace (key .Content )
70+
71+ if matched := principalRegexp .MatchString (principal ); ! matched {
72+ return false , fmt .Errorf ("does not match %s" , principalRegexp .String ())
73+ }
74+
75+ sshKey = fmt .Sprintf ("%s # user-%d" , principal , key .OwnerID )
6076 } else {
6177 pubKey , _ , _ , _ , err := ssh .ParseAuthorizedKey ([]byte (key .Content ))
6278 if err != nil {
0 commit comments