2017-03-16 04:27:35 +03:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2017-03-16 04:27:35 +03:00
2021-12-10 11:14:24 +03:00
package asymkey
2017-03-16 04:27:35 +03:00
import (
2021-12-10 11:14:24 +03:00
"context"
2017-03-16 04:27:35 +03:00
"fmt"
"strings"
"time"
2021-09-19 14:49:59 +03:00
"code.gitea.io/gitea/models/db"
2021-11-11 10:03:30 +03:00
user_model "code.gitea.io/gitea/models/user"
2019-08-15 17:46:21 +03:00
"code.gitea.io/gitea/modules/timeutil"
2017-03-22 13:43:54 +03:00
2024-07-15 01:14:00 +03:00
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/ProtonMail/go-crypto/openpgp/packet"
2024-01-15 05:19:25 +03:00
"xorm.io/builder"
2017-03-16 04:27:35 +03:00
)
// GPGKey represents a GPG key.
type GPGKey struct {
2019-08-15 17:46:21 +03:00
ID int64 ` xorm:"pk autoincr" `
OwnerID int64 ` xorm:"INDEX NOT NULL" `
KeyID string ` xorm:"INDEX CHAR(16) NOT NULL" `
PrimaryKeyID string ` xorm:"CHAR(16)" `
2022-08-22 16:32:28 +03:00
Content string ` xorm:"MEDIUMTEXT NOT NULL" `
2019-08-15 17:46:21 +03:00
CreatedUnix timeutil . TimeStamp ` xorm:"created" `
ExpiredUnix timeutil . TimeStamp
AddedUnix timeutil . TimeStamp
2017-03-16 04:27:35 +03:00
SubsKey [ ] * GPGKey ` xorm:"-" `
2021-11-11 10:03:30 +03:00
Emails [ ] * user_model . EmailAddress
2021-07-13 16:28:07 +03:00
Verified bool ` xorm:"NOT NULL DEFAULT false" `
2017-03-16 04:27:35 +03:00
CanSign bool
CanEncryptComms bool
CanEncryptStorage bool
CanCertify bool
}
2021-09-19 14:49:59 +03:00
func init ( ) {
db . RegisterModel ( new ( GPGKey ) )
}
2017-03-16 04:27:35 +03:00
// BeforeInsert will be invoked by XORM before inserting a record
func ( key * GPGKey ) BeforeInsert ( ) {
2019-08-15 17:46:21 +03:00
key . AddedUnix = timeutil . TimeStampNow ( )
2017-03-16 04:27:35 +03:00
}
2024-01-15 05:19:25 +03:00
func ( key * GPGKey ) LoadSubKeys ( ctx context . Context ) error {
if err := db . GetEngine ( ctx ) . Where ( "primary_key_id=?" , key . KeyID ) . Find ( & key . SubsKey ) ; err != nil {
return fmt . Errorf ( "find Sub GPGkeys[%s]: %v" , key . KeyID , err )
2017-03-16 04:27:35 +03:00
}
2024-01-15 05:19:25 +03:00
return nil
2017-03-16 04:27:35 +03:00
}
2022-08-21 09:50:15 +03:00
// PaddedKeyID show KeyID padded to 16 characters
func ( key * GPGKey ) PaddedKeyID ( ) string {
2022-12-30 07:53:05 +03:00
return PaddedKeyID ( key . KeyID )
}
// PaddedKeyID show KeyID padded to 16 characters
func PaddedKeyID ( keyID string ) string {
if len ( keyID ) > 15 {
return keyID
2022-08-21 09:50:15 +03:00
}
zeros := "0000000000000000"
2022-12-30 07:53:05 +03:00
return zeros [ 0 : 16 - len ( keyID ) ] + keyID
2022-08-21 09:50:15 +03:00
}
2024-01-15 05:19:25 +03:00
type FindGPGKeyOptions struct {
db . ListOptions
OwnerID int64
KeyID string
IncludeSubKeys bool
2017-03-16 04:27:35 +03:00
}
2024-01-15 05:19:25 +03:00
func ( opts FindGPGKeyOptions ) ToConds ( ) builder . Cond {
cond := builder . NewCond ( )
if ! opts . IncludeSubKeys {
cond = cond . And ( builder . Eq { "primary_key_id" : "" } )
}
if opts . OwnerID > 0 {
cond = cond . And ( builder . Eq { "owner_id" : opts . OwnerID } )
}
if opts . KeyID != "" {
cond = cond . And ( builder . Eq { "key_id" : opts . KeyID } )
}
return cond
2021-08-12 15:43:08 +03:00
}
2023-11-25 20:21:21 +03:00
func GetGPGKeyForUserByID ( ctx context . Context , ownerID , keyID int64 ) ( * GPGKey , error ) {
2017-03-16 04:27:35 +03:00
key := new ( GPGKey )
2023-11-25 20:21:21 +03:00
has , err := db . GetEngine ( ctx ) . Where ( "id=? AND owner_id=?" , keyID , ownerID ) . Get ( key )
2017-03-16 04:27:35 +03:00
if err != nil {
return nil , err
} else if ! has {
return nil , ErrGPGKeyNotExist { keyID }
}
return key , nil
}
2021-03-14 21:52:12 +03:00
// GPGKeyToEntity retrieve the imported key and the traducted entity
2023-10-14 11:37:24 +03:00
func GPGKeyToEntity ( ctx context . Context , k * GPGKey ) ( * openpgp . Entity , error ) {
impKey , err := GetGPGImportByKeyID ( ctx , k . KeyID )
2019-04-14 19:43:56 +03:00
if err != nil {
return nil , err
}
2020-08-21 13:45:50 +03:00
keys , err := checkArmoredGPGKeyString ( impKey . Content )
if err != nil {
return nil , err
}
return keys [ 0 ] , err
2019-04-14 19:43:56 +03:00
}
2021-03-14 21:52:12 +03:00
// parseSubGPGKey parse a sub Key
2017-03-16 04:27:35 +03:00
func parseSubGPGKey ( ownerID int64 , primaryID string , pubkey * packet . PublicKey , expiry time . Time ) ( * GPGKey , error ) {
content , err := base64EncPubKey ( pubkey )
if err != nil {
return nil , err
}
return & GPGKey {
OwnerID : ownerID ,
KeyID : pubkey . KeyIdString ( ) ,
PrimaryKeyID : primaryID ,
Content : content ,
2019-08-15 17:46:21 +03:00
CreatedUnix : timeutil . TimeStamp ( pubkey . CreationTime . Unix ( ) ) ,
ExpiredUnix : timeutil . TimeStamp ( expiry . Unix ( ) ) ,
2017-03-16 04:27:35 +03:00
CanSign : pubkey . CanSign ( ) ,
CanEncryptComms : pubkey . PubKeyAlgo . CanEncrypt ( ) ,
CanEncryptStorage : pubkey . PubKeyAlgo . CanEncrypt ( ) ,
CanCertify : pubkey . PubKeyAlgo . CanSign ( ) ,
} , nil
}
2021-03-14 21:52:12 +03:00
// parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature)
2023-09-14 20:09:32 +03:00
func parseGPGKey ( ctx context . Context , ownerID int64 , e * openpgp . Entity , verified bool ) ( * GPGKey , error ) {
2019-04-16 03:32:15 +03:00
pubkey := e . PrimaryKey
expiry := getExpiryTime ( e )
2017-03-16 04:27:35 +03:00
2021-03-14 21:52:12 +03:00
// Parse Subkeys
2017-03-16 04:27:35 +03:00
subkeys := make ( [ ] * GPGKey , len ( e . Subkeys ) )
for i , k := range e . Subkeys {
2024-07-15 01:14:00 +03:00
subKeyExpiry := expiry
if k . Sig . KeyLifetimeSecs != nil {
subKeyExpiry = k . PublicKey . CreationTime . Add ( time . Duration ( * k . Sig . KeyLifetimeSecs ) * time . Second )
}
subs , err := parseSubGPGKey ( ownerID , pubkey . KeyIdString ( ) , k . PublicKey , subKeyExpiry )
2017-03-16 04:27:35 +03:00
if err != nil {
2020-05-29 00:25:54 +03:00
return nil , ErrGPGKeyParsing { ParseError : err }
2017-03-16 04:27:35 +03:00
}
subkeys [ i ] = subs
}
2021-03-14 21:52:12 +03:00
// Check emails
2023-09-14 20:09:32 +03:00
userEmails , err := user_model . GetEmailAddresses ( ctx , ownerID )
2017-03-16 04:27:35 +03:00
if err != nil {
return nil , err
}
2017-09-05 16:45:18 +03:00
2021-11-11 10:03:30 +03:00
emails := make ( [ ] * user_model . EmailAddress , 0 , len ( e . Identities ) )
2017-03-16 04:27:35 +03:00
for _ , ident := range e . Identities {
2024-07-15 01:14:00 +03:00
// Check if the identity is revoked.
if ident . Revoked ( time . Now ( ) ) {
2020-08-16 11:44:34 +03:00
continue
}
2017-06-17 13:56:40 +03:00
email := strings . ToLower ( strings . TrimSpace ( ident . UserId . Email ) )
2017-03-16 04:27:35 +03:00
for _ , e := range userEmails {
2021-07-13 16:28:07 +03:00
if e . IsActivated && e . LowerEmail == email {
2017-09-05 16:45:18 +03:00
emails = append ( emails , e )
2017-03-16 04:27:35 +03:00
break
}
}
2017-09-05 16:45:18 +03:00
}
2021-07-13 16:28:07 +03:00
if ! verified {
// In the case no email as been found
if len ( emails ) == 0 {
failedEmails := make ( [ ] string , 0 , len ( e . Identities ) )
for _ , ident := range e . Identities {
failedEmails = append ( failedEmails , ident . UserId . Email )
}
return nil , ErrGPGNoEmailFound { failedEmails , e . PrimaryKey . KeyIdString ( ) }
2017-03-16 04:27:35 +03:00
}
}
2017-09-05 16:45:18 +03:00
2017-03-16 04:27:35 +03:00
content , err := base64EncPubKey ( pubkey )
if err != nil {
return nil , err
}
return & GPGKey {
OwnerID : ownerID ,
KeyID : pubkey . KeyIdString ( ) ,
PrimaryKeyID : "" ,
Content : content ,
2019-08-15 17:46:21 +03:00
CreatedUnix : timeutil . TimeStamp ( pubkey . CreationTime . Unix ( ) ) ,
ExpiredUnix : timeutil . TimeStamp ( expiry . Unix ( ) ) ,
2017-03-16 04:27:35 +03:00
Emails : emails ,
SubsKey : subkeys ,
2021-07-13 16:28:07 +03:00
Verified : verified ,
2017-03-16 04:27:35 +03:00
CanSign : pubkey . CanSign ( ) ,
CanEncryptComms : pubkey . PubKeyAlgo . CanEncrypt ( ) ,
CanEncryptStorage : pubkey . PubKeyAlgo . CanEncrypt ( ) ,
CanCertify : pubkey . PubKeyAlgo . CanSign ( ) ,
} , nil
}
// deleteGPGKey does the actual key deletion
2022-05-20 17:08:52 +03:00
func deleteGPGKey ( ctx context . Context , keyID string ) ( int64 , error ) {
2017-03-16 04:27:35 +03:00
if keyID == "" {
2021-03-14 21:52:12 +03:00
return 0 , fmt . Errorf ( "empty KeyId forbidden" ) // Should never happen but just to be sure
2017-03-16 04:27:35 +03:00
}
2021-03-14 21:52:12 +03:00
// Delete imported key
2022-05-20 17:08:52 +03:00
n , err := db . GetEngine ( ctx ) . Where ( "key_id=?" , keyID ) . Delete ( new ( GPGKeyImport ) )
2019-04-14 19:43:56 +03:00
if err != nil {
return n , err
}
2022-05-20 17:08:52 +03:00
return db . GetEngine ( ctx ) . Where ( "key_id=?" , keyID ) . Or ( "primary_key_id=?" , keyID ) . Delete ( new ( GPGKey ) )
2017-03-16 04:27:35 +03:00
}
// DeleteGPGKey deletes GPG key information in database.
2023-09-25 16:17:37 +03:00
func DeleteGPGKey ( ctx context . Context , doer * user_model . User , id int64 ) ( err error ) {
2023-11-25 20:21:21 +03:00
key , err := GetGPGKeyForUserByID ( ctx , doer . ID , id )
2017-03-16 04:27:35 +03:00
if err != nil {
if IsErrGPGKeyNotExist ( err ) {
return nil
}
2022-10-24 22:29:17 +03:00
return fmt . Errorf ( "GetPublicKeyByID: %w" , err )
2017-03-16 04:27:35 +03:00
}
2023-09-25 16:17:37 +03:00
ctx , committer , err := db . TxContext ( ctx )
2021-09-19 14:49:59 +03:00
if err != nil {
2017-03-16 04:27:35 +03:00
return err
}
2021-09-19 14:49:59 +03:00
defer committer . Close ( )
2017-03-16 04:27:35 +03:00
2022-05-20 17:08:52 +03:00
if _ , err = deleteGPGKey ( ctx , key . KeyID ) ; err != nil {
2017-03-16 04:27:35 +03:00
return err
}
2021-09-19 14:49:59 +03:00
return committer . Commit ( )
2017-03-16 04:27:35 +03:00
}
2017-03-22 13:43:54 +03:00
2023-09-14 20:09:32 +03:00
func checkKeyEmails ( ctx context . Context , email string , keys ... * GPGKey ) ( bool , string ) {
2021-07-13 16:28:07 +03:00
uid := int64 ( 0 )
2021-11-11 10:03:30 +03:00
var userEmails [ ] * user_model . EmailAddress
2021-11-24 12:49:20 +03:00
var user * user_model . User
2019-10-16 16:42:42 +03:00
for _ , key := range keys {
2021-07-13 16:28:07 +03:00
for _ , e := range key . Emails {
if e . IsActivated && ( email == "" || strings . EqualFold ( e . Email , email ) ) {
return true , e . Email
2018-03-04 05:45:01 +03:00
}
2019-10-16 16:42:42 +03:00
}
2021-07-13 16:28:07 +03:00
if key . Verified && key . OwnerID != 0 {
if uid != key . OwnerID {
2023-09-14 20:09:32 +03:00
userEmails , _ = user_model . GetEmailAddresses ( ctx , key . OwnerID )
2021-07-13 16:28:07 +03:00
uid = key . OwnerID
2021-11-24 12:49:20 +03:00
user = & user_model . User { ID : uid }
2023-09-14 20:09:32 +03:00
_ , _ = user_model . GetUser ( ctx , user )
2017-03-22 13:43:54 +03:00
}
2021-07-13 16:28:07 +03:00
for _ , e := range userEmails {
if e . IsActivated && ( email == "" || strings . EqualFold ( e . Email , email ) ) {
return true , e . Email
2017-09-05 16:45:18 +03:00
}
}
2021-07-13 16:28:07 +03:00
if user . KeepEmailPrivate && strings . EqualFold ( email , user . GetEmail ( ) ) {
return true , user . GetEmail ( )
2017-03-22 13:43:54 +03:00
}
2019-10-16 16:42:42 +03:00
}
}
2021-07-13 16:28:07 +03:00
return false , email
2020-02-27 22:20:55 +03:00
}