initial commit

Signed-off-by: Xe <me@christine.website>
This commit is contained in:
Cadey Ratio 2022-01-21 21:25:39 -05:00
commit f266968e6d
10 changed files with 287 additions and 0 deletions

1
.envrc Normal file
View File

@ -0,0 +1 @@
eval "$(lorri direnv)"

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2022 Xe <me@christine.website>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

54
changeset/changeset.go Normal file
View File

@ -0,0 +1,54 @@
package changeset
import (
"fmt"
"golang.org/x/crypto/ssh"
)
// Metadata is the metadata for the changeset.
type Metadata struct {
// The name of the service
Name string
// The git hash of the repo checkout that made the service
Version string
// Slug SHA256 hash, this is what is signed
Hash []byte
// The SSH signatures for this ChangeSet
Signatures []*ssh.Signature
}
type ChangeSet struct {
Metadata Metadata
SlugFile string
}
func (cs ChangeSet) Validate(trustedKeys []ssh.PublicKey, minNeeded int) error {
var gotKeys = map[string]struct{}{}
for _, sig := range cs.Metadata.Signatures {
for _, pubkey := range trustedKeys {
fp := ssh.FingerprintSHA256(pubkey)
if _, got := gotKeys[fp]; got {
return fmt.Errorf("changeset: %v signed this changeset more than once", fp)
}
if fp != string(sig.Rest) {
continue
}
err := pubkey.Verify(cs.Metadata.Hash, sig)
if err != nil {
return err
}
gotKeys[fp] = struct{}{}
}
}
if gk := len(gotKeys); gk != minNeeded {
return fmt.Errorf("wanted %d keys, only signed with %d keys", minNeeded, gk)
}
return nil
}

View File

@ -0,0 +1,82 @@
package changeset
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/json"
"os"
"testing"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
func sha256sum(data []byte) []byte {
h := sha256.New()
h.Write(data)
return h.Sum(nil)
}
func TestChangeSetSimpleValidate(t *testing.T) {
a := agent.NewKeyring()
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
err = a.Add(agent.AddedKey{
PrivateKey: key,
})
if err != nil {
t.Fatal(err)
}
slugLoc := "../var/thoth.md"
data, err := os.ReadFile(slugLoc)
if err != nil {
t.Fatal(err)
}
checksum := sha256sum(data)
var sigs []*ssh.Signature
var pubkeys []ssh.PublicKey
aPubkeys, err := a.List()
if err != nil {
t.Fatal(err)
}
for _, ak := range aPubkeys {
pubkeys = append(pubkeys, ak)
}
for _, ak := range pubkeys {
sig, err := a.Sign(ak, checksum)
if err != nil {
t.Fatal(err)
}
sig.Rest = []byte(ssh.FingerprintSHA256(ak))
sigs = append(sigs, sig)
}
cs := ChangeSet{
Metadata: Metadata{
Name: "test",
Version: "test",
Hash: checksum,
Signatures: sigs,
},
SlugFile: slugLoc,
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(cs)
err = cs.Validate(pubkeys, 1)
if err != nil {
t.Fatal(err)
}
}

3
cmd/thoth/main.go Normal file
View File

@ -0,0 +1,3 @@
package main
func main() {}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module tulpa.dev/cadey/thoth
go 1.16
require golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect

10
go.sum Normal file
View File

@ -0,0 +1,10 @@
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce h1:Roh6XWxHFKrPgC/EQhVubSAGQ6Ozk6IdxHSzt1mR0EI=
golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

76
main.go Normal file
View File

@ -0,0 +1,76 @@
package main
import (
"encoding/json"
"flag"
"log"
"net"
"os"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
)
var (
fname = flag.String("fname", "var/thoth.md", "file to sign and validate")
trustedPubkeys = []string{
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPg9gYKVglnO2HQodSJt4z4mNrUSUiyJQ7b+J798bwD9",
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPYr9hiLtDHgd6lZDgQMkJzvYeAXmePOrgFaWHAjJvNU",
}
)
func main() {
flag.Parse()
// ssh-agent(1) provides a UNIX socket at $SSH_AUTH_SOCK.
socket := os.Getenv("SSH_AUTH_SOCK")
conn, err := net.Dial("unix", socket)
if err != nil {
log.Fatalf("Failed to open SSH_AUTH_SOCK: %v", err)
}
agentClient := agent.NewClient(conn)
data, err := os.ReadFile(*fname)
if err != nil {
log.Fatal(err)
}
var sigs []*ssh.Signature
var pubkeys []ssh.PublicKey
for _, pkString := range trustedPubkeys {
pubkey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pkString))
if err != nil {
log.Fatal(err)
}
pubkeys = append(pubkeys, pubkey)
sig, err := agentClient.Sign(pubkey, data)
if err != nil {
log.Fatal(err)
}
sig.Rest = []byte(ssh.FingerprintSHA256(pubkey))
sigs = append(sigs, sig)
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(sigs)
os.Stdout.Sync()
for _, sig := range sigs {
for _, pubkey := range pubkeys {
if ssh.FingerprintSHA256(pubkey) != string(sig.Rest) {
continue
}
err := pubkey.Verify(data, sig)
if err != nil {
log.Fatal(err)
}
}
}
}

10
shell.nix Normal file
View File

@ -0,0 +1,10 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = with pkgs; [
go gopls goimports
# keep this line if you use bash
pkgs.bashInteractive
];
}

27
var/thoth.md Normal file
View File

@ -0,0 +1,27 @@
---
title: A Tool to Aid Forgetfulness
date: 2022-01-12
series: stories
---
The Egyptian God Thoth lived in the Egyptian city of Naucratis. Thoth was the
inventor of many arts such as math and astronomy, but the most significant was
the invention of writing. Thoth showed writing to the king of Egypt, claiming
that it would make Egyptians wiser and give them better memories; that it it
would vastly improve both the memory and the wit of the Egyptian people.
The king replied: "Thoth, you invented this tool. As such you are not the best
one to judge such things. You have not created a tool to aid memory, you have
created a tool to aid forgetfulness. Learners will not use their memories, they
will blindly trust these sigils and not remember for themselves.
"You have discovered an aid to vague recollection, as the users of this tool
will not be given truth. They will only be given a semblance of truth.
"They will be hearers of many things and learners of nothing. They will appear
to know all the knowledge of the world yet when asked they will only be the
middleman to external forces that are trusted without verification. They will
know wisdom, but not truth."
Adapted from The Dialogues of Plato in Five Volumes, 3rd ed. Oxford
University, 1892. Vol. 1 pp. 483-489.