Add support for http signatures (#553)
Reviewed-on: https://gitea.com/gitea/go-sdk/pulls/553 Reviewed-by: Norwin <noerw@noreply.gitea.io> Reviewed-by: 6543 <6543@obermui.de> Co-authored-by: Wim <42wim@noreply.gitea.io> Co-committed-by: Wim <42wim@noreply.gitea.io>
This commit is contained in:
parent
359c771ce3
commit
e5f0c189f2
8 changed files with 403 additions and 11 deletions
1
gitea/agent_darwin.go
Symbolic link
1
gitea/agent_darwin.go
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
agent_linux.go
|
36
gitea/agent_linux.go
Normal file
36
gitea/agent_linux.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasAgent returns true if the ssh agent is available
|
||||||
|
func hasAgent() bool {
|
||||||
|
if _, err := os.Stat(os.Getenv("SSH_AUTH_SOCK")); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAgent returns a ssh agent
|
||||||
|
func GetAgent() (agent.Agent, error) {
|
||||||
|
if !hasAgent() {
|
||||||
|
return nil, fmt.Errorf("no ssh agent available")
|
||||||
|
}
|
||||||
|
|
||||||
|
sshAgent, err := net.Dial("unix", os.Getenv("SSH_AUTH_SOCK"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return agent.NewClient(sshAgent), nil
|
||||||
|
}
|
26
gitea/agent_windows.go
Normal file
26
gitea/agent_windows.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/davidmz/go-pageant"
|
||||||
|
"golang.org/x/crypto/ssh/agent"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hasAgent returns true if pageant is available
|
||||||
|
func hasAgent() bool {
|
||||||
|
return pageant.Available()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAgent returns a ssh agent
|
||||||
|
func GetAgent() (agent.Agent, error) {
|
||||||
|
if !hasAgent() {
|
||||||
|
return nil, fmt.Errorf("no pageant available")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pageant.New(), nil
|
||||||
|
}
|
|
@ -36,10 +36,10 @@ type Client struct {
|
||||||
otp string
|
otp string
|
||||||
sudo string
|
sudo string
|
||||||
debug bool
|
debug bool
|
||||||
|
httpsigner *HTTPSign
|
||||||
client *http.Client
|
client *http.Client
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
|
|
||||||
serverVersion *version.Version
|
serverVersion *version.Version
|
||||||
getVersionOnce sync.Once
|
getVersionOnce sync.Once
|
||||||
ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock
|
ignoreVersion bool // only set by SetGiteaVersion so don't need a mutex lock
|
||||||
|
@ -69,6 +69,7 @@ func NewClient(url string, options ...ClientOption) (*Client, error) {
|
||||||
if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
if err := client.checkServerVersionGreaterThanOrEqual(version1_11_0); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +113,52 @@ func SetBasicAuth(username, password string) ClientOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UseSSHCert is an option for NewClient to enable SSH certificate authentication via HTTPSign
|
||||||
|
// If you want to auth against the ssh-agent you'll need to set a principal, if you want to
|
||||||
|
// use a file on disk you'll need to specify sshKey.
|
||||||
|
// If you have an encrypted sshKey you'll need to also set the passphrase.
|
||||||
|
func UseSSHCert(principal, sshKey, passphrase string) ClientOption {
|
||||||
|
return func(client *Client) error {
|
||||||
|
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.mutex.Lock()
|
||||||
|
defer client.mutex.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.httpsigner, err = NewHTTPSignWithCert(principal, sshKey, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseSSHPubkey is an option for NewClient to enable SSH pubkey authentication via HTTPSign
|
||||||
|
// If you want to auth against the ssh-agent you'll need to set a fingerprint, if you want to
|
||||||
|
// use a file on disk you'll need to specify sshKey.
|
||||||
|
// If you have an encrypted sshKey you'll need to also set the passphrase.
|
||||||
|
func UseSSHPubkey(fingerprint, sshKey, passphrase string) ClientOption {
|
||||||
|
return func(client *Client) error {
|
||||||
|
if err := client.checkServerVersionGreaterThanOrEqual(version1_17_0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
client.mutex.Lock()
|
||||||
|
defer client.mutex.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
client.httpsigner, err = NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetBasicAuth sets username and password
|
// SetBasicAuth sets username and password
|
||||||
func (c *Client) SetBasicAuth(username, password string) {
|
func (c *Client) SetBasicAuth(username, password string) {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
|
@ -239,6 +286,13 @@ func (c *Client) doRequest(method, path string, header http.Header, body io.Read
|
||||||
req.Header[k] = v
|
req.Header[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.httpsigner != nil {
|
||||||
|
err = c.SignRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -3,6 +3,8 @@ module code.gitea.io/sdk/gitea
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-fed/httpsig v1.1.0
|
||||||
github.com/hashicorp/go-version v1.5.0
|
github.com/hashicorp/go-version v1.5.0
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
|
||||||
)
|
)
|
||||||
|
|
19
gitea/go.sum
19
gitea/go.sum
|
@ -1,5 +1,7 @@
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
|
||||||
|
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
|
||||||
github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=
|
github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E=
|
||||||
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
@ -7,6 +9,23 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
|
||||||
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
|
253
gitea/httpsign.go
Normal file
253
gitea/httpsign.go
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a MIT-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package gitea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-fed/httpsig"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPSign contains the signer used for signing requests
|
||||||
|
type HTTPSign struct {
|
||||||
|
ssh.Signer
|
||||||
|
cert bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPSignConfig contains the configuration for creating a HTTPSign
|
||||||
|
type HTTPSignConfig struct {
|
||||||
|
fingerprint string
|
||||||
|
principal string
|
||||||
|
pubkey bool
|
||||||
|
cert bool
|
||||||
|
sshKey string
|
||||||
|
passphrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPSignWithPubkey can be used to create a HTTPSign with a public key
|
||||||
|
// if no fingerprint is specified it returns the first public key found
|
||||||
|
func NewHTTPSignWithPubkey(fingerprint, sshKey, passphrase string) (*HTTPSign, error) {
|
||||||
|
return newHTTPSign(&HTTPSignConfig{
|
||||||
|
fingerprint: fingerprint,
|
||||||
|
pubkey: true,
|
||||||
|
sshKey: sshKey,
|
||||||
|
passphrase: passphrase,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPSignWithCert can be used to create a HTTPSign with a certificate
|
||||||
|
// if no principal is specified it returns the first certificate found
|
||||||
|
func NewHTTPSignWithCert(principal, sshKey, passphrase string) (*HTTPSign, error) {
|
||||||
|
return newHTTPSign(&HTTPSignConfig{
|
||||||
|
principal: principal,
|
||||||
|
cert: true,
|
||||||
|
sshKey: sshKey,
|
||||||
|
passphrase: passphrase,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewHTTPSign returns a new HTTPSign
|
||||||
|
// It will check the ssh-agent or a local file is config.sshKey is set.
|
||||||
|
// Depending on the configuration it will either use a certificate or a public key
|
||||||
|
func newHTTPSign(config *HTTPSignConfig) (*HTTPSign, error) {
|
||||||
|
var signer ssh.Signer
|
||||||
|
|
||||||
|
if config.sshKey != "" {
|
||||||
|
priv, err := os.ReadFile(config.sshKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.passphrase == "" {
|
||||||
|
signer, err = ssh.ParsePrivateKey(priv)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
signer, err = ssh.ParsePrivateKeyWithPassphrase(priv, []byte(config.passphrase))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.cert {
|
||||||
|
certbytes, err := os.ReadFile(config.sshKey + "-cert.pub")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, _, _, _, err := ssh.ParseAuthorizedKey(certbytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, ok := pub.(*ssh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to parse certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
signer, err = ssh.NewCertSigner(cert, signer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if no sshKey is specified, check if we have a ssh-agent and use it
|
||||||
|
agent, err := GetAgent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
signers, err := agent.Signers()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(signers) == 0 {
|
||||||
|
return nil, fmt.Errorf("no signers found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.cert {
|
||||||
|
signer = findCertSigner(signers, config.principal)
|
||||||
|
if signer == nil {
|
||||||
|
return nil, fmt.Errorf("no certificate found for %s", config.principal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.pubkey {
|
||||||
|
signer = findPubkeySigner(signers, config.fingerprint)
|
||||||
|
if signer == nil {
|
||||||
|
return nil, fmt.Errorf("no public key found for %s", config.fingerprint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &HTTPSign{
|
||||||
|
Signer: signer,
|
||||||
|
cert: config.cert,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignRequest signs a HTTP request
|
||||||
|
func (c *Client) SignRequest(r *http.Request) error {
|
||||||
|
var contents []byte
|
||||||
|
|
||||||
|
headersToSign := []string{httpsig.RequestTarget, "(created)", "(expires)"}
|
||||||
|
|
||||||
|
if c.httpsigner.cert {
|
||||||
|
// add our certificate to the headers to sign
|
||||||
|
pubkey, _ := ssh.ParsePublicKey(c.httpsigner.Signer.PublicKey().Marshal())
|
||||||
|
if cert, ok := pubkey.(*ssh.Certificate); ok {
|
||||||
|
certString := base64.RawStdEncoding.EncodeToString(cert.Marshal())
|
||||||
|
r.Header.Add("x-ssh-certificate", certString)
|
||||||
|
|
||||||
|
headersToSign = append(headersToSign, "x-ssh-certificate")
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("no ssh certificate found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we have a body, the Digest header will be added and we'll include this also in
|
||||||
|
// our signature.
|
||||||
|
if r.Body != nil {
|
||||||
|
body, err := r.GetBody()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("getBody() failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
contents, err = io.ReadAll(body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed reading body: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headersToSign = append(headersToSign, "Digest")
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a signer for the request and headers, the signature will be valid for 10 seconds
|
||||||
|
signer, _, err := httpsig.NewSSHSigner(c.httpsigner.Signer, httpsig.DigestSha512, headersToSign, httpsig.Signature, 10)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("httpsig.NewSSHSigner failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sign the request, use the fingerprint if we don't have a certificate
|
||||||
|
keyID := "gitea"
|
||||||
|
if !c.httpsigner.cert {
|
||||||
|
keyID = ssh.FingerprintSHA256(c.httpsigner.Signer.PublicKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = signer.SignRequest(keyID, r, contents)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("httpsig.Signrequest failed: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findCertSigner returns the Signer containing a valid certificate
|
||||||
|
// if no principal is specified it returns the first certificate found
|
||||||
|
func findCertSigner(sshsigners []ssh.Signer, principal string) ssh.Signer {
|
||||||
|
for _, s := range sshsigners {
|
||||||
|
// Check if the key is a certificate
|
||||||
|
if !strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the ssh.Signer to a ssh.Certificate
|
||||||
|
mpubkey, _ := ssh.ParsePublicKey(s.PublicKey().Marshal())
|
||||||
|
cryptopub := mpubkey.(crypto.PublicKey)
|
||||||
|
cert := cryptopub.(*ssh.Certificate)
|
||||||
|
t := time.Unix(int64(cert.ValidBefore), 0)
|
||||||
|
|
||||||
|
// make sure the certificate is at least 10 seconds valid
|
||||||
|
if time.Until(t) <= time.Second*10 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if principal == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, p := range cert.ValidPrincipals {
|
||||||
|
if p == principal {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// findPubkeySigner returns the Signer containing a valid public key
|
||||||
|
// if no fingerprint is specified it returns the first public key found
|
||||||
|
func findPubkeySigner(sshsigners []ssh.Signer, fingerprint string) ssh.Signer {
|
||||||
|
for _, s := range sshsigners {
|
||||||
|
// Check if the key is a certificate
|
||||||
|
if strings.Contains(s.PublicKey().Type(), "cert-v01@openssh.com") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if fingerprint == "" {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(string(ssh.MarshalAuthorizedKey(s.PublicKey()))) == fingerprint {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssh.FingerprintSHA256(s.PublicKey()) == fingerprint {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -67,6 +67,7 @@ var (
|
||||||
version1_14_0 = version.Must(version.NewVersion("1.14.0"))
|
version1_14_0 = version.Must(version.NewVersion("1.14.0"))
|
||||||
version1_15_0 = version.Must(version.NewVersion("1.15.0"))
|
version1_15_0 = version.Must(version.NewVersion("1.15.0"))
|
||||||
version1_16_0 = version.Must(version.NewVersion("1.16.0"))
|
version1_16_0 = version.Must(version.NewVersion("1.16.0"))
|
||||||
|
version1_17_0 = version.Must(version.NewVersion("1.17.0"))
|
||||||
)
|
)
|
||||||
|
|
||||||
// checkServerVersionGreaterThanOrEqual is the canonical way in the SDK to check for versions for API compatibility reasons
|
// checkServerVersionGreaterThanOrEqual is the canonical way in the SDK to check for versions for API compatibility reasons
|
||||||
|
|
Loading…
Reference in a new issue