From 63e2b298ea4c1562e5b10bc874a38d29119fb98f Mon Sep 17 00:00:00 2001 From: Gusted Date: Mon, 12 Jul 2021 15:18:31 +0200 Subject: [PATCH] feat(crypto): add nonce scrambling into crypto functions --- handlers/api/callback.go | 2 +- handlers/oauthProvider/access_token.go | 3 +- handlers/oauthProvider/authorize.go | 7 ++-- handlers/oauthProvider/authorize_style.go | 9 ++-- handlers/style/add.go | 5 ++- handlers/user/register.go | 3 +- handlers/user/reset.go | 7 ++-- handlers/user/verify.go | 3 +- modules/config/config.go | 23 +++++++++++ utils/chacha20poly1305.go | 20 +++++---- utils/chacha20poly1305_test.go | 50 +++++++++++++++-------- utils/oauth.go | 2 +- 12 files changed, 92 insertions(+), 42 deletions(-) diff --git a/handlers/api/callback.go b/handlers/api/callback.go index 85e0e656..c738f0a8 100644 --- a/handlers/api/callback.go +++ b/handlers/api/callback.go @@ -39,7 +39,7 @@ func CallbackGet(c *fiber.Ctx) error { if redirectCode != "codeberg" && redirectCode != "gitlab" { service = "github" // Decode the string so we get our actual information back. - code, err := utils.DecodePreparedText(redirectCode, utils.AEAD_OAUTH) + code, err := utils.DecodePreparedText(redirectCode, utils.AEAD_OAUTH, config.ScrambleConfig) if err != nil { log.Println("Error: Couldn't decode our prepared text.") return c.Next() diff --git a/handlers/oauthProvider/access_token.go b/handlers/oauthProvider/access_token.go index 6bf39a07..475cce00 100644 --- a/handlers/oauthProvider/access_token.go +++ b/handlers/oauthProvider/access_token.go @@ -9,6 +9,7 @@ import ( "github.com/gofiber/fiber/v2" "userstyles.world/models" + "userstyles.world/modules/config" "userstyles.world/utils" ) @@ -34,7 +35,7 @@ func TokenPost(c *fiber.Ctx) error { return errorMessage(c, 400, "Incorrect client_secret specified") } - unsealedText, err := utils.DecodePreparedText(tCode, utils.AEAD_OAUTHP) + unsealedText, err := utils.DecodePreparedText(tCode, utils.AEAD_OAUTHP, config.ScrambleConfig) if err != nil { log.Println("Error: Couldn't unseal JWT Token:", err.Error()) return errorMessage(c, 500, "JWT Token error, please notify the admins.") diff --git a/handlers/oauthProvider/authorize.go b/handlers/oauthProvider/authorize.go index 68403c6d..2dbfe6f2 100644 --- a/handlers/oauthProvider/authorize.go +++ b/handlers/oauthProvider/authorize.go @@ -11,6 +11,7 @@ import ( jwtware "userstyles.world/handlers/jwt" "userstyles.world/models" + "userstyles.world/modules/config" "userstyles.world/utils" ) @@ -34,7 +35,7 @@ func redirectFunction(c *fiber.Ctx, state, redirectURI string) error { return errorMessage(c, 500, "JWT Token error, please notify the admins.") } - returnCode := "?code=" + utils.PrepareText(jwt, utils.AEAD_OAUTHP) + returnCode := "?code=" + utils.PrepareText(jwt, utils.AEAD_OAUTHP, config.ScrambleConfig) if state != "" { returnCode += "&state=" + state } @@ -97,7 +98,7 @@ func AuthorizeGet(c *fiber.Ctx) error { arguments := fiber.Map{ "User": u, "OAuth": OAuth, - "SecureToken": utils.PrepareText(jwt, utils.AEAD_OAUTHP), + "SecureToken": utils.PrepareText(jwt, utils.AEAD_OAUTHP, config.ScrambleConfig), } for _, v := range OAuth.Scopes { arguments["Scope_"+v] = true @@ -115,7 +116,7 @@ func AuthPost(c *fiber.Ctx) error { return errorMessage(c, 400, "Incorrect oauthID specified") } - unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP) + unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP, config.ScrambleConfig) if err != nil { log.Println("Error: Couldn't unseal JWT Token:", err.Error()) return errorMessage(c, 500, "JWT Token error, please notify the admins.") diff --git a/handlers/oauthProvider/authorize_style.go b/handlers/oauthProvider/authorize_style.go index a7740dc4..fc27fe75 100644 --- a/handlers/oauthProvider/authorize_style.go +++ b/handlers/oauthProvider/authorize_style.go @@ -11,6 +11,7 @@ import ( jwtware "userstyles.world/handlers/jwt" "userstyles.world/models" + "userstyles.world/modules/config" "userstyles.world/utils" ) @@ -45,7 +46,7 @@ func OAuthStyleGet(c *fiber.Ctx) error { log.Println("Error: Couldn't make a JWT Token due to:", err.Error()) return errorMessage(c, 500, "Couldn't make JWT Token, please notify the admins.") } - secureToken := utils.PrepareText(jwt, utils.AEAD_OAUTHP) + secureToken := utils.PrepareText(jwt, utils.AEAD_OAUTHP, config.ScrambleConfig) styles, err := models.GetStylesByUser(u.Username) if err != nil { @@ -81,7 +82,7 @@ func OAuthStylePost(c *fiber.Ctx) error { return errorMessage(c, 400, "Incorrect oauthID specified") } - unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP) + unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP, config.ScrambleConfig) if err != nil { log.Println("Error: Couldn't unseal JWT Token:", err.Error()) return errorMessage(c, 500, "JWT Token error, please notify the admins.") @@ -128,7 +129,7 @@ func OAuthStylePost(c *fiber.Ctx) error { return errorMessage(c, 500, "JWT Token error, please notify the admins.") } - returnCode := "?code=" + utils.PrepareText(jwt, utils.AEAD_OAUTHP) + returnCode := "?code=" + utils.PrepareText(jwt, utils.AEAD_OAUTHP, config.ScrambleConfig) returnCode += "&style_id=" + styleID if state != "" { returnCode += "&state=" + state @@ -146,7 +147,7 @@ func OAuthStyleNewPost(c *fiber.Ctx) error { return errorMessage(c, 400, "Incorrect oauthID specified") } - unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP) + unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP, config.ScrambleConfig) if err != nil { log.Println("Error: Couldn't unseal JWT Token:", err.Error()) return errorMessage(c, 500, "JWT Token error, please notify the admins.") diff --git a/handlers/style/add.go b/handlers/style/add.go index d3278e7b..b9a44d61 100644 --- a/handlers/style/add.go +++ b/handlers/style/add.go @@ -16,6 +16,7 @@ import ( jwtware "userstyles.world/handlers/jwt" "userstyles.world/models" + "userstyles.world/modules/config" "userstyles.world/modules/database" "userstyles.world/modules/images" "userstyles.world/search" @@ -146,7 +147,7 @@ func handleAPIStyle(c *fiber.Ctx, secureToken, oauthID, styleID string, style *m }) } - unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP) + unsealedText, err := utils.DecodePreparedText(secureToken, utils.AEAD_OAUTHP, config.ScrambleConfig) if err != nil { log.Println("Error: Couldn't unseal JWT Token:", err.Error()) return c.Status(500). @@ -205,7 +206,7 @@ func handleAPIStyle(c *fiber.Ctx, secureToken, oauthID, styleID string, style *m }) } - returnCode := "?code=" + utils.PrepareText(jwt, utils.AEAD_OAUTHP) + returnCode := "?code=" + utils.PrepareText(jwt, utils.AEAD_OAUTHP, config.ScrambleConfig) returnCode += "&style_id=" + styleID if state != "" { returnCode += "&state=" + state diff --git a/handlers/user/register.go b/handlers/user/register.go index 2396548b..fb767c62 100644 --- a/handlers/user/register.go +++ b/handlers/user/register.go @@ -10,6 +10,7 @@ import ( "userstyles.world/handlers/jwt" "userstyles.world/models" + "userstyles.world/modules/config" "userstyles.world/utils" ) @@ -57,7 +58,7 @@ func RegisterPost(c *fiber.Ctx) error { }) } - link := c.BaseURL() + "/verify/" + utils.PrepareText(token, utils.AEAD_CRYPTO) + link := c.BaseURL() + "/verify/" + utils.PrepareText(token, utils.AEAD_CRYPTO, config.ScrambleConfig) partPlain := utils.NewPart(). SetBody("Verify your UserStyles.world account by clicking the link below.\n" + diff --git a/handlers/user/reset.go b/handlers/user/reset.go index cc30524a..950c61ba 100644 --- a/handlers/user/reset.go +++ b/handlers/user/reset.go @@ -10,6 +10,7 @@ import ( jwtware "userstyles.world/handlers/jwt" "userstyles.world/models" + "userstyles.world/modules/config" "userstyles.world/modules/database" "userstyles.world/utils" ) @@ -39,7 +40,7 @@ func ResetGet(c *fiber.Ctx) error { return renderError } - _, err := utils.DecodePreparedText(key, utils.AEAD_CRYPTO) + _, err := utils.DecodePreparedText(key, utils.AEAD_CRYPTO, config.ScrambleConfig) if err != nil { log.Printf("Couldn't decode key due to: %s\n", err.Error()) return renderError @@ -71,7 +72,7 @@ func ResetPost(c *fiber.Ctx) error { return renderError } - unSealedText, err := utils.DecodePreparedText(key, utils.AEAD_CRYPTO) + unSealedText, err := utils.DecodePreparedText(key, utils.AEAD_CRYPTO, config.ScrambleConfig) if err != nil { log.Printf("Couldn't decode key due to: %s\n", err.Error()) return renderError @@ -154,7 +155,7 @@ func RecoverPost(c *fiber.Ctx) error { }) } - link := c.BaseURL() + "/reset/" + utils.PrepareText(jwt, utils.AEAD_CRYPTO) + link := c.BaseURL() + "/reset/" + utils.PrepareText(jwt, utils.AEAD_CRYPTO, config.ScrambleConfig) partPlain := utils.NewPart(). SetBody("We have received a request to reset the password for your UserStyles.world account.\n\n" + diff --git a/handlers/user/verify.go b/handlers/user/verify.go index 06cfbcf6..056b706b 100644 --- a/handlers/user/verify.go +++ b/handlers/user/verify.go @@ -8,6 +8,7 @@ import ( jwtware "userstyles.world/handlers/jwt" "userstyles.world/models" + "userstyles.world/modules/config" "userstyles.world/modules/database" "userstyles.world/utils" ) @@ -28,7 +29,7 @@ func VerifyGet(c *fiber.Ctx) error { }) } - unSealedText, err := utils.DecodePreparedText(base64Key, utils.AEAD_CRYPTO) + unSealedText, err := utils.DecodePreparedText(base64Key, utils.AEAD_CRYPTO, config.ScrambleConfig) if err != nil { log.Printf("Couldn't decode key due to: %s\n", err.Error()) return c.Render("err", fiber.Map{ diff --git a/modules/config/config.go b/modules/config/config.go index e6b3f9f6..2e3e662e 100644 --- a/modules/config/config.go +++ b/modules/config/config.go @@ -3,8 +3,14 @@ package config import ( "fmt" "os" + "strconv" ) +type NonceScramblingConfig struct { + StepSize int + BytesPerInsert int +} + var ( PORT = getEnv("PORT", ":3000") DB = getEnv("DB", "dev.db") @@ -32,8 +38,25 @@ var ( // Production is used for various "feature flags". Production = DB != "dev.db" + + ScrambleConfig = &NonceScramblingConfig{ + StepSize: getEnvInt("NONCE_SCRAMBLE_STEP", 2), + BytesPerInsert: getEnvInt("NONCE_SCRAMBLE_BYTES_PER_INSERT", 3), + } ) +func getEnvInt(name string, defaultValue int) int { + envValue := getEnv(name, "__NOT_FOUND!") + if envValue == "__NOT_FOUND!" { + return defaultValue + } + if envInt, err := strconv.Atoi(envValue); err != nil { + return defaultValue + } else { + return envInt + } +} + func getEnv(name, fallback string) string { if val, set := os.LookupEnv(name); set { return val diff --git a/utils/chacha20poly1305.go b/utils/chacha20poly1305.go index ec3385e5..c483c16f 100644 --- a/utils/chacha20poly1305.go +++ b/utils/chacha20poly1305.go @@ -42,10 +42,11 @@ func InitalizeCrypto() { AEAD_OAUTHP = aead } -func SealText(text string, aead cipher.AEAD) []byte { +func SealText(text string, aead cipher.AEAD, nonceScrambling *config.NonceScramblingConfig) []byte { nonce := RandStringBytesMaskImprSrcUnsafe(aead.NonceSize()) - return aead.Seal(nonce, nonce, UnsafeBytes(text), nil) + dest := aead.Seal(nil, nonce, UnsafeBytes(text), nil) + return ScrambleNonce(nonce, dest, nonceScrambling.StepSize, nonceScrambling.BytesPerInsert) } // ScrambleNonce into string takes a nonce and a text @@ -171,15 +172,16 @@ mainLoop: return nonce, text } -func OpenText(encryptedMsg string, aead cipher.AEAD) ([]byte, error) { +func OpenText(encryptedMsg string, aead cipher.AEAD, nonceScrambling *config.NonceScramblingConfig) ([]byte, error) { if len(encryptedMsg) < aead.NonceSize() { return nil, errors.ErrMessageSmall } + // Split nonce and ciphertext. - nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():] + nonce, ciphertext := DescrambleNonce([]byte(encryptedMsg), aead.NonceSize(), nonceScrambling.StepSize, nonceScrambling.BytesPerInsert) // Decrypt the message and check it wasn't tampered with. - return aead.Open(nil, UnsafeBytes(nonce), UnsafeBytes(ciphertext), nil) + return aead.Open(nil, nonce, ciphertext, nil) } func VerifyJwtKeyFunction(t *jwt.Token) (interface{}, error) { @@ -196,15 +198,15 @@ func OAuthPJwtKeyFunction(t *jwt.Token) (interface{}, error) { return OAuthPSigningKey, nil } -func PrepareText(text string, aead cipher.AEAD) string { +func PrepareText(text string, aead cipher.AEAD, nonceScrambling *config.NonceScramblingConfig) string { // We have to prepare the encrypted text for transport // Seal Text -> Base64(URL Version) - sealedText := SealText(text, aead) + sealedText := SealText(text, aead, nonceScrambling) return EncodeToString(sealedText) } -func DecodePreparedText(preparedText string, aead cipher.AEAD) (string, error) { +func DecodePreparedText(preparedText string, aead cipher.AEAD, nonceScrambling *config.NonceScramblingConfig) (string, error) { // Now we have to reverse the process. // Decode Base64(URL version) -> Unseal Text enryptedText, err := DecodeString(preparedText) @@ -212,7 +214,7 @@ func DecodePreparedText(preparedText string, aead cipher.AEAD) (string, error) { return "", err } - decryptedText, err := OpenText(UnsafeString(enryptedText), aead) + decryptedText, err := OpenText(UnsafeString(enryptedText), aead, nonceScrambling) if err != nil { return "", err } diff --git a/utils/chacha20poly1305_test.go b/utils/chacha20poly1305_test.go index d54434f0..b58a3274 100644 --- a/utils/chacha20poly1305_test.go +++ b/utils/chacha20poly1305_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/form3tech-oss/jwt-go" + "userstyles.world/modules/config" ) func TestSimpleKey(t *testing.T) { @@ -20,8 +21,13 @@ func TestSimpleKey(t *testing.T) { t.Error(err) } - sealedText := SealText(jwtToken, AEAD_CRYPTO) - unSealedText, err := OpenText(UnsafeString(sealedText), AEAD_CRYPTO) + scrambleConfig := &config.NonceScramblingConfig{ + StepSize: 3, + BytesPerInsert: 2, + } + + sealedText := SealText(jwtToken, AEAD_CRYPTO, scrambleConfig) + unSealedText, err := OpenText(UnsafeString(sealedText), AEAD_CRYPTO, scrambleConfig) if err != nil { t.Error(err) } @@ -35,7 +41,7 @@ func TestSimpleKey(t *testing.T) { } } -func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte) { +func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte, scrambleConfig *config.NonceScramblingConfig) { b.Helper() b.ReportAllocs() @@ -43,25 +49,25 @@ func benchamarkChaCha20Poly1305Seal(b *testing.B, buf []byte) { b.ResetTimer() for i := 0; i < b.N; i++ { - _ = SealText(UnsafeString(buf), AEAD_CRYPTO) + _ = SealText(UnsafeString(buf), AEAD_CRYPTO, scrambleConfig) } } -func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte) { +func benchamarkChaCha20Poly1305Open(b *testing.B, buf []byte, scrambleConfig *config.NonceScramblingConfig) { b.Helper() b.ReportAllocs() b.SetBytes(int64(len(buf))) - ct := SealText(UnsafeString(buf), AEAD_CRYPTO) + ct := SealText(UnsafeString(buf), AEAD_CRYPTO, scrambleConfig) b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = OpenText(UnsafeString(ct), AEAD_CRYPTO) + _, _ = OpenText(UnsafeString(ct), AEAD_CRYPTO, scrambleConfig) } } -func benchamarkPrepareText(b *testing.B, buf []byte) { +func benchamarkPrepareText(b *testing.B, buf []byte, scrambleConfig *config.NonceScramblingConfig) { b.Helper() b.ReportAllocs() @@ -69,33 +75,39 @@ func benchamarkPrepareText(b *testing.B, buf []byte) { b.ResetTimer() for i := 0; i < b.N; i++ { - _ = PrepareText(UnsafeString(buf), AEAD_CRYPTO) + _ = PrepareText(UnsafeString(buf), AEAD_CRYPTO, scrambleConfig) } } -func benchamarkDecodePreparedText(b *testing.B, buf []byte) { +func benchamarkDecodePreparedText(b *testing.B, buf []byte, scrambleConfig *config.NonceScramblingConfig) { b.Helper() b.ReportAllocs() b.SetBytes(int64(len(buf))) - ct := PrepareText(UnsafeString(buf), AEAD_CRYPTO) + ct := PrepareText(UnsafeString(buf), AEAD_CRYPTO, scrambleConfig) b.ResetTimer() for i := 0; i < b.N; i++ { - _, _ = DecodePreparedText(ct, AEAD_CRYPTO) + _, _ = DecodePreparedText(ct, AEAD_CRYPTO, scrambleConfig) } } func BenchmarkPureChaCha20Poly1305(b *testing.B) { InitalizeCrypto() b.ResetTimer() + + scrambleConfig := &config.NonceScramblingConfig{ + StepSize: 2, + BytesPerInsert: 4, + } + for _, length := range []int{215, 1350, 8 * 1024} { b.Run("Open-"+strconv.Itoa(length)+"-X", func(b *testing.B) { - benchamarkChaCha20Poly1305Open(b, make([]byte, length)) + benchamarkChaCha20Poly1305Open(b, make([]byte, length), scrambleConfig) }) b.Run("Seal-"+strconv.Itoa(length)+"-X", func(b *testing.B) { - benchamarkChaCha20Poly1305Seal(b, make([]byte, length)) + benchamarkChaCha20Poly1305Seal(b, make([]byte, length), scrambleConfig) }) } } @@ -103,12 +115,18 @@ func BenchmarkPureChaCha20Poly1305(b *testing.B) { func BenchmarkPrepareText(b *testing.B) { InitalizeCrypto() b.ResetTimer() + + scrambleConfig := &config.NonceScramblingConfig{ + StepSize: 2, + BytesPerInsert: 4, + } + for _, length := range []int{215, 1350, 8 * 1024} { b.Run("Prepare-"+strconv.Itoa(length), func(b *testing.B) { - benchamarkPrepareText(b, make([]byte, length)) + benchamarkPrepareText(b, make([]byte, length), scrambleConfig) }) b.Run("Decode-"+strconv.Itoa(length), func(b *testing.B) { - benchamarkDecodePreparedText(b, make([]byte, length)) + benchamarkDecodePreparedText(b, make([]byte, length), scrambleConfig) }) } } diff --git a/utils/oauth.go b/utils/oauth.go index 6de954d0..5462cc2e 100644 --- a/utils/oauth.go +++ b/utils/oauth.go @@ -101,7 +101,7 @@ func OauthMakeURL(service string) string { // From which site the callback was from. redirectURL := config.OAuthURL() if service == github { - redirectURL += PrepareText(nonsenseState, AEAD_OAUTH) + "/" + redirectURL += PrepareText(nonsenseState, AEAD_OAUTH, config.ScrambleConfig) + "/" } else { redirectURL += service + "/" }