Skip to content

Commit

Permalink
feat(security): pass encrypted password
Browse files Browse the repository at this point in the history
  • Loading branch information
baurine committed Aug 29, 2023
1 parent 949927a commit 19c68d2
Show file tree
Hide file tree
Showing 10 changed files with 780 additions and 533 deletions.
22 changes: 21 additions & 1 deletion pkg/apiserver/user/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package user

import (
"crypto/rsa"
"encoding/base64"
"encoding/json"
"errors"
Expand Down Expand Up @@ -35,6 +36,9 @@ type AuthService struct {

middleware *jwt.GinJWTMiddleware
authenticators map[utils.AuthType]Authenticator

rsaPublicKey *rsa.PublicKey
RsaPrivateKey *rsa.PrivateKey
}

type AuthenticateForm struct {
Expand Down Expand Up @@ -90,10 +94,17 @@ func NewAuthService(featureFlags *featureflag.Registry) *AuthService {
secret = cryptopasta.NewEncryptionKey()
}

privateKey, publicKey, err := GenerateKey()
if err != nil {
log.Fatal("Failed to generate rsa key pairs", zap.Error(err))
}

Check warning on line 100 in pkg/apiserver/user/auth.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/auth.go#L97-L100

Added lines #L97 - L100 were not covered by tests

service := &AuthService{
FeatureFlagNonRootLogin: featureFlags.Register("nonRootLogin", ">= 5.3.0"),
middleware: nil,
authenticators: map[utils.AuthType]Authenticator{},
RsaPrivateKey: privateKey,
rsaPublicKey: publicKey,

Check warning on line 107 in pkg/apiserver/user/auth.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/auth.go#L106-L107

Added lines #L106 - L107 were not covered by tests
}

middleware, err := jwt.New(&jwt.GinJWTMiddleware{
Expand Down Expand Up @@ -278,7 +289,8 @@ func (s *AuthService) RegisterAuthenticator(typeID utils.AuthType, a Authenticat
}

type GetLoginInfoResponse struct {
SupportedAuthTypes []int `json:"supported_auth_types"`
SupportedAuthTypes []int `json:"supported_auth_types"`
SQLAuthPublicKey string `json:"sql_auth_public_key"`
}

// @ID userGetLoginInfo
Expand All @@ -298,8 +310,16 @@ func (s *AuthService) GetLoginInfoHandler(c *gin.Context) {
}
}
sort.Ints(supportedAuth)
// both work
// publicKeyStr, err := DumpPublicKeyBase64(s.rsaPublicKey)
publicKeyStr, err := ExportPublicKeyAsString(s.rsaPublicKey)
if err != nil {
rest.Error(c, err)
return
}

Check warning on line 319 in pkg/apiserver/user/auth.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/auth.go#L313-L319

Added lines #L313 - L319 were not covered by tests
resp := GetLoginInfoResponse{
SupportedAuthTypes: supportedAuth,
SQLAuthPublicKey: publicKeyStr,

Check warning on line 322 in pkg/apiserver/user/auth.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/auth.go#L322

Added line #L322 was not covered by tests
}
c.JSON(http.StatusOK, resp)
}
Expand Down
96 changes: 96 additions & 0 deletions pkg/apiserver/user/rsa_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright 2023 PingCAP, Inc. Licensed under Apache-2.0.

package user

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
)

// Generate RSA private/public key.
func GenerateKey() (*rsa.PrivateKey, *rsa.PublicKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}

Check warning on line 18 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L14-L18

Added lines #L14 - L18 were not covered by tests

publicKey := &privateKey.PublicKey
return privateKey, publicKey, nil

Check warning on line 21 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L20-L21

Added lines #L20 - L21 were not covered by tests
}

// Export public key to string
// Output format:
// -----BEGIN PUBLIC KEY-----
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA67F1RPMUO4SjARRe4UfX
// J7ZOCbcysna0jx2Av14KteGo6AWFHhuIxZwgp83GDqFv0Dhc/be7n+9V5vfq0Ob4
// fUtdjBio5ciF4pcqzVGbddfJ0R2e52DF6TI2pDgUFdN+1bmGDwZOCyrwBvVh0wW2
// jAI+QfQyRimZOMqFeX97XjW32vGk7cxNYMys9ExyJcfzfLanbzOwp6kdNbPXnYtU
// Y2nmp+evlPKrRzBPnmO0bpZhYHklrRxLo/u/mThysMEttLkgzCare+JPQyb3z3Si
// Q2E7WG4yz6+6L/wB4etHDfRljMOtqEwv9z4inUfh5716Mg23Div/AbwqGPiKPZf7
// cQIDAQAB
// -----END PUBLIC KEY-----.
func ExportPublicKeyAsString(publicKey *rsa.PublicKey) (string, error) {
publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", err
}

