Skip to content

Commit a4aee74

Browse files
committed
started working on adding support for encrypted scraper users
1 parent 0f09c59 commit a4aee74

File tree

10 files changed

+248
-20
lines changed

10 files changed

+248
-20
lines changed

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"deno.enable": true,
3+
"deno.unstable": true
4+
}

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,18 @@ go install github.com/script-development/rtcv_scraper_client/v2@v2.3.0
5353
5454
### *2.* Obtain a `env.json`
5555
56-
Create a `env.json` file with the following content **(this file can also be obtained from the RTCV dashboard, tough note that you might need to add login_users yourself)**
56+
Create a `env.json` file with the following content **(this file can also be obtained from the RTCV dashboard, tough note that you need to add the `private_key` and `public_key` yourself)**
57+
58+
The public and private key pair can be generated using the go program inside the [gen_key](./gen_key/) folder
59+
5760
```js
5861
{
5962
"primary_server": {
6063
"server_location": "http://localhost:4000",
6164
"api_key_id": "aa",
62-
"api_key": "bbb"
65+
"api_key": "bbb",
66+
"private_key": "......",
67+
"public_key": "......"
6368
},
6469
"alternative_servers": [
6570
// If you want to send CVs to multiple servers you can add additional servers here

crypto/crypto.go

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package crypto
2+
3+
import (
4+
crypto_rand "crypto/rand"
5+
"encoding/base64"
6+
"errors"
7+
"log"
8+
9+
"golang.org/x/crypto/nacl/box"
10+
)
11+
12+
func CreateKeys() (pubBase64 string, privBase64 string) {
13+
pub, priv, err := box.GenerateKey(crypto_rand.Reader)
14+
if err != nil {
15+
log.Fatal(err)
16+
}
17+
return base64.StdEncoding.EncodeToString(pub[:]), base64.StdEncoding.EncodeToString(priv[:])
18+
}
19+
20+
func LoadAndVerivyKeys(pubBase64 string, privBase64 string) *Key {
21+
pubBytes, err := base64.StdEncoding.DecodeString(pubBase64)
22+
if err != nil {
23+
log.Fatal(err)
24+
}
25+
privBytes, err := base64.StdEncoding.DecodeString(privBase64)
26+
if err != nil {
27+
log.Fatal(err)
28+
}
29+
30+
if len(pubBytes) != 32 {
31+
log.Fatal("base64 decoded public key is not 32 bytes long")
32+
}
33+
if len(privBytes) != 32 {
34+
log.Fatal("base64 decoded private key is not 32 bytes long")
35+
}
36+
37+
k := &Key{
38+
pub: new([32]byte),
39+
PublicBase64: pubBase64,
40+
priv: new([32]byte),
41+
}
42+
43+
copy(k.pub[:], pubBytes)
44+
copy(k.priv[:], privBytes)
45+
46+
encryptedTest, err := box.SealAnonymous(nil, []byte("test"), k.pub, crypto_rand.Reader)
47+
if err != nil {
48+
log.Fatal(err)
49+
}
50+
51+
decryptedTest, err := k.rawDecrypt(encryptedTest)
52+
if err != nil {
53+
log.Fatal("decryption failed, private and/or public key are probably incorrect")
54+
}
55+
56+
if string(decryptedTest) != "test" {
57+
log.Fatal("decrypted test message does not match the original message")
58+
}
59+
60+
return k
61+
}
62+
63+
type Key struct {
64+
pub *[32]byte
65+
PublicBase64 string
66+
priv *[32]byte
67+
}
68+
69+
func (k *Key) rawDecrypt(val []byte) ([]byte, error) {
70+
decryptedValue, ok := box.OpenAnonymous(nil, val, k.pub, k.priv)
71+
if !ok {
72+
return nil, errors.New("decryption failed, either private/public/message are incorrect")
73+
}
74+
return decryptedValue, nil
75+
}
76+
77+
func (k *Key) DecryptScraperPassword(encryptedPassword string) (string, error) {
78+
encryptedPasswordBytes, err := base64.StdEncoding.DecodeString(encryptedPassword)
79+
if err != nil {
80+
return "", nil
81+
}
82+
83+
passwordBytes, err := k.rawDecrypt(encryptedPasswordBytes)
84+
if err != nil {
85+
return "", nil
86+
}
87+
88+
// Note that the first 32 bytes of a encrypted bytes are just junk to make the encrypted value harder to guess "hopefully"
89+
return string(passwordBytes[32:]), nil
90+
}

gen_key/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Generate scraper users encryption key pair
2+
3+
```sh
4+
go run .
5+
```

gen_key/go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/script-development/gen_key
2+
3+
go 1.18
4+
5+
replace github.com/script-development/rtcv_scraper_client/v2 => ../
6+
7+
require (
8+
github.com/script-development/rtcv_scraper_client/v2 v2.4.0 // indirect
9+
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
10+
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
11+
)

gen_key/go.sum

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
2+
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
3+
github.com/klauspost/compress v1.15.0 h1:xqfchp4whNFxn5A4XFyyYtitiWI8Hy5EW59jEwcyL6U=
4+
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
5+
github.com/script-development/rtcv_scraper_client/v2 v2.4.0 h1:ZdxB15fyW1nTSdGg8K6ksiX/pWgrGHa74aO6XvbGMME=
6+
github.com/script-development/rtcv_scraper_client/v2 v2.4.0/go.mod h1:xP4vAvJLoCDkH5LLN2QM2xLE2zkTex0fu01o/zIffs0=
7+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
8+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
9+
github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkMbE=
10+
github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
11+
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
12+
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
13+
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
14+
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
15+
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
16+
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
17+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
18+
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
19+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
20+
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
21+
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
22+
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
23+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
24+
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
25+
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
26+
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
27+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

gen_key/main.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/script-development/rtcv_scraper_client/v2/crypto"
8+
)
9+
10+
func main() {
11+
pub, priv := crypto.CreateKeys()
12+
13+
fmt.Println("Generated keys:")
14+
fmt.Println("don't lose these keys!!")
15+
16+
fmt.Println()
17+
fmt.Println("Public key:")
18+
fmt.Println(pub)
19+
fmt.Println("Private key:")
20+
fmt.Println(priv)
21+
22+
fmt.Println()
23+
fmt.Println("Add them here your scrapers `env.json` file using")
24+
envAddition, _ := json.MarshalIndent(map[string]any{
25+
"primary_server": map[string]string{
26+
"server_location": "..",
27+
"api_key_id": "..",
28+
"api_key": "..",
29+
"private_key": priv,
30+
"public_key": pub,
31+
},
32+
}, "", " ")
33+
fmt.Println(string(envAddition))
34+
35+
fmt.Println()
36+
fmt.Println("Don't forget to add the Public Key to the scraper api key on the RT-CV dashboard")
37+
}

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
module github.com/script-development/rtcv_scraper_client/v2
22

3-
go 1.17
3+
go 1.18
44

55
require github.com/valyala/fasthttp v1.37.0
66

77
require (
88
github.com/andybalholm/brotli v1.0.4 // indirect
99
github.com/klauspost/compress v1.15.0 // indirect
1010
github.com/valyala/bytebufferpool v1.0.0 // indirect
11+
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 // indirect
12+
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
1113
)

go.sum

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,15 @@ github.com/valyala/fasthttp v1.37.0 h1:7WHCyI7EAkQMVmrfBhWTCOaeROb1aCBiTopx63LkM
88
github.com/valyala/fasthttp v1.37.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
99
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
1010
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
11+
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8 h1:GIAS/yBem/gq2MUqgNIzUHW7cJMmx3TGZOrnyYaNQ6c=
12+
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
1113
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
1214
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
1315
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1416
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
1517
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1618
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
19+
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
1720
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
1821
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
1922
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

main.go

Lines changed: 61 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"os/exec"
1111
"sync"
1212
"syscall"
13+
14+
"github.com/script-development/rtcv_scraper_client/v2/crypto"
1315
)
1416

1517
// Env contains the structure of the .env file
@@ -23,22 +25,22 @@ type Env struct {
2325
func (e *Env) validate() error {
2426
if e.MockMode {
2527
if len(e.MockUsers) == 0 {
26-
fmt.Println("\"mock_users\" is empty in env.json, most scrapers require at least one user to login.")
27-
fmt.Println("For documentation about mocking see https://github.com/script-development/rtcv_scraper_client")
28+
fmt.Println(`"mock_users" is empty in env.json, most scrapers require at least one user to login.`)
29+
fmt.Println(`For documentation about mocking see https://github.com/script-development/rtcv_scraper_client`)
2830
if e.MockUsers == nil {
2931
e.MockUsers = []EnvUser{}
3032
}
3133
}
3234
return nil
3335
}
3436

