Support GPG clearsign for verification #7
@@ -5,7 +5,6 @@ package asymkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
"gitea.dev/modules/log"
|
||||
@@ -78,15 +77,9 @@ func AddGPGKey(ctx context.Context, ownerID int64, content, token, signature str
|
||||
verified := false
|
||||
// Handle provided signature
|
||||
if signature != "" {
|
||||
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
|
||||
signer, err := checkSignatureWithClearsign(ekeys, token, signature)
|
||||
if err != nil {
|
||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
|
||||
}
|
||||
if err != nil {
|
||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
|
||||
}
|
||||
if err != nil {
|
||||
log.Debug("AddGPGKey CheckArmoredDetachedSignature failed: %v", err)
|
||||
log.Debug("AddGPGKey checkSignatureWithClearsign failed: %v", err)
|
||||
return nil, ErrGPGInvalidTokenSignature{
|
||||
ID: ekeys[0].PrimaryKey.KeyIdString(),
|
||||
Wrapped: err,
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/armor"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/clearsign"
|
||||
"github.com/ProtonMail/go-crypto/openpgp/packet"
|
||||
)
|
||||
|
||||
@@ -121,21 +122,89 @@ func readArmoredSign(r io.Reader) (body io.Reader, err error) {
|
||||
}
|
||||
|
||||
func ExtractSignature(s string) (*packet.Signature, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
if strings.HasPrefix(s, "-----BEGIN PGP SIGNED MESSAGE-----") {
|
||||
block, _ := clearsign.Decode([]byte(s))
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode clearsign block")
|
||||
}
|
||||
p, err := packet.Read(block.ArmoredSignature.Body)
|
||||
if err != nil {
|
||||
return nil, errors.New("failed to read signature packet from clearsign")
|
||||
}
|
||||
sig, ok := p.(*packet.Signature)
|
||||
if !ok {
|
||||
return nil, errors.New("packet is not a signature")
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
r, err := readArmoredSign(strings.NewReader(s))
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to read signature armor")
|
||||
return nil, errors.New("failed to read signature armor")
|
||||
}
|
||||
p, err := packet.Read(r)
|
||||
if err != nil {
|
||||
return nil, errors.New("Failed to read signature packet")
|
||||
return nil, errors.New("failed to read signature packet")
|
||||
}
|
||||
sig, ok := p.(*packet.Signature)
|
||||
if !ok {
|
||||
return nil, errors.New("Packet is not a signature")
|
||||
return nil, errors.New("packet is not a signature")
|
||||
}
|
||||
return sig, nil
|
||||
}
|
||||
|
||||
func extractSignatureAndPayload(signature string) (*packet.Signature, string, error) {
|
||||
s := strings.TrimSpace(signature)
|
||||
|
||||
if strings.HasPrefix(s, "-----BEGIN PGP SIGNED MESSAGE-----") {
|
||||
block, _ := clearsign.Decode([]byte(s))
|
||||
if block == nil {
|
||||
return nil, "", errors.New("failed to decode clearsign block")
|
||||
}
|
||||
p, err := packet.Read(block.ArmoredSignature.Body)
|
||||
if err != nil {
|
||||
return nil, "", errors.New("failed to read signature packet from clearsign")
|
||||
}
|
||||
sig, ok := p.(*packet.Signature)
|
||||
if !ok {
|
||||
return nil, "", errors.New("packet is not a signature")
|
||||
}
|
||||
return sig, string(block.Bytes), nil
|
||||
}
|
||||
|
||||
sig, err := ExtractSignature(s)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return sig, "", nil
|
||||
}
|
||||
|
||||
func checkSignatureWithClearsign(ekeys openpgp.EntityList, token, signature string) (*openpgp.Entity, error) {
|
||||
s := strings.TrimSpace(signature)
|
||||
|
||||
if strings.HasPrefix(s, "-----BEGIN PGP SIGNED MESSAGE-----") {
|
||||
block, _ := clearsign.Decode([]byte(s))
|
||||
if block == nil {
|
||||
return nil, errors.New("failed to decode clearsign block")
|
||||
}
|
||||
if !strings.Contains(string(block.Bytes), token) {
|
||||
return nil, errors.New("clearsign body does not contain token")
|
||||
}
|
||||
return openpgp.CheckDetachedSignature(ekeys, bytes.NewReader(block.Bytes), block.ArmoredSignature.Body, nil)
|
||||
}
|
||||
|
||||
signer, err := openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token), strings.NewReader(signature), nil)
|
||||
if err != nil {
|
||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\n"), strings.NewReader(signature), nil)
|
||||
}
|
||||
if err != nil {
|
||||
signer, err = openpgp.CheckArmoredDetachedSignature(ekeys, strings.NewReader(token+"\r\n"), strings.NewReader(signature), nil)
|
||||
}
|
||||
return signer, err
|
||||
}
|
||||
|
||||
func TryGetKeyIDFromSignature(sig *packet.Signature) string {
|
||||
if sig.IssuerKeyId != nil && (*sig.IssuerKeyId) != 0 {
|
||||
return fmt.Sprintf("%016X", *sig.IssuerKeyId)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.dev/models/db"
|
||||
@@ -55,14 +56,12 @@ func VerifyNonce(nonce string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// VerifyGPGSignature verifies a detached GPG signature against a nonce,
|
||||
// extracts the key ID, looks up the key in the DB and returns the owning user.
|
||||
func VerifyGPGSignature(ctx context.Context, nonce, signature string) (*user_model.User, error) {
|
||||
if !VerifyNonce(nonce) {
|
||||
return nil, ErrGPGInvalidTokenSignature{}
|
||||
}
|
||||
|
||||
sig, err := ExtractSignature(signature)
|
||||
sig, clearsignPayload, err := extractSignatureAndPayload(signature)
|
||||
if err != nil {
|
||||
return nil, ErrGPGInvalidTokenSignature{Wrapped: err}
|
||||
}
|
||||
@@ -84,12 +83,20 @@ func VerifyGPGSignature(ctx context.Context, nonce, signature string) (*user_mod
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signer, err := hashAndVerifyWithSubKeys(sig, nonce, key)
|
||||
if signer == nil {
|
||||
signer, err = hashAndVerifyWithSubKeys(sig, nonce+"\n", key)
|
||||
}
|
||||
if signer == nil {
|
||||
signer, err = hashAndVerifyWithSubKeys(sig, nonce+"\n\n", key)
|
||||
var signer *GPGKey
|
||||
if clearsignPayload != "" {
|
||||
if !strings.Contains(clearsignPayload, nonce) {
|
||||
return nil, ErrGPGInvalidTokenSignature{}
|
||||
}
|
||||
signer, err = hashAndVerifyWithSubKeys(sig, clearsignPayload, key)
|
||||
} else {
|
||||
signer, err = hashAndVerifyWithSubKeys(sig, nonce, key)
|
||||
if signer == nil {
|
||||
signer, err = hashAndVerifyWithSubKeys(sig, nonce+"\n", key)
|
||||
}
|
||||
if signer == nil {
|
||||
signer, err = hashAndVerifyWithSubKeys(sig, nonce+"\n\n", key)
|
||||
}
|
||||
}
|
||||
if signer == nil {
|
||||
return nil, ErrGPGInvalidTokenSignature{ID: key.KeyID, Wrapped: err}
|
||||
@@ -103,18 +110,20 @@ func VerifyGPGSignature(ctx context.Context, nonce, signature string) (*user_mod
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// VerifyGPGSignatureWithKey verifies a detached GPG signature against a nonce
|
||||
// using a provided armored public key, without requiring it to be in the DB.
|
||||
func VerifyGPGSignatureWithKey(nonce, signature, armoredKey string) (bool, error) {
|
||||
if !VerifyNonce(nonce) {
|
||||
return false, ErrGPGInvalidTokenSignature{}
|
||||
}
|
||||
|
||||
sig, err := ExtractSignature(signature)
|
||||
sig, clearsignPayload, err := extractSignatureAndPayload(signature)
|
||||
if err != nil {
|
||||
return false, ErrGPGInvalidTokenSignature{Wrapped: err}
|
||||
}
|
||||
|
||||
if clearsignPayload != "" && !strings.Contains(clearsignPayload, nonce) {
|
||||
return false, ErrGPGInvalidTokenSignature{}
|
||||
}
|
||||
|
||||
keys, err := CheckArmoredGPGKeyString(armoredKey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -143,12 +152,17 @@ func VerifyGPGSignatureWithKey(nonce, signature, armoredKey string) (bool, error
|
||||
})
|
||||
}
|
||||
|
||||
signer, _ := hashAndVerifyWithSubKeys(sig, nonce, gpgKey)
|
||||
if signer == nil {
|
||||
signer, _ = hashAndVerifyWithSubKeys(sig, nonce+"\n", gpgKey)
|
||||
}
|
||||
if signer == nil {
|
||||
signer, _ = hashAndVerifyWithSubKeys(sig, nonce+"\n\n", gpgKey)
|
||||
var signer *GPGKey
|
||||
if clearsignPayload != "" {
|
||||
signer, _ = hashAndVerifyWithSubKeys(sig, clearsignPayload, gpgKey)
|
||||
} else {
|
||||
signer, _ = hashAndVerifyWithSubKeys(sig, nonce, gpgKey)
|
||||
if signer == nil {
|
||||
signer, _ = hashAndVerifyWithSubKeys(sig, nonce+"\n", gpgKey)
|
||||
}
|
||||
if signer == nil {
|
||||
signer, _ = hashAndVerifyWithSubKeys(sig, nonce+"\n\n", gpgKey)
|
||||
}
|
||||
}
|
||||
if signer != nil {
|
||||
return true, nil
|
||||
|
||||
@@ -359,4 +359,8 @@ func loadKeysData(ctx *context.Context) {
|
||||
|
||||
ctx.Data["VerifyingID"] = ctx.FormString("verify_gpg")
|
||||
ctx.Data["VerifyingFingerprint"] = ctx.FormString("verify_ssh")
|
||||
|
||||
if fp := ctx.FormString("verify_ssh"); fp != "" {
|
||||
ctx.Data["TokenToSign"] = asymkey_model.VerificationToken(ctx.Doer, 1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user