Skip to content

Commit

Permalink
Added support for generation of JWT tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
Michal Saloň authored and MichalSalon committed Oct 18, 2024
1 parent 98f15f4 commit ade4df2
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 35 deletions.
8 changes: 1 addition & 7 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@ jobs:
runLint: true
runTests: true

- name: linux 386
os: ubuntu-latest
osEnv: GOOS=linux GOARCH=386
runLint: true
runTests: true

- name: darwin amd64
os: macos-latest
osEnv: GOOS=darwin GOARCH=amd64
Expand Down Expand Up @@ -58,7 +52,7 @@ jobs:
- name: Get dependencies
run: |
export GOPATH=$HOME/go
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.61.0
- name: Build
run: env ${{ matrix.osEnv }} go build -v ./cmd/... ./src/...
Expand Down
9 changes: 0 additions & 9 deletions .github/workflows/tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,6 @@ jobs:
fail-fast: false
matrix:
include:
- name: linux 386
os: ubuntu-latest
buildCmd: make buildLinux386
file: zparser-linux-i386
compress: true
strip: true
runLint: true
runTests: true

- name: linux amd64
os: ubuntu-latest
buildCmd: make buildLinuxAmd64
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v2.1.2] - 2024-10-18

### Added
- `generateJWT` function to generate JSON Web Tokens

### Updated
- `golangci-lint` installer to `v1.61.0`

### Fixed
- install path used by `installLint` command in Makefile

### Removed
- support for `32bit`/`i386` platform

## [v2.1.1] - 2024-05-15

### Updated
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: buildAll buildLinux buildLinux386 buildLinuxAmd64 buildMac buildMacAmd64 buildMacArm64 buildWindows installLint fmt lint lintFix test exampleStdout example

all: fmt lint test
all: tidy fmt lint test

buildAll: buildLinux buildWindows buildMac

Expand All @@ -23,7 +23,10 @@ buildLinux: buildLinuxAmd64 buildLinux386
buildMac: buildMacAmd64 buildMacArm64

installLint:
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.58.1
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(GOPATH)/bin v1.61.0

tidy:
go mod tidy

fmt:
gofmt -s -w ./src/. ./cmd/.
Expand Down
65 changes: 49 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,9 +320,11 @@ When error occurs, binary returns a formatted error to the output
<summary>Example</summary>

```yaml
...
...
#...
#...
MERCURY_RETROGRADE: "<@mercuryInRetrograde(<mercury is in retrograde>, <mercury is not in retrograde>, <my third unexpected param>) | title>"
#...
#...
```

