code.gitea.io/gitea@v1.21.7/services/auth/httpsign.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package auth 5 6 import ( 7 "bytes" 8 "encoding/base64" 9 "errors" 10 "fmt" 11 "net/http" 12 "strings" 13 14 asymkey_model "code.gitea.io/gitea/models/asymkey" 15 user_model "code.gitea.io/gitea/models/user" 16 "code.gitea.io/gitea/modules/log" 17 "code.gitea.io/gitea/modules/setting" 18 19 "github.com/go-fed/httpsig" 20 "golang.org/x/crypto/ssh" 21 ) 22 23 // Ensure the struct implements the interface. 24 var ( 25 _ Method = &HTTPSign{} 26 ) 27 28 // HTTPSign implements the Auth interface and authenticates requests (API requests 29 // only) by looking for http signature data in the "Signature" header. 30 // more information can be found on https://github.com/go-fed/httpsig 31 type HTTPSign struct{} 32 33 // Name represents the name of auth method 34 func (h *HTTPSign) Name() string { 35 return "httpsign" 36 } 37 38 // Verify extracts and validates HTTPsign from the Signature header of the request and returns 39 // the corresponding user object on successful validation. 40 // Returns nil if header is empty or validation fails. 41 func (h *HTTPSign) Verify(req *http.Request, w http.ResponseWriter, store DataStore, sess SessionStore) (*user_model.User, error) { 42 sigHead := req.Header.Get("Signature") 43 if len(sigHead) == 0 { 44 return nil, nil 45 } 46 47 var ( 48 publicKey *asymkey_model.PublicKey 49 err error 50 ) 51 52 if len(req.Header.Get("X-Ssh-Certificate")) != 0 { 53 // Handle Signature signed by SSH certificates 54 if len(setting.SSH.TrustedUserCAKeys) == 0 { 55 return nil, nil 56 } 57 58 publicKey, err = VerifyCert(req) 59 if err != nil { 60 log.Debug("VerifyCert on request from %s: failed: %v", req.RemoteAddr, err) 61 log.Warn("Failed authentication attempt from %s", req.RemoteAddr) 62 return nil, nil 63 } 64 } else { 65 // Handle Signature signed by Public Key 66 publicKey, err = VerifyPubKey(req) 67 if err != nil { 68 log.Debug("VerifyPubKey on request from %s: failed: %v", req.RemoteAddr, err) 69 log.Warn("Failed authentication attempt from %s", req.RemoteAddr) 70 return nil, nil 71 } 72 } 73 74 u, err := user_model.GetUserByID(req.Context(), publicKey.OwnerID) 75 if err != nil { 76 log.Error("GetUserByID: %v", err) 77 return nil, err 78 } 79 80 store.GetData()["IsApiToken"] = true 81 82 log.Trace("HTTP Sign: Logged in user %-v", u) 83 84 return u, nil 85 } 86 87 func VerifyPubKey(r *http.Request) (*asymkey_model.PublicKey, error) { 88 verifier, err := httpsig.NewVerifier(r) 89 if err != nil { 90 return nil, fmt.Errorf("httpsig.NewVerifier failed: %s", err) 91 } 92 93 keyID := verifier.KeyId() 94 95 publicKeys, err := asymkey_model.SearchPublicKey(0, keyID) 96 if err != nil { 97 return nil, err 98 } 99 100 if len(publicKeys) == 0 { 101 return nil, fmt.Errorf("no public key found for keyid %s", keyID) 102 } 103 104 sshPublicKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKeys[0].Content)) 105 if err != nil { 106 return nil, err 107 } 108 109 if err := doVerify(verifier, []ssh.PublicKey{sshPublicKey}); err != nil { 110 return nil, err 111 } 112 113 return publicKeys[0], nil 114 } 115 116 // VerifyCert verifies the validity of the ssh certificate and returns the publickey of the signer 117 // We verify that the certificate is signed with the correct CA 118 // We verify that the http request is signed with the private key (of the public key mentioned in the certificate) 119 func VerifyCert(r *http.Request) (*asymkey_model.PublicKey, error) { 120 // Get our certificate from the header 121 bcert, err := base64.RawStdEncoding.DecodeString(r.Header.Get("x-ssh-certificate")) 122 if err != nil { 123 return nil, err 124 } 125 126 pk, err := ssh.ParsePublicKey(bcert) 127 if err != nil { 128 return nil, err 129 } 130 131 // Check if it's really a ssh certificate 132 cert, ok := pk.(*ssh.Certificate) 133 if !ok { 134 return nil, fmt.Errorf("no certificate found") 135 } 136 137 c := &ssh.CertChecker{ 138 IsUserAuthority: func(auth ssh.PublicKey) bool { 139 marshaled := auth.Marshal() 140 141 for _, k := range setting.SSH.TrustedUserCAKeysParsed { 142 if bytes.Equal(marshaled, k.Marshal()) { 143 return true 144 } 145 } 146 147 return false 148 }, 149 } 150 151 // check the CA of the cert 152 if !c.IsUserAuthority(cert.SignatureKey) { 153 return nil, fmt.Errorf("CA check failed") 154 } 155 156 // Create a verifier 157 verifier, err := httpsig.NewVerifier(r) 158 if err != nil { 159 return nil, fmt.Errorf("httpsig.NewVerifier failed: %s", err) 160 } 161 162 // now verify that this request was signed with the private key that matches the certificate public key 163 if err := doVerify(verifier, []ssh.PublicKey{cert.Key}); err != nil { 164 return nil, err 165 } 166 167 // Now for each of the certificate valid principals 168 for _, principal := range cert.ValidPrincipals { 169 // Look in the db for the public key 170 publicKey, err := asymkey_model.SearchPublicKeyByContentExact(r.Context(), principal) 171 if asymkey_model.IsErrKeyNotExist(err) { 172 // No public key matches this principal - try the next principal 173 continue 174 } else if err != nil { 175 // this error will be a db error therefore we can't solve this and we should abort 176 log.Error("SearchPublicKeyByContentExact: %v", err) 177 return nil, err 178 } 179 180 // Validate the cert for this principal 181 if err := c.CheckCert(principal, cert); err != nil { 182 // however, because principal is a member of ValidPrincipals - if this fails then the certificate itself is invalid 183 return nil, err 184 } 185 186 // OK we have a public key for a principal matching a valid certificate whose key has signed this request. 187 return publicKey, nil 188 } 189 190 // No public key matching a principal in the certificate is registered in gitea 191 return nil, fmt.Errorf("no valid principal found") 192 } 193 194 // doVerify iterates across the provided public keys attempting the verify the current request against each key in turn 195 func doVerify(verifier httpsig.Verifier, sshPublicKeys []ssh.PublicKey) error { 196 for _, publicKey := range sshPublicKeys { 197 cryptoPubkey := publicKey.(ssh.CryptoPublicKey).CryptoPublicKey() 198 199 var algos []httpsig.Algorithm 200 201 switch { 202 case strings.HasPrefix(publicKey.Type(), "ssh-ed25519"): 203 algos = []httpsig.Algorithm{httpsig.ED25519} 204 case strings.HasPrefix(publicKey.Type(), "ssh-rsa"): 205 algos = []httpsig.Algorithm{httpsig.RSA_SHA1, httpsig.RSA_SHA256, httpsig.RSA_SHA512} 206 } 207 for _, algo := range algos { 208 if err := verifier.Verify(cryptoPubkey, algo); err == nil { 209 return nil 210 } 211 } 212 } 213 214 return errors.New("verification failed") 215 }