Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.

Commit 7c166cd

Browse files
feat: add keygen tool (#972)
1 parent c8f9ce6 commit 7c166cd

File tree

6 files changed

+198
-0
lines changed

6 files changed

+198
-0
lines changed

go.work

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ use (
2424
./sdk/go/examples/time
2525
./sdk/go/examples/vectors
2626
./sdk/go/templates/default
27+
./tools/keygen
2728
)

tools/keygen/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.pem
2+
keygen
3+
keygen.exe

tools/keygen/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Keygen Tool
2+
3+
This is a small utility that will generate keys and tokens for use with Modus.
4+
5+
## Usage
6+
7+
You must have Go installed locally. We currently do not release this tool as a binary.
8+
9+
From the `/tools/keygen` directory, run the utility via: `go run .`
10+
11+
The output will contain the following:
12+
13+
- A JSON encoded object containing an RSA public key, suitable for passing to `MODUS_PEMS`
14+
- A JWT token that is signed with that key, having a 1 year expiration date and no other claims,
15+
suitable for passing as a bearer token for authorization
16+
17+
The tool will also create two files in the current working directory:
18+
19+
- `private-key.pem` - A 2048 bit RSA private key
20+
- `public-key.pem` - The corresponding public key
21+
22+
If the private key file already exists in the working directory, the tool will re-use it instead of
23+
creating a new one.
24+
25+
**IMPORTANT** - Keep the private key secret, and do not lose it! You will need it to issue new JWT
26+
tokens that can be validated with the public key.
27+
28+
The public key file contains the same key that was given as JSON. It is there for convenience only.
29+
30+
You can run the tool multiple times to generate additional JWTs from the same key pair.

tools/keygen/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module keygen
2+
3+
go 1.25.0
4+
5+
require github.com/golang-jwt/jwt/v5 v5.3.0

tools/keygen/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
2+
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=

tools/keygen/main.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package main
2+
3+
import (
4+
"crypto/rand"
5+
"crypto/rsa"
6+
"crypto/x509"
7+
"encoding/json"
8+
"encoding/pem"
9+
"fmt"
10+
"os"
11+
"time"
12+
13+
"github.com/golang-jwt/jwt/v5"
14+
)
15+
16+
const privateKeyFileName = "private-key.pem"
17+
const publicKeyFileName = "public-key.pem"
18+
19+
func main() {
20+
fmt.Println()
21+
privateKey, err := getPrivateKey()
22+
if err != nil {
23+
fmt.Fprintf(os.Stderr, "Error getting private key: %v\n", err)
24+
os.Exit(1)
25+
}
26+
27+
jsonPubKey, err := getPublicKeyJson(privateKey)
28+
if err != nil {
29+
fmt.Fprintf(os.Stderr, "Error getting public key JSON: %v\n", err)
30+
os.Exit(1)
31+
}
32+
fmt.Println(jsonPubKey)
33+
fmt.Println()
34+
35+
signedJWT, err := generateSignedJWT(privateKey)
36+
if err != nil {
37+
fmt.Fprintf(os.Stderr, "Error generating signed JWT: %v\n", err)
38+
os.Exit(1)
39+
}
40+
fmt.Println(signedJWT)
41+
fmt.Println()
42+
}
43+
44+
func generateSignedJWT(priv *rsa.PrivateKey) (string, error) {
45+
now := time.Now()
46+
issued := now.Unix()
47+
expires := now.AddDate(1, 0, 0).Unix()
48+
claims := jwt.MapClaims{
49+
"iat": issued,
50+
"exp": expires,
51+
}
52+
53+
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
54+
return token.SignedString(priv)
55+
}
56+
57+
func getPrivateKey() (*rsa.PrivateKey, error) {
58+
if _, err := os.Stat(privateKeyFileName); err == nil {
59+
return loadPrivateKey()
60+
} else if os.IsNotExist(err) {
61+
return createPrivateKey()
62+
} else {
63+
return nil, err
64+
}
65+
}
66+
67+
func loadPrivateKey() (*rsa.PrivateKey, error) {
68+
keyData, err := os.ReadFile(privateKeyFileName)
69+
if err != nil {
70+
return nil, err
71+
}
72+
73+
block, _ := pem.Decode(keyData)
74+
if block == nil || block.Type != "RSA PRIVATE KEY" {
75+
return nil, fmt.Errorf("failed to decode PEM block containing private key")
76+
}
77+
78+
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
79+
if err != nil {
80+
return nil, err
81+
}
82+
return priv, nil
83+
}
84+
85+
func createPrivateKey() (*rsa.PrivateKey, error) {
86+
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
if err := writePrivateKey(privateKey); err != nil {
92+
return nil, err
93+
}
94+
95+
if err := writePublicKey(privateKey); err != nil {
96+
return nil, err
97+
}
98+
99+
return privateKey, nil
100+
}
101+
102+
func writePrivateKey(priv *rsa.PrivateKey) error {
103+
keyFile, err := os.Create(privateKeyFileName)
104+
if err != nil {
105+
return err
106+
}
107+
defer keyFile.Close()
108+
109+
pemBlock := &pem.Block{
110+
Type: "RSA PRIVATE KEY",
111+
Bytes: x509.MarshalPKCS1PrivateKey(priv),
112+
}
113+
if err := pem.Encode(keyFile, pemBlock); err != nil {
114+
return err
115+
}
116+
117+
return nil
118+
}
119+
120+
func writePublicKey(priv *rsa.PrivateKey) error {
121+
publicKey := &priv.PublicKey
122+
123+
keyFile, err := os.Create(publicKeyFileName)
124+
if err != nil {
125+
return err
126+
}
127+
defer keyFile.Close()
128+
129+
pemBlock := &pem.Block{
130+
Type: "RSA PUBLIC KEY",
131+
Bytes: x509.MarshalPKCS1PublicKey(publicKey),
132+
}
133+
if err := pem.Encode(keyFile, pemBlock); err != nil {
134+
return err
135+
}
136+
137+
return nil
138+
}
139+
140+
func getPublicKeyJson(priv *rsa.PrivateKey) (string, error) {
141+
publicKey := &priv.PublicKey
142+
keyData := x509.MarshalPKCS1PublicKey(publicKey)
143+
block := pem.EncodeToMemory(&pem.Block{
144+
Type: "RSA PUBLIC KEY",
145+
Bytes: keyData,
146+
})
147+
148+
keys := map[string]string{
149+
"key1": string(block),
150+
}
151+
152+
jsonData, err := json.Marshal(keys)
153+
if err != nil {
154+
return "", err
155+
}
156+
return string(jsonData), nil
157+
}

0 commit comments

Comments
 (0)