Check warning on line 39 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L35-L39

Added lines #L35 - L39 were not covered by tests

publicKeyPEM := &pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKeyBytes,
}

publicKeyString := string(pem.EncodeToMemory(publicKeyPEM))

return publicKeyString, nil

Check warning on line 48 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L41-L48

Added lines #L41 - L48 were not covered by tests
}

// Dump private key to base64 string
// 1. Have no header/tailer line
// 2. Key content is merged into one-line format
//
// The output is:
//
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2y8mEdCRE8siiI7udpge......2QIDAQAB
func DumpPrivateKeyBase64(privatekey *rsa.PrivateKey) (string, error) {
keyBytes := x509.MarshalPKCS1PrivateKey(privatekey)

keyBase64 := base64.StdEncoding.EncodeToString(keyBytes)
return keyBase64, nil

Check warning on line 62 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L58-L62

Added lines #L58 - L62 were not covered by tests
}

// Dump public key to base64 string
// 1. Have no header/tailer line
// 2. Key content is merged into one-line format
//
// The output is:
//
// MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2y8mEdCRE8siiI7udpge......2QIDAQAB
func DumpPublicKeyBase64(publicKey *rsa.PublicKey) (string, error) {
keyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return "", err
}

Check warning on line 76 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L72-L76

Added lines #L72 - L76 were not covered by tests

keyBase64 := base64.StdEncoding.EncodeToString(keyBytes)
return keyBase64, nil

Check warning on line 79 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L78-L79

Added lines #L78 - L79 were not covered by tests
}

// Decrypt by private key.
func Decrypt(cipherText string, privateKey *rsa.PrivateKey) (string, error) {
// the cipherText is encoded by base64 in the frontend by jsEncrypt
decodedText, err := base64.StdEncoding.DecodeString(cipherText)
if err != nil {
return "", err
}

Check warning on line 88 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L83-L88

Added lines #L83 - L88 were not covered by tests

decryptedText, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decodedText)
if err != nil {
return "", err
}

Check warning on line 93 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L90-L93

Added lines #L90 - L93 were not covered by tests

return string(decryptedText), nil

Check warning on line 95 in pkg/apiserver/user/rsa_utils.go

View check run for this annotation

Codecov / codecov/patch

pkg/apiserver/user/rsa_utils.go#L95

Added line #L95 was not covered by tests
}
15 changes: 12 additions & 3 deletions pkg/apiserver/user/sqlauth/sqlauth.go
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package sqlauth

