mirror of
https://github.com/passbolt/go-passbolt.git
synced 2025-05-09 17:48:20 +00:00
Added ServerVerification Functions, Minor Cleanup
This commit is contained in:
parent
8bccb80cb2
commit
43193345fa
6 changed files with 128 additions and 68 deletions
69
api/auth.go
69
api/auth.go
|
@ -6,19 +6,12 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/gopenpgp/v2/helper"
|
"github.com/ProtonMail/gopenpgp/v2/helper"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PublicKeyReponse the Body of a Public Key Api Request
|
|
||||||
type PublicKeyReponse struct {
|
|
||||||
Fingerprint string `json:"fingerprint"`
|
|
||||||
Keydata string `json:"keydata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login is used for login
|
// Login is used for login
|
||||||
type Login struct {
|
type Login struct {
|
||||||
Auth *GPGAuth `json:"gpg_auth"`
|
Auth *GPGAuth `json:"gpg_auth"`
|
||||||
|
@ -30,24 +23,6 @@ type GPGAuth struct {
|
||||||
Token string `json:"user_token_result,omitempty"`
|
Token string `json:"user_token_result,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add Server Verification Function
|
|
||||||
|
|
||||||
// GetPublicKey gets the Public Key and Fingerprint of the Passbolt instance
|
|
||||||
func (c *Client) GetPublicKey(ctx context.Context) (string, string, error) {
|
|
||||||
msg, err := c.DoCustomRequest(ctx, "GET", "auth/verify.json", "v2", nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("Doing Request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var body PublicKeyReponse
|
|
||||||
err = json.Unmarshal(msg.Body, &body)
|
|
||||||
if err != nil {
|
|
||||||
return "", "", fmt.Errorf("Parsing JSON: %w", err)
|
|
||||||
}
|
|
||||||
// TODO check if that Fingerpirnt is actually from the Publickey
|
|
||||||
return body.Keydata, body.Fingerprint, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckSession Check to see if you have a Valid Session
|
// CheckSession Check to see if you have a Valid Session
|
||||||
func (c *Client) CheckSession(ctx context.Context) bool {
|
func (c *Client) CheckSession(ctx context.Context) bool {
|
||||||
_, err := c.DoCustomRequest(ctx, "GET", "auth/is-authenticated.json", "v2", nil, nil)
|
_, err := c.DoCustomRequest(ctx, "GET", "auth/is-authenticated.json", "v2", nil, nil)
|
||||||
|
@ -120,24 +95,12 @@ func (c *Client) Login(ctx context.Context) error {
|
||||||
return fmt.Errorf("Cannot Find Session Cookie!")
|
return fmt.Errorf("Cannot Find Session Cookie!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// You have to get a make GET Request to get the CSRF Token which is Required for Write Operations
|
// Because of MFA, the custom Request Function now Fetches the CSRF token, we still need the user for his public key
|
||||||
apiMsg, err := c.DoCustomRequest(ctx, "GET", "/users/me.json", "v2", nil, nil)
|
apiMsg, err := c.DoCustomRequest(ctx, "GET", "/users/me.json", "v2", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Getting CSRF Token: %w", err)
|
return fmt.Errorf("Getting CSRF Token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Because of MFA, the custom Request Functin now Fetches the CSRF token, we still need the user for his public key
|
|
||||||
/*
|
|
||||||
for _, cookie := range msg.Cookies() {
|
|
||||||
if cookie.Name == "csrfToken" {
|
|
||||||
c.csrfToken = *cookie
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.csrfToken.Name == "" {
|
|
||||||
return fmt.Errorf("Cannot Find csrfToken Cookie!")
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// Get Users Own Public Key from Server
|
// Get Users Own Public Key from Server
|
||||||
var user User
|
var user User
|
||||||
err = json.Unmarshal(apiMsg.Body, &user)
|
err = json.Unmarshal(apiMsg.Body, &user)
|
||||||
|
@ -176,33 +139,3 @@ func (c *Client) Logout(ctx context.Context) error {
|
||||||
c.csrfToken = http.Cookie{}
|
c.csrfToken = http.Cookie{}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserID Gets the ID of the Current User
|
|
||||||
func (c *Client) GetUserID() string {
|
|
||||||
return c.userID
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkAuthTokenFormat(authToken string) error {
|
|
||||||
splitAuthToken := strings.Split(authToken, "|")
|
|
||||||
if len(splitAuthToken) != 4 {
|
|
||||||
return fmt.Errorf("Auth Token Has Wrong amount of Fields")
|
|
||||||
}
|
|
||||||
|
|
||||||
if splitAuthToken[0] != splitAuthToken[3] {
|
|
||||||
return fmt.Errorf("Auth Token Version Fields Don't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(splitAuthToken[0], "gpgauth") {
|
|
||||||
return fmt.Errorf("Auth Token Version does not start with 'gpgauth'")
|
|
||||||
}
|
|
||||||
|
|
||||||
length, err := strconv.Atoi(splitAuthToken[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Cannot Convert Auth Token Length Field to int: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(splitAuthToken[2]) != length {
|
|
||||||
return fmt.Errorf("Auth Token Data Length does not Match Length Field")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -39,6 +39,12 @@ type Client struct {
|
||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PublicKeyReponse the Body of a Public Key Api Request
|
||||||
|
type PublicKeyReponse struct {
|
||||||
|
Fingerprint string `json:"fingerprint"`
|
||||||
|
Keydata string `json:"keydata"`
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient Returns a new Passbolt Client.
|
// NewClient Returns a new Passbolt Client.
|
||||||
// if httpClient is nil http.DefaultClient will be used.
|
// if httpClient is nil http.DefaultClient will be used.
|
||||||
// if UserAgent is "" "goPassboltClient/1.0" will be used.
|
// if UserAgent is "" "goPassboltClient/1.0" will be used.
|
||||||
|
@ -183,3 +189,29 @@ func addOptions(s, version string, opt interface{}) (string, error) {
|
||||||
u.RawQuery = vs.Encode()
|
u.RawQuery = vs.Encode()
|
||||||
return u.String(), nil
|
return u.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserID Gets the ID of the Current User
|
||||||
|
func (c *Client) GetUserID() string {
|
||||||
|
return c.userID
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKey gets the Public Key and Fingerprint of the Passbolt instance
|
||||||
|
func (c *Client) GetPublicKey(ctx context.Context) (string, string, error) {
|
||||||
|
msg, err := c.DoCustomRequest(ctx, "GET", "auth/verify.json", "v2", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Doing Request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var body PublicKeyReponse
|
||||||
|
err = json.Unmarshal(msg.Body, &body)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Parsing JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lets get the actual Fingerprint instead of trusting the Server
|
||||||
|
privateKeyObj, err := crypto.NewKeyFromArmored(c.userPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Parsing Server Key: %w", err)
|
||||||
|
}
|
||||||
|
return body.Keydata, privateKeyObj.GetFingerprint(), nil
|
||||||
|
}
|
||||||
|
|
28
api/misc.go
28
api/misc.go
|
@ -1,7 +1,10 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
@ -13,3 +16,28 @@ func randStringBytesRmndr(length int) string {
|
||||||
}
|
}
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkAuthTokenFormat(authToken string) error {
|
||||||
|
splitAuthToken := strings.Split(authToken, "|")
|
||||||
|
if len(splitAuthToken) != 4 {
|
||||||
|
return fmt.Errorf("Auth Token Has Wrong amount of Fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
if splitAuthToken[0] != splitAuthToken[3] {
|
||||||
|
return fmt.Errorf("Auth Token Version Fields Don't match")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(splitAuthToken[0], "gpgauth") {
|
||||||
|
return fmt.Errorf("Auth Token Version does not start with 'gpgauth'")
|
||||||
|
}
|
||||||
|
|
||||||
|
length, err := strconv.Atoi(splitAuthToken[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Cannot Convert Auth Token Length Field to int: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(splitAuthToken[2]) != length {
|
||||||
|
return fmt.Errorf("Auth Token Data Length does not Match Length Field")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
64
api/verify.go
Normal file
64
api/verify.go
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GPGVerifyContainer struct {
|
||||||
|
Req GPGVerify `json:"gpg_auth"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GPGVerify is used for verification
|
||||||
|
type GPGVerify struct {
|
||||||
|
KeyID string `json:"keyid"`
|
||||||
|
Token string `json:"server_verify_token,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SetupServerVerification(ctx context.Context) (string, string, error) {
|
||||||
|
serverKey, _, err := c.GetPublicKey(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Getting Server Key: %w", err)
|
||||||
|
}
|
||||||
|
uuid, err := uuid.NewRandom()
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Generating UUID: %w", err)
|
||||||
|
}
|
||||||
|
token := "gpgauthv1.3.0|36|" + uuid.String() + "|gpgauthv1.3.0"
|
||||||
|
encToken, err := c.EncryptMessageWithPublicKey(serverKey, token)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Encrypting Challange: %w", err)
|
||||||
|
}
|
||||||
|
err = c.VerifyServer(ctx, token, encToken)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("Initial Verification: %w", err)
|
||||||
|
}
|
||||||
|
return token, encToken, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) VerifyServer(ctx context.Context, token, encToken string) error {
|
||||||
|
privateKeyObj, err := crypto.NewKeyFromArmored(c.userPrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Parsing User Private Key: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := GPGVerifyContainer{
|
||||||
|
Req: GPGVerify{
|
||||||
|
Token: encToken,
|
||||||
|
KeyID: privateKeyObj.GetFingerprint(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
raw, _, err := c.DoCustomRequestAndReturnRawResponse(ctx, "POST", "/auth/verify.json", "v2", data, nil)
|
||||||
|
if err != nil && !strings.Contains(err.Error(), "The authentication failed.") {
|
||||||
|
return fmt.Errorf("Sending Verification Challange: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if raw.Header.Get("X-GPGAuth-Verify-Response") != token {
|
||||||
|
return fmt.Errorf("Server Response did not Match Saved Token")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c // indirect
|
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c // indirect
|
||||||
github.com/ProtonMail/gopenpgp/v2 v2.2.2
|
github.com/ProtonMail/gopenpgp/v2 v2.2.2
|
||||||
github.com/google/go-querystring v1.1.0
|
github.com/google/go-querystring v1.1.0
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.8.1 // indirect
|
github.com/sirupsen/logrus v1.8.1 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -13,6 +13,8 @@ github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
|
||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
|
Loading…
Add table
Reference in a new issue