1// Copyright 2014 The Gogs Authors. All rights reserved.
2// Copyright 2019 The Gitea Authors. All rights reserved.
3// Use of this source code is governed by a MIT-style
4// license that can be found in the LICENSE file.
5
6package asymkey
7
8import (
9	"context"
10	"fmt"
11	"strings"
12	"time"
13
14	"code.gitea.io/gitea/models/auth"
15	"code.gitea.io/gitea/models/db"
16	"code.gitea.io/gitea/models/perm"
17	user_model "code.gitea.io/gitea/models/user"
18	"code.gitea.io/gitea/modules/log"
19	"code.gitea.io/gitea/modules/timeutil"
20	"code.gitea.io/gitea/modules/util"
21
22	"golang.org/x/crypto/ssh"
23	"xorm.io/builder"
24)
25
26// KeyType specifies the key type
27type KeyType int
28
29const (
30	// KeyTypeUser specifies the user key
31	KeyTypeUser = iota + 1
32	// KeyTypeDeploy specifies the deploy key
33	KeyTypeDeploy
34	// KeyTypePrincipal specifies the authorized principal key
35	KeyTypePrincipal
36)
37
38// PublicKey represents a user or deploy SSH public key.
39type PublicKey struct {
40	ID            int64           `xorm:"pk autoincr"`
41	OwnerID       int64           `xorm:"INDEX NOT NULL"`
42	Name          string          `xorm:"NOT NULL"`
43	Fingerprint   string          `xorm:"INDEX NOT NULL"`
44	Content       string          `xorm:"TEXT NOT NULL"`
45	Mode          perm.AccessMode `xorm:"NOT NULL DEFAULT 2"`
46	Type          KeyType         `xorm:"NOT NULL DEFAULT 1"`
47	LoginSourceID int64           `xorm:"NOT NULL DEFAULT 0"`
48
49	CreatedUnix       timeutil.TimeStamp `xorm:"created"`
50	UpdatedUnix       timeutil.TimeStamp `xorm:"updated"`
51	HasRecentActivity bool               `xorm:"-"`
52	HasUsed           bool               `xorm:"-"`
53	Verified          bool               `xorm:"NOT NULL DEFAULT false"`
54}
55
56func init() {
57	db.RegisterModel(new(PublicKey))
58}
59
60// AfterLoad is invoked from XORM after setting the values of all fields of this object.
61func (key *PublicKey) AfterLoad() {
62	key.HasUsed = key.UpdatedUnix > key.CreatedUnix
63	key.HasRecentActivity = key.UpdatedUnix.AddDuration(7*24*time.Hour) > timeutil.TimeStampNow()
64}
65
66// OmitEmail returns content of public key without email address.
67func (key *PublicKey) OmitEmail() string {
68	return strings.Join(strings.Split(key.Content, " ")[:2], " ")
69}
70
71// AuthorizedString returns formatted public key string for authorized_keys file.
72//
73// TODO: Consider dropping this function
74func (key *PublicKey) AuthorizedString() string {
75	return AuthorizedStringForKey(key)
76}
77
78func addKey(e db.Engine, key *PublicKey) (err error) {
79	if len(key.Fingerprint) == 0 {
80		key.Fingerprint, err = calcFingerprint(key.Content)
81		if err != nil {
82			return err
83		}
84	}
85
86	// Save SSH key.
87	if _, err = e.Insert(key); err != nil {
88		return err
89	}
90
91	return appendAuthorizedKeysToFile(key)
92}
93
94// AddPublicKey adds new public key to database and authorized_keys file.
95func AddPublicKey(ownerID int64, name, content string, authSourceID int64) (*PublicKey, error) {
96	log.Trace(content)
97
98	fingerprint, err := calcFingerprint(content)
99	if err != nil {
100		return nil, err
101	}
102
103	ctx, committer, err := db.TxContext()
104	if err != nil {
105		return nil, err
106	}
107	defer committer.Close()
108	sess := db.GetEngine(ctx)
109
110	if err := checkKeyFingerprint(sess, fingerprint); err != nil {
111		return nil, err
112	}
113
114	// Key name of same user cannot be duplicated.
115	has, err := sess.
116		Where("owner_id = ? AND name = ?", ownerID, name).
117		Get(new(PublicKey))
118	if err != nil {
119		return nil, err
120	} else if has {
121		return nil, ErrKeyNameAlreadyUsed{ownerID, name}
122	}
123
124	key := &PublicKey{
125		OwnerID:       ownerID,
126		Name:          name,
127		Fingerprint:   fingerprint,
128		Content:       content,
129		Mode:          perm.AccessModeWrite,
130		Type:          KeyTypeUser,
131		LoginSourceID: authSourceID,
132	}
133	if err = addKey(sess, key); err != nil {
134		return nil, fmt.Errorf("addKey: %v", err)
135	}
136
137	return key, committer.Commit()
138}
139
140// GetPublicKeyByID returns public key by given ID.
141func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
142	key := new(PublicKey)
143	has, err := db.GetEngine(db.DefaultContext).
144		ID(keyID).
145		Get(key)
146	if err != nil {
147		return nil, err
148	} else if !has {
149		return nil, ErrKeyNotExist{keyID}
150	}
151	return key, nil
152}
153
154func searchPublicKeyByContentWithEngine(e db.Engine, content string) (*PublicKey, error) {
155	key := new(PublicKey)
156	has, err := e.
157		Where("content like ?", content+"%").
158		Get(key)
159	if err != nil {
160		return nil, err
161	} else if !has {
162		return nil, ErrKeyNotExist{}
163	}
164	return key, nil
165}
166
167// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
168// and returns public key found.
169func SearchPublicKeyByContent(content string) (*PublicKey, error) {
170	return searchPublicKeyByContentWithEngine(db.GetEngine(db.DefaultContext), content)
171}
172
173func searchPublicKeyByContentExactWithEngine(e db.Engine, content string) (*PublicKey, error) {
174	key := new(PublicKey)
175	has, err := e.
176		Where("content = ?", content).
177		Get(key)
178	if err != nil {
179		return nil, err
180	} else if !has {
181		return nil, ErrKeyNotExist{}
182	}
183	return key, nil
184}
185
186// SearchPublicKeyByContentExact searches content
187// and returns public key found.
188func SearchPublicKeyByContentExact(content string) (*PublicKey, error) {
189	return searchPublicKeyByContentExactWithEngine(db.GetEngine(db.DefaultContext), content)
190}
191
192// SearchPublicKey returns a list of public keys matching the provided arguments.
193func SearchPublicKey(uid int64, fingerprint string) ([]*PublicKey, error) {
194	keys := make([]*PublicKey, 0, 5)
195	cond := builder.NewCond()
196	if uid != 0 {
197		cond = cond.And(builder.Eq{"owner_id": uid})
198	}
199	if fingerprint != "" {
200		cond = cond.And(builder.Eq{"fingerprint": fingerprint})
201	}
202	return keys, db.GetEngine(db.DefaultContext).Where(cond).Find(&keys)
203}
204
205// ListPublicKeys returns a list of public keys belongs to given user.
206func ListPublicKeys(uid int64, listOptions db.ListOptions) ([]*PublicKey, error) {
207	sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", uid, KeyTypePrincipal)
208	if listOptions.Page != 0 {
209		sess = db.SetSessionPagination(sess, &listOptions)
210
211		keys := make([]*PublicKey, 0, listOptions.PageSize)
212		return keys, sess.Find(&keys)
213	}
214
215	keys := make([]*PublicKey, 0, 5)
216	return keys, sess.Find(&keys)
217}
218
219// CountPublicKeys count public keys a user has
220func CountPublicKeys(userID int64) (int64, error) {
221	sess := db.GetEngine(db.DefaultContext).Where("owner_id = ? AND type != ?", userID, KeyTypePrincipal)
222	return sess.Count(&PublicKey{})
223}
224
225// ListPublicKeysBySource returns a list of synchronized public keys for a given user and login source.
226func ListPublicKeysBySource(uid, authSourceID int64) ([]*PublicKey, error) {
227	keys := make([]*PublicKey, 0, 5)
228	return keys, db.GetEngine(db.DefaultContext).
229		Where("owner_id = ? AND login_source_id = ?", uid, authSourceID).
230		Find(&keys)
231}
232
233// UpdatePublicKeyUpdated updates public key use time.
234func UpdatePublicKeyUpdated(id int64) error {
235	// Check if key exists before update as affected rows count is unreliable
236	//    and will return 0 affected rows if two updates are made at the same time
237	if cnt, err := db.GetEngine(db.DefaultContext).ID(id).Count(&PublicKey{}); err != nil {
238		return err
239	} else if cnt != 1 {
240		return ErrKeyNotExist{id}
241	}
242
243	_, err := db.GetEngine(db.DefaultContext).ID(id).Cols("updated_unix").Update(&PublicKey{
244		UpdatedUnix: timeutil.TimeStampNow(),
245	})
246	if err != nil {
247		return err
248	}
249	return nil
250}
251
252// DeletePublicKeys does the actual key deletion but does not update authorized_keys file.
253func DeletePublicKeys(ctx context.Context, keyIDs ...int64) error {
254	if len(keyIDs) == 0 {
255		return nil
256	}
257
258	_, err := db.GetEngine(ctx).In("id", keyIDs).Delete(new(PublicKey))
259	return err
260}
261
262// PublicKeysAreExternallyManaged returns whether the provided KeyID represents an externally managed Key
263func PublicKeysAreExternallyManaged(keys []*PublicKey) ([]bool, error) {
264	sources := make([]*auth.Source, 0, 5)
265	externals := make([]bool, len(keys))
266keyloop:
267	for i, key := range keys {
268		if key.LoginSourceID == 0 {
269			externals[i] = false
270			continue keyloop
271		}
272
273		var source *auth.Source
274
275	sourceloop:
276		for _, s := range sources {
277			if s.ID == key.LoginSourceID {
278				source = s
279				break sourceloop
280			}
281		}
282
283		if source == nil {
284			var err error
285			source, err = auth.GetSourceByID(key.LoginSourceID)
286			if err != nil {
287				if auth.IsErrSourceNotExist(err) {
288					externals[i] = false
289					sources[i] = &auth.Source{
290						ID: key.LoginSourceID,
291					}
292					continue keyloop
293				}
294				return nil, err
295			}
296		}
297
298		if sshKeyProvider, ok := source.Cfg.(auth.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
299			// Disable setting SSH keys for this user
300			externals[i] = true
301		}
302	}
303
304	return externals, nil
305}
306
307// PublicKeyIsExternallyManaged returns whether the provided KeyID represents an externally managed Key
308func PublicKeyIsExternallyManaged(id int64) (bool, error) {
309	key, err := GetPublicKeyByID(id)
310	if err != nil {
311		return false, err
312	}
313	if key.LoginSourceID == 0 {
314		return false, nil
315	}
316	source, err := auth.GetSourceByID(key.LoginSourceID)
317	if err != nil {
318		if auth.IsErrSourceNotExist(err) {
319			return false, nil
320		}
321		return false, err
322	}
323	if sshKeyProvider, ok := source.Cfg.(auth.SSHKeyProvider); ok && sshKeyProvider.ProvidesSSHKeys() {
324		// Disable setting SSH keys for this user
325		return true, nil
326	}
327	return false, nil
328}
329
330// deleteKeysMarkedForDeletion returns true if ssh keys needs update
331func deleteKeysMarkedForDeletion(keys []string) (bool, error) {
332	// Start session
333	ctx, committer, err := db.TxContext()
334	if err != nil {
335		return false, err
336	}
337	defer committer.Close()
338	sess := db.GetEngine(ctx)
339
340	// Delete keys marked for deletion
341	var sshKeysNeedUpdate bool
342	for _, KeyToDelete := range keys {
343		key, err := searchPublicKeyByContentWithEngine(sess, KeyToDelete)
344		if err != nil {
345			log.Error("SearchPublicKeyByContent: %v", err)
346			continue
347		}
348		if err = DeletePublicKeys(ctx, key.ID); err != nil {
349			log.Error("deletePublicKeys: %v", err)
350			continue
351		}
352		sshKeysNeedUpdate = true
353	}
354
355	if err := committer.Commit(); err != nil {
356		return false, err
357	}
358
359	return sshKeysNeedUpdate, nil
360}
361
362// AddPublicKeysBySource add a users public keys. Returns true if there are changes.
363func AddPublicKeysBySource(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
364	var sshKeysNeedUpdate bool
365	for _, sshKey := range sshPublicKeys {
366		var err error
367		found := false
368		keys := []byte(sshKey)
369	loop:
370		for len(keys) > 0 && err == nil {
371			var out ssh.PublicKey
372			// We ignore options as they are not relevant to Gitea
373			out, _, _, keys, err = ssh.ParseAuthorizedKey(keys)
374			if err != nil {
375				break loop
376			}
377			found = true
378			marshalled := string(ssh.MarshalAuthorizedKey(out))
379			marshalled = marshalled[:len(marshalled)-1]
380			sshKeyName := fmt.Sprintf("%s-%s", s.Name, ssh.FingerprintSHA256(out))
381
382			if _, err := AddPublicKey(usr.ID, sshKeyName, marshalled, s.ID); err != nil {
383				if IsErrKeyAlreadyExist(err) {
384					log.Trace("AddPublicKeysBySource[%s]: Public SSH Key %s already exists for user", sshKeyName, usr.Name)
385				} else {
386					log.Error("AddPublicKeysBySource[%s]: Error adding Public SSH Key for user %s: %v", sshKeyName, usr.Name, err)
387				}
388			} else {
389				log.Trace("AddPublicKeysBySource[%s]: Added Public SSH Key for user %s", sshKeyName, usr.Name)
390				sshKeysNeedUpdate = true
391			}
392		}
393		if !found && err != nil {
394			log.Warn("AddPublicKeysBySource[%s]: Skipping invalid Public SSH Key for user %s: %v", s.Name, usr.Name, sshKey)
395		}
396	}
397	return sshKeysNeedUpdate
398}
399
400// SynchronizePublicKeys updates a users public keys. Returns true if there are changes.
401func SynchronizePublicKeys(usr *user_model.User, s *auth.Source, sshPublicKeys []string) bool {
402	var sshKeysNeedUpdate bool
403
404	log.Trace("synchronizePublicKeys[%s]: Handling Public SSH Key synchronization for user %s", s.Name, usr.Name)
405
406	// Get Public Keys from DB with current LDAP source
407	var giteaKeys []string
408	keys, err := ListPublicKeysBySource(usr.ID, s.ID)
409	if err != nil {
410		log.Error("synchronizePublicKeys[%s]: Error listing Public SSH Keys for user %s: %v", s.Name, usr.Name, err)
411	}
412
413	for _, v := range keys {
414		giteaKeys = append(giteaKeys, v.OmitEmail())
415	}
416
417	// Process the provided keys to remove duplicates and name part
418	var providedKeys []string
419	for _, v := range sshPublicKeys {
420		sshKeySplit := strings.Split(v, " ")
421		if len(sshKeySplit) > 1 {
422			key := strings.Join(sshKeySplit[:2], " ")
423			if !util.ExistsInSlice(key, providedKeys) {
424				providedKeys = append(providedKeys, key)
425			}
426		}
427	}
428
429	// Check if Public Key sync is needed
430	if util.IsEqualSlice(giteaKeys, providedKeys) {
431		log.Trace("synchronizePublicKeys[%s]: Public Keys are already in sync for %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
432		return false
433	}
434	log.Trace("synchronizePublicKeys[%s]: Public Key needs update for user %s (Source:%v/DB:%v)", s.Name, usr.Name, len(providedKeys), len(giteaKeys))
435
436	// Add new Public SSH Keys that doesn't already exist in DB
437	var newKeys []string
438	for _, key := range providedKeys {
439		if !util.ExistsInSlice(key, giteaKeys) {
440			newKeys = append(newKeys, key)
441		}
442	}
443	if AddPublicKeysBySource(usr, s, newKeys) {
444		sshKeysNeedUpdate = true
445	}
446
447	// Mark keys from DB that no longer exist in the source for deletion
448	var giteaKeysToDelete []string
449	for _, giteaKey := range giteaKeys {
450		if !util.ExistsInSlice(giteaKey, providedKeys) {
451			log.Trace("synchronizePublicKeys[%s]: Marking Public SSH Key for deletion for user %s: %v", s.Name, usr.Name, giteaKey)
452			giteaKeysToDelete = append(giteaKeysToDelete, giteaKey)
453		}
454	}
455
456	// Delete keys from DB that no longer exist in the source
457	needUpd, err := deleteKeysMarkedForDeletion(giteaKeysToDelete)
458	if err != nil {
459		log.Error("synchronizePublicKeys[%s]: Error deleting Public Keys marked for deletion for user %s: %v", s.Name, usr.Name, err)
460	}
461	if needUpd {
462		sshKeysNeedUpdate = true
463	}
464
465	return sshKeysNeedUpdate
466}
467