import (
"crypto/rsa"

"github.com/joomcode/errorx"
"go.uber.org/fx"

Expand All @@ -15,7 +17,8 @@ const typeID utils.AuthType = 0

type Authenticator struct {
user.BaseAuthenticator
tidbClient *tidb.Client
tidbClient *tidb.Client
rsaPrivateKey *rsa.PrivateKey
}

func NewAuthenticator(tidbClient *tidb.Client) *Authenticator {
Expand All @@ -26,6 +29,7 @@ func NewAuthenticator(tidbClient *tidb.Client) *Authenticator {

func registerAuthenticator(a *Authenticator, authService *user.AuthService) {
authService.RegisterAuthenticator(typeID, a)
a.rsaPrivateKey = authService.RsaPrivateKey
}

var Module = fx.Options(
Expand All @@ -34,7 +38,12 @@ var Module = fx.Options(
)

func (a *Authenticator) Authenticate(f user.AuthenticateForm) (*utils.SessionUser, error) {
writeable, err := user.VerifySQLUser(a.tidbClient, f.Username, f.Password)
plainPwd, err := user.Decrypt(f.Password, a.rsaPrivateKey)
if err != nil {
return nil, user.ErrSignInOther.WrapWithNoMessage(err)
}

writeable, err := user.VerifySQLUser(a.tidbClient, f.Username, plainPwd)
if err != nil {
if errorx.Cast(err) == nil {
return nil, user.ErrSignInOther.WrapWithNoMessage(err)
Expand All @@ -52,7 +61,7 @@ func (a *Authenticator) Authenticate(f user.AuthenticateForm) (*utils.SessionUse
Version: utils.SessionVersion,
HasTiDBAuth: true,
TiDBUsername: f.Username,
TiDBPassword: f.Password,
TiDBPassword: plainPwd,
DisplayName: f.Username,
IsShareable: true,
IsWriteable: writeable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
* @interface UserGetLoginInfoResponse
*/
export interface UserGetLoginInfoResponse {
/**
*
* @type {string}
* @memberof UserGetLoginInfoResponse
*/
'sql_auth_public_key'?: string;
/**
*
* @type {Array<number>}
Expand Down
3 changes: 3 additions & 0 deletions ui/packages/tidb-dashboard-client/swagger/spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -5776,6 +5776,9 @@
"user.GetLoginInfoResponse": {
"type": "object",
"properties": {
"sql_auth_public_key": {
"type": "string"
},
"supported_auth_types": {
"type": "array",
"items": {
Expand Down
1 change: 1 addition & 0 deletions ui/packages/tidb-dashboard-for-op/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"compare-versions": "^5.0.1",
"eventemitter2": "^6.4.5",
"i18next": "^23.2.9",
"jsencrypt": "^3.3.2",
"nprogress": "^0.2.0",
"rc-animate": "^3.1.0",
"react": "^17.0.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next'
import { useMount } from 'react-use'
import Flexbox from '@g07cha/flexbox-react'
import { useMemoizedFn } from 'ahooks'
import JSEncrypt from 'jsencrypt'

import {
// distro
Expand Down Expand Up @@ -228,7 +229,7 @@ function useSignInSubmit(

const LAST_LOGIN_USERNAME_KEY = 'dashboard_last_login_username'

function TiDBSignInForm({ successRoute, onClickAlternative }) {
function TiDBSignInForm({ successRoute, onClickAlternative, publicKey }) {
const supportNonRootLogin = useIsFeatureSupport('nonRootLogin')

const { t } = useTranslation()
Expand All @@ -238,11 +239,21 @@ function TiDBSignInForm({ successRoute, onClickAlternative }) {

const { handleSubmit, loading, errorMsg, clearErrorMsg } = useSignInSubmit(
successRoute,
(form) => ({
username: form.username,
password: form.password,
type: auth.AuthTypes.SQLUser
}),
(form) => {
let password = form.password ?? ''
if (!!publicKey) {
const jsEncrypt = new JSEncrypt()
// both work
// jsEncrypt.setPublicKey('-----BEGIN PUBLIC KEY-----' + publicKey + '-----END PUBLIC KEY-----')
jsEncrypt.setPublicKey(publicKey)
password = jsEncrypt.encrypt(password)
}
return {
username: form.username,
password,
type: auth.AuthTypes.SQLUser
}
},
(form) => {
localStorage.setItem(LAST_LOGIN_USERNAME_KEY, form.username)
},
Expand Down Expand Up @@ -449,6 +460,7 @@ function App({ registry }) {
const [supportedAuthTypes, setSupportedAuthTypes] = useState<Array<number>>([
0
])
const [publicKey, setPublicKey] = useState('')

const handleClickAlternative = useCallback(() => {
setAlternativeVisible(true)
Expand Down Expand Up @@ -479,6 +491,9 @@ function App({ registry }) {
setFormType(DisplayFormType.tidbCredential)
}
setSupportedAuthTypes(loginInfo.supported_auth_types ?? [])
if (!!loginInfo.sql_auth_public_key) {
setPublicKey(loginInfo.sql_auth_public_key)
}
} catch (e) {
if ((e as any).response?.status === 404) {
setFormType(DisplayFormType.tidbCredential)
Expand Down Expand Up @@ -517,6 +532,7 @@ function App({ registry }) {
<TiDBSignInForm
successRoute={successRoute}
onClickAlternative={handleClickAlternative}
publicKey={publicKey}
/>
)}
{formType === DisplayFormType.shareCode && (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1 @@
{
"tidb": "TiDB",
"tikv": "TiKV",
"pd": "PD",
"tiflash": "TiFlash",
"ticdc": "TiCDC"
}
{"tidb":"TiDB","tikv":"TiKV","pd":"PD","tiflash":"TiFlash","ticdc":"TiCDC"}
6 changes: 6 additions & 0 deletions ui/packages/tidb-dashboard-lib/src/client/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3809,6 +3809,12 @@ export interface UserAuthenticateForm {
* @interface UserGetLoginInfoResponse
*/
export interface UserGetLoginInfoResponse {
/**
*
* @type {string}
* @memberof UserGetLoginInfoResponse
*/
'sql_auth_public_key'?: string;
/**
*
* @type {Array<number>}
Expand Down
Loading

0 comments on commit 19c68d2

Please sign in to comment.