Compare commits

..

5 Commits

Author SHA1 Message Date
Lakhan Samani
128a2a8f75 feat: add support for response mode 2022-03-07 18:49:18 +05:30
Lakhan Samani
7b09a8817c fix: env encryption 2022-03-07 16:16:54 +05:30
Lakhan Samani
1d61840c6d fix: env decryption + remove log 2022-03-07 15:35:33 +05:30
Lakhan Samani
4b25e8941c fix: env decryption 2022-03-07 15:33:39 +05:30
Lakhan Samani
136eda15bf fix: env encryption 2022-03-07 15:29:37 +05:30
5 changed files with 246 additions and 117 deletions

View File

@@ -3,12 +3,14 @@ package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"io"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
)
var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05}
var bytes = []byte{35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 0o5}
// EncryptAES method is to encrypt or hide any classified text
func EncryptAES(text string) (string, error) {
@@ -40,3 +42,67 @@ func DecryptAES(text string) (string, error) {
cfb.XORKeyStream(plainText, []byte(cipherText))
return string(plainText), nil
}
// EncryptAESEnv encrypts data using AES algorithm
// kept for the backward compatibility of env data encryption
func EncryptAESEnv(text []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key)
var res []byte
if err != nil {
return res, err
}
// gcm or Galois/Counter Mode, is a mode of operation
// for symmetric key cryptographic block ciphers
// - https://en.wikipedia.org/wiki/Galois/Counter_Mode
gcm, err := cipher.NewGCM(c)
if err != nil {
return res, err
}
// creates a new byte array the size of the nonce
// which must be passed to Seal
nonce := make([]byte, gcm.NonceSize())
// populates our nonce with a cryptographically secure
// random sequence
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return res, err
}
// here we encrypt our text using the Seal function
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
return gcm.Seal(nonce, nonce, text, nil), nil
}
// DecryptAES decrypts data using AES algorithm
// Kept for the backward compatibility of env data decryption
func DecryptAESEnv(ciphertext []byte) ([]byte, error) {
key := []byte(envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEncryptionKey))
c, err := aes.NewCipher(key)
var res []byte
if err != nil {
return res, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return res, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return res, err
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return res, err
}
return plaintext, nil
}

View File

@@ -94,12 +94,13 @@ func EncryptEnvData(data envstore.Store) (string, error) {
if err != nil {
return "", err
}
encryptedConfig, err := EncryptAES(string(configData))
encryptedConfig, err := EncryptAESEnv(configData)
if err != nil {
return "", err
}
return string(encryptedConfig), nil
return EncryptB64(string(encryptedConfig)), nil
}
// EncryptPassword is used for encrypting password

View File