35-
err := e.PrimaryServer.validate()
37+
err := e.PrimaryServer.validate(true)
3638
if err != nil {
3739
return fmt.Errorf("primary_server.%s", err.Error())
3840
}
3941

4042
for idx, server := range e.AlternativeServers {
41-
err := server.validate()
43+
err := server.validate(false)
4244
if err != nil {
4345
return fmt.Errorf("%s[%d].%s", "alternative_servers", idx, err.Error())
4446
}
@@ -52,9 +54,11 @@ type EnvServer struct {
5254
ServerLocation string `json:"server_location"`
5355
APIKeyID string `json:"api_key_id"`
5456
APIKey string `json:"api_key"`
57+
PrivateKey string `json:"private_key"`
58+
PublicKey string `json:"public_key"`
5559
}
5660

57-
func (e *EnvServer) validate() error {
61+
func (e *EnvServer) validate(isPrimary bool) error {
5862
if e.ServerLocation == "" {
5963
return errors.New("server_location is required")
6064
}
@@ -64,6 +68,23 @@ func (e *EnvServer) validate() error {
6468
if e.APIKey == "" {
6569
return errors.New("api_key is required")
6670
}
71+
if isPrimary {
72+
if e.PrivateKey == "" && e.PublicKey == "" {
73+
return errors.New(`"public_key" and "private_key" required by "primary_server"`)
74+
} else if e.PrivateKey == "" {
75+
return errors.New(`"private_key" required by "primary_server"`)
76+
} else if e.PublicKey == "" {
77+
return errors.New(`"public_key" required by "primary_server"`)
78+
}
79+
} else {
80+
if e.PrivateKey != "" && e.PublicKey != "" {
81+
fmt.Println("WARN: public_key and private_key pairs are not used by alternative servers")
82+
} else if e.PrivateKey != "" {
83+
fmt.Println("WARN: private_key not used by alternative servers")
84+
} else if e.PublicKey != "" {
85+
fmt.Println("WARN: public_key not used by alternative servers")
86+
}
87+
}
6788

6889
return nil
6990
}
@@ -79,8 +100,9 @@ func (e *EnvServer) toCredArg(isPrimary bool) SetCredentialsArg {
79100

80101
// EnvUser contains the structure of the login_users inside the .env file
81102
type EnvUser struct {
82-
Username string `json:"username"`
83-
Password string `json:"password"`
103+
Username string `json:"username"`
104+
Password string `json:"password"`
105+
EncryptedPassword string `json:"encryptedPassword,omitempty"`
84106
}
85107

86108
func main() {
@@ -120,16 +142,17 @@ func main() {
120142
credentials = append(credentials, server.toCredArg(false))
121143
}
122144

123-
loginUsers := []EnvUser{}
145+
var loginUsers []EnvUser
124146
if !env.MockMode {
125147
err = api.SetCredentials(credentials)
126148
if err != nil {
127149
log.Fatal(err)
128150
}
151+
decryptionKey := crypto.LoadAndVerivyKeys(env.PrimaryServer.PublicKey, env.PrimaryServer.PrivateKey)
129152

130153
fmt.Println("credentials set")
131154
fmt.Println("testing connections..")
132-
loginUsers = testServerConnections(api, credentials[0].APIKeyID)
155+
loginUsers = testServerConnections(api, credentials[0].APIKeyID, decryptionKey)
133156
fmt.Println("connected to RTCV")
134157
} else {
135158
api.SetMockMode()
@@ -165,7 +188,7 @@ func main() {
165188
}
166189
}
167190

168-
func testServerConnections(api *API, apiKeyID string) []EnvUser {
191+
func testServerConnections(api *API, apiKeyID string, decryptionKey *crypto.Key) []EnvUser {
169192
var wg sync.WaitGroup
170193

171194
for _, conn := range api.connections {
@@ -201,17 +224,38 @@ func testServerConnections(api *API, apiKeyID string) []EnvUser {
201224
}
202225

203226
scraperUsers := struct {
204-
Users []EnvUser `json:"users"`
227+
ScraperPublicKey string `json:"scraperPubKey"`
228+
Users []EnvUser `json:"users"`
205229
}{}
206230
err := api.connections[0].Get("/api/v1/scraperUsers/"+apiKeyID, &scraperUsers)
207-
208-
// Wait for the connections above to complete checking before we do this error check but do the request already so we don't have to wait for that
209-
// If one of the connections has an error they will throw
210-
wg.Wait()
211-
212231
if err != nil {
232+
// Wait for the connections above to complete checking before we do this error check but do the request already so we don't have to wait for that
233+
// If one of the connections has an error they will throw
234+
wg.Wait()
213235
log.Fatal(err)
214236
}
215237

216-
return scraperUsers.Users
238+
if scraperUsers.ScraperPublicKey != "" && scraperUsers.ScraperPublicKey != decryptionKey.PublicBase64 {
239+
log.Fatal("the env.json provided contains a diffrent public key than registered in RTCV, scraper users won't be able to be decrypted")
240+
}
241+
242+
loginUsers := []EnvUser{}
243+
for _, user := range scraperUsers.Users {
244+
if user.EncryptedPassword != "" {
245+
user.Password, err = decryptionKey.DecryptScraperPassword(user.EncryptedPassword)
246+
if err != nil {
247+
log.Fatal("unable to decrypt password for user " + user.Username + ", error: " + err.Error())
248+
}
249+
loginUsers = append(loginUsers, user)
250+
} else if user.Password != "" {
251+
loginUsers = append(loginUsers, user)
252+
} else {
253+
fmt.Println("WARN: unusable login user", user.Username)
254+
}
255+
loginUsers = append(loginUsers, user)
256+
}
257+
258+
wg.Wait()
259+
260+
return loginUsers
217261
}

0 commit comments

Comments
 (0)