Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Go Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.25'

- name: Build Client
run: go build -o ./freon -v ./client

- name: Build Coordinator
run: go build -o ./freon-server -v ./coordinator

- name: Test Client
run: cd client && go test -v -short && cd ..

- name: Test Coordinator
run: cd coordinator && go test -v -short && cd ..
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

> FOSS Resists Executive Overreaching Nations

[![Build Status](https://github.com/soatok/freon/actions/workflows/ci.yml/badge.svg)](https://github.com/soatok/freon/actions/workflows/ci.yml)

FREON implements FROST ([RFC 9591](https://www.rfc-editor.org/rfc/rfc9591.html)) to allow geographically distributed teams produce digital signatures.

Each share of the signing key is encrypted locally using [age](https://github.com/FiloSottile/age).
Expand Down Expand Up @@ -44,7 +46,7 @@ go install
```terminal
git clone https://github.com/soatok/freon.git
cd freon
CGO_ENABLED=1 go build -o coordinator ./coordinator
go build -o coordinator ./coordinator
./coordinator
```

Expand Down
5 changes: 1 addition & 4 deletions client/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,11 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
114 changes: 114 additions & 0 deletions client/internal/crypto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package internal_test

import (
"bytes"
"os"
"path/filepath"
"testing"

"filippo.io/age"
"github.com/soatok/freon/client/internal"
)

func TestEncryptDecryptShare(t *testing.T) {
identity, err := age.GenerateX25519Identity()
if err != nil {
t.Fatal(err)
}

recipient := identity.Recipient().String()
share := []byte("test share")

encrypted, err := internal.EncryptShare(recipient, share)
if err != nil {
t.Fatal(err)
}

decrypted, err := internal.DecryptShare(encrypted, identity)
if err != nil {
t.Fatal(err)
}

if !bytes.Equal(share, decrypted) {
t.Fatal("decrypted share does not match original share")
}
}

func TestParseAgeIdentityFile(t *testing.T) {
identity, err := age.GenerateX25519Identity()
if err != nil {
t.Fatal(err)
}

tempDir := t.TempDir()
identityFile := filepath.Join(tempDir, "key.txt")

f, err := os.Create(identityFile)
if err != nil {
t.Fatal(err)
}
defer f.Close()

_, err = f.WriteString(identity.String())
if err != nil {
t.Fatal(err)
}
f.Close()

identities, err := internal.ParseAgeIdentityFile(identityFile)
if err != nil {
t.Fatal(err)
}

if len(identities) != 1 {
t.Fatalf("expected 1 identity, got %d", len(identities))
}

parsedIdentity, ok := identities[0].(*age.X25519Identity)
if !ok {
t.Fatal("identity is not of type X25519Identity")
}

if parsedIdentity.String() != identity.String() {
t.Fatal("parsed identity does not match original identity")
}
}

func TestDecryptShareFor(t *testing.T) {
identity, err := age.GenerateX25519Identity()
if err != nil {
t.Fatal(err)
}

recipient := identity.Recipient().String()
share := []byte("test share")

encrypted, err := internal.EncryptShare(recipient, share)
if err != nil {
t.Fatal(err)
}

tempDir := t.TempDir()
identityFile := filepath.Join(tempDir, "key.txt")

f, err := os.Create(identityFile)
if err != nil {
t.Fatal(err)
}
defer f.Close()

_, err = f.WriteString(identity.String())
if err != nil {
t.Fatal(err)
}
f.Close()

decrypted, err := internal.DecryptShareFor(encrypted, identityFile)
if err != nil {
t.Fatal(err)
}

if !bytes.Equal(share, decrypted) {
t.Fatal("decrypted share does not match original share")
}
}
4 changes: 2 additions & 2 deletions client/internal/freon.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ func JoinKeyGenCeremony(host, groupID, recipient string) {
// Let's build the list of public shares
publicShares := make(map[string]string)
for index, sh := range public.Shares {
i := uint16ToHexBE(uint16(index))
i := Uint16ToHexBE(uint16(index))
shh := hex.EncodeToString(sh.BytesEd25519())
publicShares[i] = shh
}
Expand Down Expand Up @@ -405,7 +405,7 @@ func JoinSignCeremony(ceremonyID, host, identityFile string, message []byte) {
// Let's deserialize the public shares
publicShares := make(map[party.ID]*ristretto.Element, len(publicSharesHex))
for k, v := range publicSharesHex {
p16, err := hexBEToUint16(k)
p16, err := HexBEToUint16(k)
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err.Error())
os.Exit(1)
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions client/internal/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ func HashMessageForSanity(data []byte, groupID string) string {
return hex.EncodeToString(mac.Sum(nil))
}

func uint16ToHexBE(n uint16) string {
func Uint16ToHexBE(n uint16) string {
bytes := []byte{byte(n >> 8), byte(n)}
return hex.EncodeToString(bytes)
}

func hexBEToUint16(s string) (uint16, error) {
func HexBEToUint16(s string) (uint16, error) {
bytes, err := hex.DecodeString(s)
if err != nil {
return 0, err
Expand Down
18 changes: 18 additions & 0 deletions client/internal/util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,3 +113,21 @@ func TestPartyToUint16(t *testing.T) {
slice := internal.PartyToUint16(party)
assert.Equal(t, []uint16{5, 6, 7}, slice)
}

func TestHexBEToUint16(t *testing.T) {
val, err := internal.HexBEToUint16("0100")
assert.NoError(t, err)
assert.Equal(t, uint16(256), val)

val, err = internal.HexBEToUint16("ffff")
assert.NoError(t, err)
assert.Equal(t, uint16(65535), val)
}

func TestUint16ToHexBE(t *testing.T) {
hex := internal.Uint16ToHexBE(256)
assert.Equal(t, "0100", hex)

hex = internal.Uint16ToHexBE(65535)
assert.Equal(t, "ffff", hex)
}
1 change: 0 additions & 1 deletion client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,6 @@ func FreonSignList(args []string) {
fs.Usage()
os.Exit(1)
}

internal.ListSign(*host, *groupID, *limit, *offset)
}

Expand Down
15 changes: 13 additions & 2 deletions coordinator/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,19 @@ require github.com/taurusgroup/frost-ed25519 v0.0.0-20210707140332-5abc84a4dba7

require github.com/alexedwards/scs/v2 v2.9.0

require github.com/mattn/go-sqlite3 v1.14.32
require (
github.com/ncruces/go-sqlite3 v0.28.0
github.com/stretchr/testify v1.10.0
)

require filippo.io/edwards25519 v1.1.0 // indirect
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/ncruces/julianday v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tetratelabs/wazero v1.9.0 // indirect
golang.org/x/sys v0.35.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

replace github.com/taurusgroup/frost-ed25519 => github.com/soatok/frost-ed25519 v0.0.0-20250805104728-ae78c7826e4b
13 changes: 11 additions & 2 deletions coordinator/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gv
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/mattn/go-sqlite3 v1.14.30 h1:bVreufq3EAIG1Quvws73du3/QgdeZ3myglJlrzSYYCY=
github.com/mattn/go-sqlite3 v1.14.30/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/ncruces/go-sqlite3 v0.28.0 h1:AQVTUPgfamONl09LS+4rGFbHmLKM8/QrJJJi1UukjEQ=
github.com/ncruces/go-sqlite3 v0.28.0/go.mod h1:WqvLhYwtEiZzg1H8BIeahUv/DxbmR+3xG5jDHDiBAGk=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/soatok/frost-ed25519 v0.0.0-20250805104728-ae78c7826e4b h1:BmCLB7/3z4mYken+4TYyJ5n+/VVSjE+WFUYi1P4IHUo=
Expand All @@ -22,6 +24,13 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
10 changes: 5 additions & 5 deletions coordinator/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ func getConfigFile() (string, error) {

// Default user config
func NewServerConfig() (CoordinatorConfig, error) {
config := CoordinatorConfig{}
config := CoordinatorConfig{
Hostname: "localhost:8462",
Database: "./database.sqlite",
}
err := config.Save()
if err != nil {
return CoordinatorConfig{
Hostname: "localhost:8462",
Database: "./database.sqlite",
}, err
return CoordinatorConfig{}, err
}
return config, nil
}
Expand Down
47 changes: 47 additions & 0 deletions coordinator/internal/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package internal_test

import (
"os"
"path/filepath"
"testing"

"github.com/soatok/freon/coordinator/internal"
"github.com/stretchr/testify/assert"
)

func TestServerConfig(t *testing.T) {
tempDir := t.TempDir()
t.Setenv("HOME", tempDir)
// Windows uses this environment variable instead
t.Setenv("USERPROFILE", tempDir)
os.Mkdir(tempDir, 0700)

// Test NewServerConfig
config, err := internal.NewServerConfig()
assert.NoError(t, err)
assert.Equal(t, config.Hostname, "localhost:8462")
assert.Equal(t, config.Database, "./database.sqlite")

if err := config.Save(); err != nil {
panic(err)
}

// Check if the file was created
configPath := filepath.Join(tempDir, ".freon-coordinator.json")
_, err = os.Stat(configPath)
assert.NoError(t, err)

// Test LoadServerConfig
loadedConfig, err := internal.LoadServerConfig()
assert.NoError(t, err)
assert.Equal(t, config, loadedConfig)

// Test Save
loadedConfig.Hostname = "example.com:1234"
err = loadedConfig.Save()
assert.NoError(t, err)

savedConfig, err := internal.LoadServerConfig()
assert.NoError(t, err)
assert.Equal(t, loadedConfig, savedConfig)
}
Loading