@@ -34,12 +34,17 @@ func GetEnvData() (envstore.Store, error) {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
decryptedConfigs, err := crypto.DecryptAES(env.EnvData)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
return result, err
}
err = json.Unmarshal([]byte(decryptedConfigs), &result)
decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
if err != nil {
return result, err
}
err = json.Unmarshal(decryptedConfigs, &result)
if err != nil {
return result, err
}
@@ -82,7 +87,12 @@ func PersistEnv() error {
envstore.EnvStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
decryptedConfigs, err := crypto.DecryptAES(env.EnvData)
b64DecryptedConfig, err := crypto.DecryptB64(env.EnvData)
if err != nil {
return err
}
decryptedConfigs, err := crypto.DecryptAESEnv([]byte(b64DecryptedConfig))
if err != nil {
return err
}
@@ -90,7 +100,7 @@ func PersistEnv() error {
// temp store variable
var storeData envstore.Store
err = json.Unmarshal([]byte(decryptedConfigs), &storeData)
err = json.Unmarshal(decryptedConfigs, &storeData)
if err != nil {
return err
}
@@ -102,7 +112,7 @@ func PersistEnv() error {
for key, value := range storeData.StringEnv {
// don't override unexposed envs
if key != constants.EnvKeyEncryptionKey && key != constants.EnvKeyClientID && key != constants.EnvKeyClientSecret && key != constants.EnvKeyJWK {
if key != constants.EnvKeyEncryptionKey {
// check only for derivative keys
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
// as we have removed it from json

View File

@@ -6,10 +6,12 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
@@ -17,6 +19,7 @@ import (
// AuthorizeHandler is the handler for the /authorize route
// required params
// ?redirect_uri = redirect url
// ?response_mode = to decide if result should be html or re-direct
// state[recommended] = to prevent CSRF attack (for authorizer its compulsory)
// code_challenge = to prevent CSRF attack
// code_challenge_method = to prevent CSRF attack [only sh256 is supported]
@@ -31,56 +34,74 @@ func AuthorizeHandler() gin.HandlerFunc {
scopeString := strings.TrimSpace(gc.Query("scope"))
clientID := strings.TrimSpace(gc.Query("client_id"))
template := "authorize.tmpl"
responseMode := strings.TrimSpace(gc.Query("response_mode"))
if responseMode == "" {
responseMode = "query"
}
if responseMode != "query" && responseMode != "web_message" {
gc.JSON(400, gin.H{"error": "invalid response mode"})
}
if redirectURI == "" {
redirectURI = "/app"
}
isQuery := responseMode == "query"
hostname := utils.GetHost(gc)
loginRedirectState := crypto.EncryptB64(`{"authorizerURL":"` + hostname + `","redirectURL":"` + redirectURI + `"}`)
loginURL := "/app?state=" + loginRedirectState
if clientID == "" {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "client_id is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "client_id is required",
},
},
},
})
})
}
return
}
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "invalid_client_id",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "invalid_client_id",
},
},
},
})
return
}
if redirectURI == "" {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "redirect_uri is required",
},
},
})
})
}
return
}
if state == "" {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "state is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "state is required",
},
},
},
})
})
}
return
}
@@ -99,76 +120,96 @@ func AuthorizeHandler() gin.HandlerFunc {
isResponseTypeToken := responseType == "token"
if !isResponseTypeCode && !isResponseTypeToken {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "response_type is invalid",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "response_type is invalid",
},
},
},
})
})
}
return
}
if isResponseTypeCode {
if codeChallenge == "" {
gc.HTML(http.StatusBadRequest, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "code_challenge is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusBadRequest, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "code_challenge is required",
},
},
},
})
})
}
return
}
}
sessionToken, err := cookie.GetSession(gc)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
},
})
})
}
return
}
// get session from cookie
claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
},
})
})
}
return
}
userID := claims.Subject
user, err := db.Provider.GetUserByID(userID)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "signup_required",
"error_description": "Sign up required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "signup_required",
"error_description": "Sign up required",
},
},
},
})
})
}
return
}
@@ -180,16 +221,20 @@ func AuthorizeHandler() gin.HandlerFunc {
nonce := uuid.New().String()
newSessionTokenData, newSessionToken, err := token.CreateSessionToken(user, nonce, claims.Roles, scope)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
},
})
})
}
return
}
@@ -214,16 +259,20 @@ func AuthorizeHandler() gin.HandlerFunc {
// rollover the session for security
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, scope)
if err != nil {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
},
})
})
}
return
}
sessionstore.RemoveState(sessionToken)
@@ -256,16 +305,20 @@ func AuthorizeHandler() gin.HandlerFunc {
return
}
// by default return with error
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
if isQuery {
gc.Redirect(http.StatusFound, loginURL)
} else {
// by default return with error
gc.HTML(http.StatusOK, template, gin.H{
"target_origin": redirectURI,
"authorization_response": map[string]interface{}{
"type": "authorization_response",
"response": map[string]string{
"error": "login_required",
"error_description": "Login is required",
},
},
},
})
})
}
}
}

View File

@@ -8,7 +8,6 @@
(function (window, document) {
var targetOrigin = {{.target_origin}};
var authorizationResponse = {{.authorization_response}};
console.log({targetOrigin})
window.parent.postMessage(authorizationResponse, targetOrigin);
})(this, this.document);
</script>