This repository has been archived on 2024-03-28. You can view files and clone it, but cannot push or open issues or pull requests.
atsebay.t/keys.go

409 lines
10 KiB
Go

package main
import (
"bytes"
"fmt"
"log"
"net/http"
"net/mail"
"strconv"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/gin-gonic/gin"
)
func declareAPIKeysRoutes(router *gin.RouterGroup) {
usersRoutes := router.Group("/users/:uid")
usersRoutes.Use(userHandler)
usersRoutes.GET("/pgp_keys", func(c *gin.Context) {
var u *User
if user, ok := c.Get("user"); ok {
u = user.(*User)
} else {
u = c.MustGet("LoggedUser").(*User)
}
keys, err := u.GetKeys()
if err != nil {
log.Println("Unable to GetKeys:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your keys. Please try again in a few moment."})
return
}
var ret []byte
if len(keys) == 0 {
ret, err = GitLab_getUserPGPKeys(c.Request.Context(), u)
if err != nil {
log.Println("Unable to GitLab_getUserPGPKeys:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your keys from GitLab. Please try again in a few moment or add them directly on /keys page on this website."})
return
}
} else {
for _, key := range keys {
if key.Type == "pgp" {
ret = append(ret, []byte(key.Content)...)
}
}
}
c.Data(http.StatusOK, "application/pgp-keys", ret)
})
usersRoutes.GET("/allowed_signers", func(c *gin.Context) {
var u *User
if user, ok := c.Get("user"); ok {
u = user.(*User)
} else {
u = c.MustGet("LoggedUser").(*User)
}
user, err := GitLab_getUser(c.Request.Context(), u)
if err != nil {
log.Println("Unable to GitLab_getUser:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your GitLab user. Please try again in a few moment."})
return
}
keys, err := GitLab_getUserSSHKeys(c.Request.Context(), u)
if err != nil {
log.Println("Unable to GitLab_getUserSSHKeys:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your keys from GitLab. Please try again in a few moment."})
return
}
var ret string
for _, k := range keys {
if k.UsageType != "auth_and_signing" && k.UsageType != "signing" {
continue
}
if len(user.Email) > 0 {
ret += fmt.Sprintf("%s %s\n", user.Email, k.Key)
} else {
ret += fmt.Sprintf("*@epita.fr %s\n", k.Key)
}
}
c.Data(http.StatusOK, "text/plain", []byte(ret))
})
}
func declareAPIAuthKeysRoutes(router *gin.RouterGroup) {
router.GET("/keys", func(c *gin.Context) {
var u *User
if user, ok := c.Get("user"); ok {
u = user.(*User)
} else {
u = c.MustGet("LoggedUser").(*User)
}
keys, err := u.GetKeys()
if err != nil {
log.Println("Unable to GetKeys:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to retrieve your keys. Please try again in a few moment."})
return
}
var ret []int64
for _, key := range keys {
ret = append(ret, key.Id)
}
c.JSON(http.StatusOK, ret)
})
router.POST("/keys", func(c *gin.Context) {
var u *User
if user, ok := c.Get("user"); ok {
u = user.(*User)
} else {
u = c.MustGet("LoggedUser").(*User)
}
var key Key
if err := c.ShouldBindJSON(&key); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
if err := key.CheckKey(); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
k2 := key
if err := k2.ReadInfos(u); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
k, err := u.NewKey(key.Type, key.Content)
if err != nil {
log.Println("Unable to NewKey:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": "Unable to register your public key. Please try again in a few moment."})
return
}
c.JSON(http.StatusOK, k)
})
keysRoutes := router.Group("/keys/:kid")
keysRoutes.Use(keyHandler)
keysRoutes.Use(keyOnlyMyHandler)
keysRoutes.GET("", func(c *gin.Context) {
var u *User
if user, ok := c.Get("user"); ok {
u = user.(*User)
} else {
u = c.MustGet("LoggedUser").(*User)
}
k := c.MustGet("key").(*Key)
if err := k.ReadInfos(u); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("Unable to read your public key: %s", err.Error())})
return
}
c.JSON(http.StatusOK, k)
})
keysRoutes.GET("export", func(c *gin.Context) {
c.JSON(http.StatusOK, c.MustGet("key").(*Key))
})
keysRoutes.PUT("", func(c *gin.Context) {
current := c.MustGet("key").(*Key)
var new Key
if err := c.ShouldBindJSON(&new); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": err.Error()})
return
}
new.Id = current.Id
u := c.MustGet("LoggedUser").(*User)
if new.IdUser != current.IdUser && !u.IsAdmin {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"errmsg": "Operation not allowed."})
return
}
if key, err := new.Update(); err != nil {
log.Println("Unable to Update key:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during key updation: %s", err.Error())})
return
} else {
c.JSON(http.StatusOK, key)
}
})
keysRoutes.DELETE("", func(c *gin.Context) {
key := c.MustGet("key").(*Key)
if _, err := key.Delete(); err != nil {
log.Println("Unable to Delete key:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"errmsg": fmt.Sprintf("An error occurs during key deletion: %s", err.Error())})
return
} else {
c.JSON(http.StatusOK, nil)
}
})
}
func keyHandler(c *gin.Context) {
var u *User
if user, ok := c.Get("user"); ok {
u = user.(*User)
} else {
u = c.MustGet("LoggedUser").(*User)
}
if kid, err := strconv.Atoi(string(c.Param("kid"))); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"errmsg": "Bad key identifier."})
return
} else if c.MustGet("LoggedUser").(*User).IsAdmin {
if key, err := getKey(kid); err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Key not found."})
return
} else {
c.Set("key", key)
c.Next()
}
} else if key, err := u.getKey(kid); err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Key not found."})
return
} else {
c.Set("key", key)
c.Next()
}
}
func keyOnlyMyHandler(c *gin.Context) {
u := c.MustGet("LoggedUser").(*User)
k := c.MustGet("key").(*Key)
if u.IsAdmin {
c.Next()
} else if k.IdUser == u.Id {
c.Next()
} else {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"errmsg": "Key not found."})
return
}
}
type Key struct {
Id int64 `json:"id"`
IdUser int64 `json:"id_user"`
Type string `json:"type"`
Content string `json:"key,omitempty"`
Time time.Time `json:"time"`
Infos map[string]interface{} `json:"infos,omitempty"`
}
func (u *User) GetKeys() (keys []*Key, err error) {
if rows, errr := DBQuery("SELECT id_key, id_user, type, content, time FROM user_keys WHERE id_user=?", u.Id); errr != nil {
return nil, errr
} else {
defer rows.Close()
for rows.Next() {
var k Key
if err = rows.Scan(&k.Id, &k.IdUser, &k.Type, &k.Content, &k.Time); err != nil {
return
}
keys = append(keys, &k)
}
if err = rows.Err(); err != nil {
return
}
return
}
}
func getKey(id int) (k *Key, err error) {
k = new(Key)
err = DBQueryRow("SELECT id_key, id_user, type, content, time FROM user_keys WHERE id_key=?", id).Scan(&k.Id, &k.IdUser, &k.Type, &k.Content, &k.Time)
return
}
func (u *User) getKey(id int) (k *Key, err error) {
k = new(Key)
err = DBQueryRow("SELECT id_key, id_user, type, content, time FROM user_keys WHERE id_key=? AND id_user=?", id, u.Id).Scan(&k.Id, &k.IdUser, &k.Type, &k.Content, &k.Time)
return
}
func (u *User) NewKey(kind, content string) (*Key, error) {
if res, err := DBExec("INSERT INTO user_keys (id_user, type, content) VALUES (?, ?, ?)", u.Id, kind, content); err != nil {
return nil, err
} else if kid, err := res.LastInsertId(); err != nil {
return nil, err
} else {
return &Key{kid, u.Id, kind, content, time.Now(), nil}, nil
}
}
func (k *Key) CheckKey() error {
if k.Type == "pgp" {
keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(k.Content))
if err != nil {
return err
}
if len(keys) != 1 {
return fmt.Errorf("This is not a single public key file.")
}
if keys[0].PrivateKey != nil {
return fmt.Errorf("You send your PRIVATE key along with your public key. YOUR PRIVATE KEY IS COMPROMISED. Please revoke and regenerate a new key pair.")
}
if keys[0].Revocations != nil {
return fmt.Errorf("Your key seems to be revoked.")
}
return nil
} else {
return fmt.Errorf("%q is not a valid key type.", k.Type)
}
}
func (k *Key) ReadInfos(u *User) error {
if k.Type == "pgp" {
keys, err := openpgp.ReadArmoredKeyRing(bytes.NewBufferString(k.Content))
if err != nil {
return err
}
k.Content = ""
k.Infos = map[string]interface{}{}
var std_identity *openpgp.Identity
for name, idt := range keys[0].Identities {
if idt.Revoked(time.Now()) {
continue
}
address, err := mail.ParseAddress(name)
if err != nil {
continue
}
if address.Address == u.Email {
std_identity = idt
break
}
}
if std_identity == nil {
return fmt.Errorf("No identity found with %s email address.", u.Email)
}
if std_identity.UserId != nil {
k.Infos["identity"] = std_identity.UserId.Name
k.Infos["email"] = std_identity.UserId.Email
k.Infos["comment"] = std_identity.UserId.Comment
}
if std_identity.SelfSignature != nil {
k.Infos["keyid"] = fmt.Sprintf("%X", std_identity.SelfSignature.IssuerFingerprint)
k.Infos["sigexpired"] = std_identity.SelfSignature.SigExpired(time.Now())
k.Infos["creation"] = std_identity.SelfSignature.CreationTime
}
return nil
} else {
return fmt.Errorf("%q is not a valid key type.", k.Type)
}
}
func (k *Key) Update() (*Key, error) {
if _, err := DBExec("UPDATE user_keys SET id_user = ?, type = ?, content = ? WHERE id_key = ?", k.IdUser, k.Type, k.Content, k.Content, k.Id); err != nil {
return nil, err
} else {
return k, err
}
}
func (k Key) Delete() (int64, error) {
if res, err := DBExec("DELETE FROM user_keys WHERE id_key = ?", k.Id); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}
func ClearKeys() (int64, error) {
if res, err := DBExec("DELETE FROM user_keys"); err != nil {
return 0, err
} else if nb, err := res.RowsAffected(); err != nil {
return 0, err
} else {
return nb, err
}
}