Added ServerVerification Functions, Minor Cleanup

This commit is contained in:
Samuel Lorch 2021-09-22 10:11:55 +02:00
parent 8bccb80cb2
commit 43193345fa
6 changed files with 128 additions and 68 deletions

View file

@ -6,19 +6,12 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/ProtonMail/gopenpgp/v2/crypto"
"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
type Login struct {
Auth *GPGAuth `json:"gpg_auth"`
@ -30,24 +23,6 @@ type GPGAuth struct {
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
func (c *Client) CheckSession(ctx context.Context) bool {
_, 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!")
}
// 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)
if err != nil {
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
var user User
err = json.Unmarshal(apiMsg.Body, &user)
@ -176,33 +139,3 @@ func (c *Client) Logout(ctx context.Context) error {
c.csrfToken = http.Cookie{}
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
}

View file

@ -39,6 +39,12 @@ type Client struct {
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.
// if httpClient is nil http.DefaultClient 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()
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
}

View file

@ -1,7 +1,10 @@
package api
import (
"fmt"
"math/rand"
"strconv"
"strings"
)
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
@ -13,3 +16,28 @@ func randStringBytesRmndr(length int) string {
}
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
View 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
View file

@ -6,6 +6,7 @@ require (
github.com/ProtonMail/go-crypto v0.0.0-20210707164159-52430bf6b52c // indirect
github.com/ProtonMail/gopenpgp/v2 v2.2.2
github.com/google/go-querystring v1.1.0
github.com/google/uuid v1.3.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf // indirect

2
go.sum
View file

@ -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-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
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/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=