Skip to content

Commit

Permalink
[TT-2539] Refactor hash and token apis under internal/crypto, leave a…
Browse files Browse the repository at this point in the history
…liases (#6838)

### **User description**
<details open>
<summary><a href="https://tyktech.atlassian.net/browse/TT-2539"
title="TT-2539" target="_blank">TT-2539</a></summary>
  <br />
  <table>
    <tr>
      <th>Summary</th>
      <td>Transaction/Access Logs</td>
    </tr>
    <tr>
      <th>Type</th>
      <td>
<img alt="Story"
src="https://tyktech.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium"
/>
        Story
      </td>
    </tr>
    <tr>
      <th>Status</th>
      <td>In Code Review</td>
    </tr>
    <tr>
      <th>Points</th>
      <td>N/A</td>
    </tr>
    <tr>
      <th>Labels</th>
<td><a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20A%20ORDER%20BY%20created%20DESC"
title="A">A</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20America's%20ORDER%20BY%20created%20DESC"
title="America's">America's</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20CSE%20ORDER%20BY%20created%20DESC"
title="CSE">CSE</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20Gold%20ORDER%20BY%20created%20DESC"
title="Gold">Gold</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20QA_Fail%20ORDER%20BY%20created%20DESC"
title="QA_Fail">QA_Fail</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20customer_request%20ORDER%20BY%20created%20DESC"
title="customer_request">customer_request</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20innersource%20ORDER%20BY%20created%20DESC"
title="innersource">innersource</a>, <a
href="https://tyktech.atlassian.net/issues?jql=project%20%3D%20TT%20AND%20labels%20%3D%20jira_escalated%20ORDER%20BY%20created%20DESC"
title="jira_escalated">jira_escalated</a></td>
    </tr>
  </table>
</details>
<!--
  do not remove this marker as it will break jira-lint's functionality.
  added_by_jira_lint
-->

---

This is a prerequisite to implementing access logs, rebasing #6616


___

### **PR Type**
Enhancement


___

### **Description**
- Refactored hash and token-related APIs into a new `internal/crypto`
package.

- Introduced modularized functions for hashing and token generation.

- Removed redundant code from `storage` package and replaced with
references to `crypto`.

- Improved maintainability and modularity of cryptographic operations.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>hash.go</strong><dd><code>Introduced hash-related
utilities in `internal/crypto`</code>&nbsp; &nbsp; &nbsp;
</dd></summary>
<hr>

internal/crypto/hash.go

<li>Added a new file for hash-related functions.<br> <li> Implemented
<code>hashFunction</code> to support multiple algorithms.<br> <li>
Created <code>HashStr</code> and <code>HashKey</code> for string
hashing.<br> <li> Added constants for hash algorithm identifiers.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6838/files#diff-fd1c33ede81b9c5740cabc411ea8e4ce122cf642382b699114dfddcc49ea84d6">+60/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>token.go</strong><dd><code>Introduced token-related
utilities in `internal/crypto`</code>&nbsp; &nbsp; </dd></summary>
<hr>

internal/crypto/token.go

<li>Added a new file for token-related functions.<br> <li> Implemented
<code>GenerateToken</code> for token creation with optional hashing.<br>
<li> Added functions to extract token metadata (e.g.,
<code>TokenHashAlgo</code>, <br><code>TokenID</code>,
<code>TokenOrg</code>).<br> <li> Improved handling of legacy and
JSON-based tokens.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6838/files#diff-25b0099bc38076a27697918a7d82178f3f031a5abb58ae30c70c22d7332ee918">+91/-0</a>&nbsp;
&nbsp; </td>

</tr>

<tr>
  <td>
    <details>
<summary><strong>storage.go</strong><dd><code>Refactored `storage` to
use `internal/crypto`</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

storage/storage.go

<li>Removed hash and token-related functions from
<code>storage</code>.<br> <li> Delegated cryptographic operations to
<code>internal/crypto</code>.<br> <li> Cleaned up unused imports and
constants.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6838/files#diff-2a93e444b612bd9853c32889fb82c4041760536f84356bb0db04738c19b62dde">+0/-125</a>&nbsp;
</td>

</tr>
</table></td></tr><tr><td><strong>Miscellaneous</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>storage.go</strong><dd><code>Updated mock storage
handler file</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

storage/mock/storage.go

<li>Regenerated mock file for storage handler.<br> <li> Removed
unnecessary blank line.


</details>


  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6838/files#diff-0e75f439d0385d9272ea3afa9fc465dcae08554f19ff821e0743ad096325df40">+0/-1</a>&nbsp;
&nbsp; &nbsp; </td>

</tr>
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**: Comment `/help "your question"` on any pull
request to receive relevant information

Co-authored-by: Tit Petric <[email protected]>
  • Loading branch information
titpetric and Tit Petric authored Jan 20, 2025
1 parent 270b0e2 commit b7431a0
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 125 deletions.
2 changes: 2 additions & 0 deletions gateway/host_checker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,8 @@ func TestTestCheckerTCPHosts_correct_answers_proxy_protocol(t *testing.T) {
}

func TestTestCheckerTCPHosts_correct_wrong_answers(t *testing.T) {
t.Skip() // TT-13861

ts := StartTest(nil)
defer ts.Close()

Expand Down
60 changes: 60 additions & 0 deletions internal/crypto/hash.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package crypto

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"hash"

"github.com/sirupsen/logrus"

"github.com/TykTechnologies/murmur3"
)

const (
HashSha256 = "sha256"
HashMurmur32 = "murmur32"
HashMurmur64 = "murmur64"
HashMurmur128 = "murmur128"
)

func hashFunction(algorithm string) (hash.Hash, error) {
switch algorithm {
case HashSha256:
return sha256.New(), nil
case HashMurmur64:
return murmur3.New64(), nil
case HashMurmur128:
return murmur3.New128(), nil
case "", HashMurmur32:
return murmur3.New32(), nil
default:
return murmur3.New32(), fmt.Errorf("Unknown key hash function: %s. Falling back to murmur32.", algorithm)
}
}

func HashStr(in string, withAlg ...string) string {
var algo string
if len(withAlg) > 0 && withAlg[0] != "" {
algo = withAlg[0]
} else {
algo = TokenHashAlgo(in)
}

h, err := hashFunction(algo)

if err != nil {
logrus.Error(err)
}

h.Write([]byte(in))
return hex.EncodeToString(h.Sum(nil))
}

func HashKey(in string, hashKey bool) string {
if !hashKey {
// Not hashing? Return the raw key
return in
}
return HashStr(in)
}
91 changes: 91 additions & 0 deletions internal/crypto/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package crypto

import (
"encoding/base64"
"encoding/hex"
"fmt"
"strings"

"github.com/sirupsen/logrus"

"github.com/buger/jsonparser"

"github.com/TykTechnologies/tyk/internal/uuid"
)

// `{"` in base64
const B64JSONPrefix = "ey"

const DefaultHashAlgorithm = "murmur64"

const MongoBsonIdLength = 24

// GenerateToken generates a token.
// If hashing algorithm is empty, it uses legacy key generation.
func GenerateToken(orgID, keyID, hashAlgorithm string) (string, error) {
if keyID == "" {
keyID = uuid.NewHex()
}

if hashAlgorithm != "" {
_, err := hashFunction(hashAlgorithm)
if err != nil {
hashAlgorithm = DefaultHashAlgorithm
}

jsonToken := fmt.Sprintf(`{"org":"%s","id":"%s","h":"%s"}`, orgID, keyID, hashAlgorithm)
return base64.StdEncoding.EncodeToString([]byte(jsonToken)), err
}

// Legacy keys
return orgID + keyID, nil
}

func TokenHashAlgo(token string) string {
// Legacy tokens not b64 and not JSON records
if strings.HasPrefix(token, B64JSONPrefix) {
if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil {
hashAlgo, err := jsonparser.GetString(jsonToken, "h")

if err != nil {
logrus.Error(err)
return ""
}

return hashAlgo
}
}

return ""
}

func TokenID(token string) (id string, err error) {
jsonToken, err := base64.StdEncoding.DecodeString(token)
if err != nil {
return "", err
}

return jsonparser.GetString(jsonToken, "id")
}

func TokenOrg(token string) string {
if strings.HasPrefix(token, B64JSONPrefix) {
if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil {
// Checking error in case if it is a legacy token which just by accided has the same b64JSON prefix
if org, err := jsonparser.GetString(jsonToken, "org"); err == nil {
return org
}
}
}

// 24 is mongo bson id length
if len(token) > MongoBsonIdLength {
newToken := token[:MongoBsonIdLength]
_, err := hex.DecodeString(newToken)
if err == nil {
return newToken
}
}

return ""
}
24 changes: 24 additions & 0 deletions storage/alias.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package storage

import (
"github.com/TykTechnologies/tyk/internal/crypto"
)

const (
HashSha256 = crypto.HashSha256
HashMurmur32 = crypto.HashMurmur32
HashMurmur64 = crypto.HashMurmur64
HashMurmur128 = crypto.HashMurmur128
)

var (
HashStr = crypto.HashStr
HashKey = crypto.HashKey
)

var (
GenerateToken = crypto.GenerateToken
TokenHashAlgo = crypto.TokenHashAlgo
TokenID = crypto.TokenID
TokenOrg = crypto.TokenOrg
)
125 changes: 0 additions & 125 deletions storage/storage.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,9 @@
package storage

import (
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"hash"
"strings"

"github.com/buger/jsonparser"

"github.com/TykTechnologies/murmur3"
logger "github.com/TykTechnologies/tyk/log"

"github.com/TykTechnologies/tyk/internal/uuid"
)

//go:generate mockgen -destination=./mock/storage.go -package mock . Handler
Expand All @@ -26,8 +15,6 @@ var ErrKeyNotFound = errors.New("key not found")

var ErrMDCBConnectionLost = errors.New("mdcb connection is lost")

const MongoBsonIdLength = 24

// Handler is a standard interface to a storage backend, used by
// AuthorisationManager to read and write key values to the backend
type Handler interface {
Expand Down Expand Up @@ -73,115 +60,3 @@ type AnalyticsHandler interface {
SetExp(string, int64) error // Set key expiration
GetExp(string) (int64, error) // Returns expiry of a key
}

const defaultHashAlgorithm = "murmur64"

// If hashing algorithm is empty, use legacy key generation
func GenerateToken(orgID, keyID, hashAlgorithm string) (string, error) {
if keyID == "" {
keyID = uuid.NewHex()
}

if hashAlgorithm != "" {
_, err := hashFunction(hashAlgorithm)
if err != nil {
hashAlgorithm = defaultHashAlgorithm
}

jsonToken := fmt.Sprintf(`{"org":"%s","id":"%s","h":"%s"}`, orgID, keyID, hashAlgorithm)
return base64.StdEncoding.EncodeToString([]byte(jsonToken)), err
}

// Legacy keys
return orgID + keyID, nil
}

// `{"` in base64
const B64JSONPrefix = "ey"

func TokenHashAlgo(token string) string {
// Legacy tokens not b64 and not JSON records
if strings.HasPrefix(token, B64JSONPrefix) {
if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil {
hashAlgo, _ := jsonparser.GetString(jsonToken, "h")
return hashAlgo
}
}

return ""
}

// TODO: add checks
func TokenID(token string) (id string, err error) {
jsonToken, err := base64.StdEncoding.DecodeString(token)
if err != nil {
return "", err
}

return jsonparser.GetString(jsonToken, "id")
}

func TokenOrg(token string) string {
if strings.HasPrefix(token, B64JSONPrefix) {
if jsonToken, err := base64.StdEncoding.DecodeString(token); err == nil {
// Checking error in case if it is a legacy tooken which just by accided has the same b64JSON prefix
if org, err := jsonparser.GetString(jsonToken, "org"); err == nil {
return org
}
}
}

// 24 is mongo bson id length
if len(token) > MongoBsonIdLength {
newToken := token[:MongoBsonIdLength]
_, err := hex.DecodeString(newToken)
if err == nil {
return newToken
}
}

return ""
}

var (
HashSha256 = "sha256"
HashMurmur32 = "murmur32"
HashMurmur64 = "murmur64"
HashMurmur128 = "murmur128"
)

func hashFunction(algorithm string) (hash.Hash, error) {
switch algorithm {
case HashSha256:
return sha256.New(), nil
case HashMurmur64:
return murmur3.New64(), nil
case HashMurmur128:
return murmur3.New128(), nil
case "", HashMurmur32:
return murmur3.New32(), nil
default:
return murmur3.New32(), fmt.Errorf("Unknown key hash function: %s. Falling back to murmur32.", algorithm)
}
}

func HashStr(in string, withAlg ...string) string {
var algo string
if len(withAlg) > 0 && withAlg[0] != "" {
algo = withAlg[0]
} else {
algo = TokenHashAlgo(in)
}

h, _ := hashFunction(algo)
h.Write([]byte(in))
return hex.EncodeToString(h.Sum(nil))
}

func HashKey(in string, hashKey bool) string {
if !hashKey {
// Not hashing? Return the raw key
return in
}
return HashStr(in)
}

0 comments on commit b7431a0

Please sign in to comment.