diff --git a/models/asymkey/gpg_key_add.go b/models/asymkey/gpg_key_add.go index aa74f66c55..cfe873dad1 100644 --- a/models/asymkey/gpg_key_add.go +++ b/models/asymkey/gpg_key_add.go @@ -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, diff --git a/models/asymkey/gpg_key_common.go b/models/asymkey/gpg_key_common.go index 76f52a3ca4..a85b5d6d33 100644 --- a/models/asymkey/gpg_key_common.go +++ b/models/asymkey/gpg_key_common.go @@ -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) diff --git a/models/asymkey/gpg_key_verify.go b/models/asymkey/gpg_key_verify.go index 085e122ef0..a437e17403 100644 --- a/models/asymkey/gpg_key_verify.go +++ b/models/asymkey/gpg_key_verify.go @@ -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 diff --git a/routers/web/user/setting/keys.go b/routers/web/user/setting/keys.go index cbbc698cb4..209f5ca996 100644 --- a/routers/web/user/setting/keys.go +++ b/routers/web/user/setting/keys.go @@ -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) + } }