diff --git a/cli/go.mod b/cli/go.mod index 6b7d924894..fd35082e31 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -7,8 +7,8 @@ require ( github.com/muesli/mango-cobra v1.2.0 github.com/muesli/roff v0.1.0 github.com/spf13/cobra v1.6.1 - golang.org/x/crypto v0.3.0 - golang.org/x/term v0.3.0 + golang.org/x/crypto v0.6.0 + golang.org/x/term v0.5.0 ) require ( @@ -31,8 +31,8 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/rivo/uniseg v0.2.0 // indirect go.mongodb.org/mongo-driver v1.10.0 // indirect - golang.org/x/net v0.2.0 // indirect - golang.org/x/sys v0.3.0 // indirect + golang.org/x/net v0.6.0 // indirect + golang.org/x/sys v0.5.0 // indirect ) require ( diff --git a/cli/go.sum b/cli/go.sum index f7e17c62aa..8a3818528d 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -106,10 +106,14 @@ go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAV golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -123,9 +127,13 @@ golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/cli/packages/api/api.go b/cli/packages/api/api.go index 60cc7d07c0..637fc1065c 100644 --- a/cli/packages/api/api.go +++ b/cli/packages/api/api.go @@ -128,6 +128,46 @@ func CallGetSecretsV2(httpClient *resty.Client, request GetEncryptedSecretsV2Req return secretsResponse, nil } +func CallLogin1V2(httpClient *resty.Client, request GetLoginOneV2Request) (GetLoginOneV2Response, error) { + var loginOneV2Response GetLoginOneV2Response + response, err := httpClient. + R(). + SetResult(&loginOneV2Response). + SetHeader("User-Agent", USER_AGENT). + SetBody(request). + Post(fmt.Sprintf("%v/v2/auth/login1", config.INFISICAL_URL)) + + if err != nil { + return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V2: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return GetLoginOneV2Response{}, fmt.Errorf("CallLogin1V2: Unsuccessful response: [response=%s]", response) + } + + return loginOneV2Response, nil +} + +func CallLogin2V2(httpClient *resty.Client, request GetLoginTwoV2Request) (GetLoginTwoV2Response, error) { + var loginTwoV2Response GetLoginTwoV2Response + response, err := httpClient. + R(). + SetResult(&loginTwoV2Response). + SetHeader("User-Agent", USER_AGENT). + SetBody(request). + Post(fmt.Sprintf("%v/v2/auth/login2", config.INFISICAL_URL)) + + if err != nil { + return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V2: Unable to complete api request [err=%s]", err) + } + + if response.IsError() { + return GetLoginTwoV2Response{}, fmt.Errorf("CallLogin2V2: Unsuccessful response: [response=%s]", response) + } + + return loginTwoV2Response, nil +} + func CallGetAllWorkSpacesUserBelongsTo(httpClient *resty.Client) (GetWorkSpacesResponse, error) { var workSpacesResponse GetWorkSpacesResponse response, err := httpClient. diff --git a/cli/packages/api/model.go b/cli/packages/api/model.go index 7c9fbbf4e5..5dd20b1df6 100644 --- a/cli/packages/api/model.go +++ b/cli/packages/api/model.go @@ -263,3 +263,31 @@ type GetAccessibleEnvironmentsResponse struct { IsWriteDenied bool `json:"isWriteDenied"` } `json:"accessibleEnvironments"` } + +type GetLoginOneV2Request struct { + Email string `json:"email"` + ClientPublicKey string `json:"clientPublicKey"` +} + +type GetLoginOneV2Response struct { + ServerPublicKey string `json:"serverPublicKey"` + Salt string `json:"salt"` +} + +type GetLoginTwoV2Request struct { + Email string `json:"email"` + ClientProof string `json:"clientProof"` +} + +type GetLoginTwoV2Response struct { + MfaEnabled bool `json:"mfaEnabled"` + EncryptionVersion int `json:"encryptionVersion"` + Token string `json:"token"` + PublicKey string `json:"publicKey"` + EncryptedPrivateKey string `json:"encryptedPrivateKey"` + Iv string `json:"iv"` + Tag string `json:"tag"` + ProtectedKey string `json:"protectedKey"` + ProtectedKeyIV string `json:"protectedKeyIV"` + ProtectedKeyTag string `json:"protectedKeyTag"` +} diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index c37bdda080..c2d193d8a6 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -13,7 +13,6 @@ import ( "regexp" "github.com/Infisical/infisical-merge/packages/api" - "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/srp" @@ -23,8 +22,23 @@ import ( "github.com/manifoldco/promptui" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "golang.org/x/crypto/argon2" ) +type params struct { + memory uint32 + iterations uint32 + parallelism uint8 + saltLength uint32 + keyLength uint32 +} + +func generateFromPassword(password string, salt []byte, p *params) (hash []byte, err error) { + hash = argon2.IDKey([]byte(password), salt, p.iterations, p.memory, p.parallelism, p.keyLength) + + return hash, nil +} + // loginCmd represents the login command var loginCmd = &cobra.Command{ Use: "login", @@ -55,36 +69,100 @@ var loginCmd = &cobra.Command{ util.HandleError(err, "Unable to parse email and password for authentication") } - userCredentials, err := getFreshUserCredentials(email, password) + loginOneResponse, loginTwoResponse, err := getFreshUserCredentials(email, password) if err != nil { log.Infoln("Unable to authenticate with the provided credentials, please try again") log.Debugln(err) return } - encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(userCredentials.EncryptedPrivateKey) - tag, err := base64.StdEncoding.DecodeString(userCredentials.Tag) - if err != nil { - util.HandleError(err) - } + var decryptedPrivateKey []byte - IV, err := base64.StdEncoding.DecodeString(userCredentials.IV) - if err != nil { - util.HandleError(err) - } + if loginTwoResponse.EncryptionVersion == 1 { + encryptedPrivateKey, _ := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey) + tag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag) + if err != nil { + util.HandleError(err) + } - paddedPassword := fmt.Sprintf("%032s", password) - key := []byte(paddedPassword) + IV, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv) + if err != nil { + util.HandleError(err) + } - decryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV) - if err != nil || len(decryptedPrivateKey) == 0 { - util.HandleError(err) + paddedPassword := fmt.Sprintf("%032s", password) + key := []byte(paddedPassword) + + decryptedPrivateKey, err := crypto.DecryptSymmetric(key, encryptedPrivateKey, tag, IV) + if err != nil || len(decryptedPrivateKey) == 0 { + util.HandleError(err) + } + } else if loginTwoResponse.EncryptionVersion == 2 { + protectedKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKey) + if err != nil { + util.HandleError(err) + } + + protectedKeyTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyTag) + if err != nil { + util.HandleError(err) + } + + protectedKeyIV, err := base64.StdEncoding.DecodeString(loginTwoResponse.ProtectedKeyIV) + if err != nil { + util.HandleError(err) + } + + nonProtectedTag, err := base64.StdEncoding.DecodeString(loginTwoResponse.Tag) + if err != nil { + util.HandleError(err) + } + + nonProtectedIv, err := base64.StdEncoding.DecodeString(loginTwoResponse.Iv) + if err != nil { + util.HandleError(err) + } + + parameters := ¶ms{ + memory: 64 * 1024, + iterations: 3, + parallelism: 1, + keyLength: 32, + } + + derivedKey, err := generateFromPassword(password, []byte(loginOneResponse.Salt), parameters) + if err != nil { + util.HandleError(fmt.Errorf("unable to generate argon hash from password [err=%s]", err)) + } + + decryptedProtectedKey, err := crypto.DecryptSymmetric(derivedKey, protectedKey, protectedKeyTag, protectedKeyIV) + if err != nil { + util.HandleError(fmt.Errorf("unable to get decrypted protected key [err=%s]", err)) + } + + encryptedPrivateKey, err := base64.StdEncoding.DecodeString(loginTwoResponse.EncryptedPrivateKey) + if err != nil { + util.HandleError(err) + } + + decryptedProtectedKeyInHex, err := hex.DecodeString(string(decryptedProtectedKey)) + if err != nil { + util.HandleError(err) + } + + decryptedPrivateKey, err = crypto.DecryptSymmetric(decryptedProtectedKeyInHex, encryptedPrivateKey, nonProtectedTag, nonProtectedIv) + + if err != nil { + util.HandleError(err) + } + } else { + util.PrintErrorMessageAndExit("Insufficient details to decrypt private key") } userCredentialsToBeStored := &models.UserCredentials{ Email: email, PrivateKey: string(decryptedPrivateKey), - JTWToken: userCredentials.JTWToken, + JTWToken: loginTwoResponse.Token, } err = util.StoreUserCredsInKeyRing(userCredentialsToBeStored) @@ -155,7 +233,7 @@ func askForLoginCredentials() (email string, password string, err error) { return userEmail, userPassword, nil } -func getFreshUserCredentials(email string, password string) (*api.LoginTwoResponse, error) { +func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2Response, *api.GetLoginTwoV2Response, error) { log.Debugln("getFreshUserCredentials:", "email", email, "password", password) httpClient := resty.New() httpClient.SetRetryCount(5) @@ -166,36 +244,24 @@ func getFreshUserCredentials(email string, password string) (*api.LoginTwoRespon srpA := hex.EncodeToString(srpClient.ComputeA()) // ** Login one - loginOneRequest := api.LoginOneRequest{ + loginOneResponseResult, err := api.CallLogin1V2(httpClient, api.GetLoginOneV2Request{ Email: email, ClientPublicKey: srpA, - } - - var loginOneResponseResult api.LoginOneResponse - - loginOneResponse, err := httpClient. - R(). - SetBody(loginOneRequest). - SetResult(&loginOneResponseResult). - Post(fmt.Sprintf("%v/v1/auth/login1", config.INFISICAL_URL)) + }) if err != nil { - return nil, err - } - - if loginOneResponse.StatusCode() > 299 { - return nil, fmt.Errorf("ops, unsuccessful response code. [response=%v]", loginOneResponse) + util.HandleError(err) } // **** Login 2 serverPublicKey_bytearray, err := hex.DecodeString(loginOneResponseResult.ServerPublicKey) if err != nil { - return nil, err + return nil, nil, err } - userSalt, err := hex.DecodeString(loginOneResponseResult.ServerSalt) + userSalt, err := hex.DecodeString(loginOneResponseResult.Salt) if err != nil { - return nil, err + return nil, nil, err } srpClient.SetSalt(userSalt, []byte(email), []byte(password)) @@ -203,27 +269,16 @@ func getFreshUserCredentials(email string, password string) (*api.LoginTwoRespon srpM1 := srpClient.ComputeM1() - LoginTwoRequest := api.LoginTwoRequest{ + loginTwoResponseResult, err := api.CallLogin2V2(httpClient, api.GetLoginTwoV2Request{ Email: email, ClientProof: hex.EncodeToString(srpM1), - } - - var loginTwoResponseResult api.LoginTwoResponse - loginTwoResponse, err := httpClient. - R(). - SetBody(LoginTwoRequest). - SetResult(&loginTwoResponseResult). - Post(fmt.Sprintf("%v/v1/auth/login2", config.INFISICAL_URL)) + }) if err != nil { - return nil, err - } - - if loginTwoResponse.StatusCode() > 299 { - return nil, fmt.Errorf("ops, unsuccessful response code. [response=%v]", loginTwoResponse) + util.HandleError(err) } - return &loginTwoResponseResult, nil + return &loginOneResponseResult, &loginTwoResponseResult, nil } func shouldOverrideLoginPrompt(currentLoggedInUserEmail string) (bool, error) {