@@ -17,30 +17,14 @@ import (
1717	"code.gitea.io/gitea/modules/log" 
1818	"code.gitea.io/gitea/modules/setting" 
1919	"code.gitea.io/gitea/modules/util" 
20- )
2120
22- //  _____          __  .__                 .__                  .___ 
23- // /  _  \  __ ___/  |_|  |__   ___________|__|_______ ____   __| _/ 
24- // /  /_\  \|  |  \   __\  |  \ /  _ \_  __ \  \___   // __ \ / __ | 
25- // /    |    \  |  /|  | |   Y  (  <_> )  | \/  |/    /\  ___// /_/ | 
26- // \____|__  /____/ |__| |___|  /\____/|__|  |__/_____ \\___  >____ | 
27- //         \/                 \/                      \/    \/     \/ 
28- // ____  __. 
29- // |    |/ _|____ ___.__. ______ 
30- // |      <_/ __ <   |  |/  ___/ 
31- // |    |  \  ___/\___  |\___ \ 
32- // |____|__ \___  > ____/____  > 
33- //         \/   \/\/         \/ 
34- // 
35- // This file contains functions for creating authorized_keys files 
36- // 
37- // There is a dependence on the database within RegeneratePublicKeys however most of these functions probably belong in a module 
38- 
39- const  (
40- 	tplCommentPrefix  =  `# gitea public key` 
41- 	tplPublicKey      =  tplCommentPrefix  +  "\n "  +  `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s`  +  "\n " 
21+ 	"golang.org/x/crypto/ssh" 
4222)
4323
24+ // AuthorizedStringCommentPrefix is a magic tag 
25+ // some functions like RegeneratePublicKeys needs this tag to skip the keys generated by Gitea, while keep other keys 
26+ const  AuthorizedStringCommentPrefix  =  `# gitea public key` 
27+ 
4428var  sshOpLocker  sync.Mutex 
4529
4630func  WithSSHOpLocker (f  func () error ) error  {
@@ -50,17 +34,45 @@ func WithSSHOpLocker(f func() error) error {
5034}
5135
5236// AuthorizedStringForKey creates the authorized keys string appropriate for the provided key 
53- func  AuthorizedStringForKey (key  * PublicKey ) string  {
37+ func  AuthorizedStringForKey (key  * PublicKey ) ( string ,  error )  {
5438	sb  :=  & strings.Builder {}
55- 	_  =  setting .SSH .AuthorizedKeysCommandTemplateTemplate .Execute (sb , map [string ]any {
39+ 	_ , err  :=  writeAuthorizedStringForKey (key , sb )
40+ 	return  sb .String (), err 
41+ }
42+ 
43+ // WriteAuthorizedStringForValidKey writes the authorized key for the provided key. If the key is invalid, it does nothing. 
44+ func  WriteAuthorizedStringForValidKey (key  * PublicKey , w  io.Writer ) error  {
45+ 	validKey , err  :=  writeAuthorizedStringForKey (key , w )
46+ 	if  ! validKey  {
47+ 		log .Debug ("WriteAuthorizedStringForValidKey: key %s is not valid: %v" , key , err )
48+ 		return  nil 
49+ 	}
50+ 	return  err 
51+ }
52+ 
53+ func  writeAuthorizedStringForKey (key  * PublicKey , w  io.Writer ) (keyValid  bool , err  error ) {
54+ 	const  tpl  =  AuthorizedStringCommentPrefix  +  "\n "  +  `command=%s,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,no-user-rc,restrict %s %s`  +  "\n " 
55+ 	pubKey , _ , _ , _ , err  :=  ssh .ParseAuthorizedKey ([]byte (key .Content ))
56+ 	if  err  !=  nil  {
57+ 		return  false , err 
58+ 	}
59+ 	// now the key is valid, the code below could only return template/IO related errors 
60+ 	sbCmd  :=  & strings.Builder {}
61+ 	err  =  setting .SSH .AuthorizedKeysCommandTemplateTemplate .Execute (sbCmd , map [string ]any {
5662		"AppPath" :     util .ShellEscape (setting .AppPath ),
5763		"AppWorkPath" : util .ShellEscape (setting .AppWorkPath ),
5864		"CustomConf" :  util .ShellEscape (setting .CustomConf ),
5965		"CustomPath" :  util .ShellEscape (setting .CustomPath ),
6066		"Key" :         key ,
6167	})
62- 
63- 	return  fmt .Sprintf (tplPublicKey , util .ShellEscape (sb .String ()), key .Content )
68+ 	if  err  !=  nil  {
69+ 		return  true , err 
70+ 	}
71+ 	sshCommandEscaped  :=  util .ShellEscape (sbCmd .String ())
72+ 	sshKeyMarshalled  :=  strings .TrimSpace (string (ssh .MarshalAuthorizedKey (pubKey )))
73+ 	sshKeyComment  :=  fmt .Sprintf ("user-%d" , key .OwnerID )
74+ 	_ , err  =  fmt .Fprintf (w , tpl , sshCommandEscaped , sshKeyMarshalled , sshKeyComment )
75+ 	return  true , err 
6476}
6577
6678// appendAuthorizedKeysToFile appends new SSH keys' content to authorized_keys file. 
@@ -112,18 +124,17 @@ func appendAuthorizedKeysToFile(keys ...*PublicKey) error {
112124		if  key .Type  ==  KeyTypePrincipal  {
113125			continue 
114126		}
115- 		if  _ ,  err  =  f . WriteString (key . AuthorizedString () ); err  !=  nil  {
127+ 		if  err  =  WriteAuthorizedStringForValidKey (key ,  f ); err  !=  nil  {
116128			return  err 
117129		}
118130	}
119131	return  nil 
120132}
121133
122134// RegeneratePublicKeys regenerates the authorized_keys file 
123- func  RegeneratePublicKeys (ctx  context.Context , t  io.StringWriter ) error  {
135+ func  RegeneratePublicKeys (ctx  context.Context , t  io.Writer ) error  {
124136	if  err  :=  db .GetEngine (ctx ).Where ("type != ?" , KeyTypePrincipal ).Iterate (new (PublicKey ), func (idx  int , bean  any ) (err  error ) {
125- 		_ , err  =  t .WriteString ((bean .(* PublicKey )).AuthorizedString ())
126- 		return  err 
137+ 		return  WriteAuthorizedStringForValidKey (bean .(* PublicKey ), t )
127138	}); err  !=  nil  {
128139		return  err 
129140	}
@@ -144,11 +155,11 @@ func RegeneratePublicKeys(ctx context.Context, t io.StringWriter) error {
144155		scanner  :=  bufio .NewScanner (f )
145156		for  scanner .Scan () {
146157			line  :=  scanner .Text ()
147- 			if  strings .HasPrefix (line , tplCommentPrefix ) {
158+ 			if  strings .HasPrefix (line , AuthorizedStringCommentPrefix ) {
148159				scanner .Scan ()
149160				continue 
150161			}
151- 			_ , err  =  t .WriteString (line   +   "\n " )
162+ 			_ , err  =  io .WriteString (t ,  line + "\n " )
152163			if  err  !=  nil  {
153164				return  err 
154165			}
0 commit comments