```text
Expand All @@ -343,20 +345,21 @@ positionNear: e>

## Supported functions

| name | description | example |
|-------------------------|----------------------------------------------------------------------------------|------------------------------------------------|
| generateRandomString | generates random string in requested length | `<@generateRandomString(<50>)>` |
| generateRandomBytes | generates requested amount of cryptographically random bytes | `<@generateRandomBytes(<50>)>` |
| generateRandomInt | generates random in int range [min, max] | `<@generateRandomInt(<-999>, <999>)>` |
| pickRandom | selects one of the provided parameters at random | `<@pickRandom(<one>, <two>, <three>, <four>)>` |
| generateRandomStringVar | generates random string and stores it for later use | `<@generateRandomStringVar(<myName>, <50>)>` |
| setVar | stores provided content for later use | `<@setVar(<myName>, <my string content>)>` |
| getVar | returns content of a stored variable | `<@getVar(myName)>` |
| getDateTime | returns current date and time in specified format and a timezone | `<@getDatetime(<DD.MM.YYYY HH:mm:ss>, <GMT>)>` |
| generateED25519Key | generates Public and Private ED25519 key pairs and stores them for later use | `<@generateED25519Key(<myEd25519Key>)>` |
| generateRSA2048Key | generates Public and Private RSA 2048bit key pairs and stores them for later use | `<@generateRSA2048Key(<myRSA2048Key>)>` |
| generateRSA4096Key | generates Public and Private RSA 4096bit key pairs and stores them for later use | `<@generateRSA4096Key(<myRSA4096Key>)>` |
| mercuryInRetrograde | returns first parameter if Mercury IS in retrograde or second if it is not | `<@mercuryInRetrograde(<Yes>, <No>)>` |
| name | description | example |
|-------------------------|----------------------------------------------------------------------------------|------------------------------------------------------------------------|
| generateRandomString | generates random string in requested length | `<@generateRandomString(<50>)>` |
| generateRandomBytes | generates requested amount of cryptographically random bytes | `<@generateRandomBytes(<50>)>` |
| generateRandomInt | generates random integer in range [min, max] | `<@generateRandomInt(<-999>, <999>)>` |
| pickRandom | selects one of the provided parameters at random | `<@pickRandom(<one>, <two>, <three>, <four>)>` |
| generateRandomStringVar | generates random string and stores it for later use | `<@generateRandomStringVar(<myName>, <50>)>` |
| setVar | stores provided content for later use | `<@setVar(<myName>, <my string content>)>` |
| getVar | returns content of a stored variable | `<@getVar(myName)>` |
| getDateTime | returns current date and time in specified format and a timezone | `<@getDatetime(<DD.MM.YYYY HH:mm:ss>, <GMT>)>` |
| generateJWT | Generates JWT signed by `HS256` algorithm using provided secret and payload. | `<@generateJWT(<mySecretString>, <{"role":"test","exp":1798761600}>)>` |
| generateED25519Key | generates Public and Private ED25519 key pairs and stores them for later use | `<@generateED25519Key(<myEd25519Key>)>` |
| generateRSA2048Key | generates Public and Private RSA 2048bit key pairs and stores them for later use | `<@generateRSA2048Key(<myRSA2048Key>)>` |
| generateRSA4096Key | generates Public and Private RSA 4096bit key pairs and stores them for later use | `<@generateRSA4096Key(<myRSA4096Key>)>` |
| mercuryInRetrograde | returns first parameter if Mercury IS in retrograde or second if it is not | `<@mercuryInRetrograde(<Yes>, <No>)>` |

---

Expand All @@ -366,6 +369,7 @@ Generates random string, comprised of `[a-zA-Z0-9_-.]` character set, in request
<details>

#### Info

It is preferred to use `generateRandomBytes` with `toString` modifier: `<@generateRandomBytes(length) | toString>`.

#### Parameters
Expand Down Expand Up @@ -583,6 +587,35 @@ Returns current date and time in specified format.

---

### `generateJWT(tokenSecret, jsonPayload)`

Generates JWT (JSON Web Token) signed by `HS256` algorithm using provided secret and payload.

Payload MUST be a valid JSON value.

Default values for `iss` and `iat` are set to `zerops` and current timestamp respectively.
<details>

#### Parameters

| name | type | description |
|-------------|----------|----------------------------------------|
| tokenSecret | `string` | secret used to sign your JWT |
| jsonPayload | `string` | payload part of the JWT in JSON format |

#### Example

| input | output |
|--------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `<@generateJWT(<fixedSecretString>, <{"role":"test","exp":1798761600}>)>` | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTg3NjE2MDAsImlhdCI6MTcyOTA2OTYzMiwiaXNzIjoiemVyb3BzIiwicm9sZSI6InRlc3QifQ.2ba5vp9irnTabfTR1Co8Hd2LY8JfdC473Bb5k7yR-zQ |
| `<@generateJWT(<fixedSecretString>, <{}>)>` | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MjkwNjk4NTcsImlzcyI6Inplcm9wcyJ9.xPrqHDtGhK5c7WMJliguwBeKI29qzAoD7KXrtACbjio |
| `<@generateJWT(<@generateRandomStringVar(<jwtSecretKey>, <32>)>, <{"role":"test","exp":1798761600}>)>` | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTg3NjE2MDAsImlhdCI6MTcyOTA2OTYzMiwiaXNzIjoiemVyb3BzIiwicm9sZSI6InRlc3QifQ.ksbck_HQv44YXbqJk6lDrGFYTq3nmLydFIe0Xlejk5Q |
| `<@generateJWT(<@getVar(jwtSecretKeyVar)>, <{"role":"test","exp":1798761600}>)>` | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTg3NjE2MDAsImlhdCI6MTcyOTA2OTYzMiwiaXNzIjoiemVyb3BzIiwicm9sZSI6InRlc3QifQ.O0RaXzGFwj2t8P2kc4nU4PfuI43-dAuAl2d0T1uUlEE |

</details>

---

### `generateED25519Key(name)`

Generates Public and Private `ED25519` key pairs and stores them for later use under `name`+`version suffix`.
Expand Down
3 changes: 3 additions & 0 deletions example.parsed.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ services:
RANDOM_BYTES_HEX: f76d940f72964665f24719788539fcb5bbb4a4a0
RANDOM_BYTES_STRING: 3TupYwgLYhz4f58170KG

JWT_KEY_SECRET: i3qjKstmXa4GOrZnPtZXK_2V_2-o0vTj
JWT_KEY: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3OTg3NjE2MDAsImlhdCI6MTcyOTA3MDMwNCwiaXNzIjoiemVyb3BzIiwicm9sZSI6InRlc3QifQ.7RmgZ6pM5F0t_kmKnEIzbu_N9L_ZlIu8QCl58_nLpZU

ED25519_KEY_PUB: |
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAGKWmf+yIzNuuBHxOSoQNqQyAKsh1ewlxGuPeNJtseJs=
Expand Down
3 changes: 3 additions & 0 deletions example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ services:
RANDOM_BYTES_HEX: <@getVar(randomBytes) | toHex>
RANDOM_BYTES_STRING: <@getVar(randomBytes) | toString>

JWT_KEY_SECRET: <@generateRandomStringVar(<jwtKeySecret>, <32>)>
JWT_KEY: <@generateJWT(<@getVar(jwtKeySecret)>, <{"role":"test","exp":1798761600}>)>

ED25519_KEY_PUB: |
<@generateED25519Key(<myED25519Key>)>
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.22

require (
github.com/bykof/gostradamus v1.1.1
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/spf13/cobra v1.8.0
golang.org/x/crypto v0.23.0
golang.org/x/text v0.15.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down
26 changes: 26 additions & 0 deletions src/functions/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ import (
cryptoRand "crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"math/big"
mathRand "math/rand"
"strconv"
"strings"
"time"

"github.com/bykof/gostradamus"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/ssh"

"github.com/zeropsio/zParser/v2/src/util"
Expand Down Expand Up @@ -55,6 +58,7 @@ func NewFunctions(valueStore map[string]string) *Functions {
"generateED25519Key": f.generateED25519Key,
"generateRSA2048Key": f.generateRSA2048Key,
"generateRSA4096Key": f.generateRSA4096Key,
"generateJWT": f.generateJWT,
}
return f
}
Expand Down Expand Up @@ -275,6 +279,28 @@ func (f Functions) generateRSAKey(name string, bits int) (string, error) {
return f.values[name+suffixPublic], nil
}

// TODO(ms): Add support for other signing methods than fixed HS256
func (f Functions) generateJWT(param ...string) (string, error) {
if len(param) < 2 {
return "", fmt.Errorf("invalid parameter count, at least 2 expected %d provided", len(param))
}

var payload = jwt.MapClaims{
"iss": "zerops",
"iat": time.Now().Unix(),
}
if err := json.NewDecoder(strings.NewReader(param[1])).Decode(&payload); err != nil {
return "", fmt.Errorf("failed to decode provided JSON payload: %w", err)
}

token := jwt.NewWithClaims(jwt.SigningMethodHS256, payload)
tokenString, err := token.SignedString([]byte(param[0]))
if err != nil {
return "", fmt.Errorf("failed to sign token: %w", err)
}
return tokenString, nil
}

func paramCountCheck(expected, received int) error {
if expected != received {
return fmt.Errorf("invalid parameter count, %d expected %d provided", expected, received)
Expand Down
67 changes: 67 additions & 0 deletions src/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"testing"
"time"

"github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"

"github.com/zeropsio/zParser/v2/src/metaError"
Expand Down Expand Up @@ -260,6 +261,72 @@ func TestImportParser_Parse(t *testing.T) {
fields: getFields(1024, 2, MultilinePreserved, `<@setVar(<name>, <my completely custom string>)>|<@getVar(name)>`),
want: wantStaticString(`my completely custom string|my completely custom string`),
},
{
name: "generate JWT",
fields: getFields(1024, 1, MultilinePreserved, `<@generateJWT(<jwtTokenSecretKey>, <{"role":"test","exp":1799535600}>)>`),
want: func(s string) error {
now := time.Now()

token, err := jwt.Parse(s, func(token *jwt.Token) (interface{}, error) {
return []byte("jwtTokenSecretKey"), nil
})
if err != nil {
return err
}

iss, err := token.Claims.GetIssuer()
if err != nil {
return err
}
if iss != "zerops" {
return fmt.Errorf("expected issuer to be zerops, received: %s", iss)
}

iat, err := token.Claims.GetIssuedAt()
if err != nil {
return err
}
if now.Sub(iat.Time).Seconds() > 1 {
return fmt.Errorf("expected issued date to be within 1 second of now, %s vs %s", now.String(), iat.Time.String())
}

exp, err := token.Claims.GetExpirationTime()
if err != nil {
return err
}
if exp.Unix() != 1799535600 {
return fmt.Errorf("expected expiry to be 1799535600, received: %v", exp.Unix())
}

claims, _ := token.Claims.(jwt.MapClaims)
if claims["role"] != "test" {
return fmt.Errorf("expected role to be test, received: %v", claims["role"])
}

return nil
},
},
{
name: "generate JWT with different issuer",
fields: getFields(1024, 1, MultilinePreserved, `<@generateJWT(<jwtTokenSecretKey>, <{"role":"test","iss":"test","exp":1799535600}>)>`),
want: func(s string) error {
token, err := jwt.Parse(s, func(token *jwt.Token) (interface{}, error) {
return []byte("jwtTokenSecretKey"), nil
})
if err != nil {
return err
}

iss, err := token.Claims.GetIssuer()
if err != nil {
return err
}
if iss != "test" {
return fmt.Errorf("expected issuer to be test, received: %s", iss)
}
return nil
},
},
{
name: "multi line output preserve",
fields: getFields(1024, 1, MultilinePreserved, "\t\t<@generateED25519Key(<key>)>"),
Expand Down
8 changes: 7 additions & 1 deletion src/util/argon2id.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"encoding/base64"
"errors"
"fmt"
"math"
"strings"

"golang.org/x/crypto/argon2"
Expand Down Expand Up @@ -88,7 +89,12 @@ func Argon2IDPasswordVerify(hash, plain string) error {
return err
}

hashToCompare := argon2.IDKey([]byte(plain), salt, time, memory, threads, uint32(len(decodedHash)))
hashLen := len(decodedHash)
if hashLen > math.MaxUint32 {
return fmt.Errorf("invalid decoded hash length %d, exceeds max value for uint32", hashLen)
}

hashToCompare := argon2.IDKey([]byte(plain), salt, time, memory, threads, uint32(hashLen))
if subtle.ConstantTimeCompare(decodedHash, hashToCompare) != 1 {
return errors.New("hashedPassword is not the hash of the given password")
}
Expand Down

0 comments on commit ade4df2

Please sign in to comment.