commit
f266968e6d
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
package main
|
||||
|
||||
func main() {}
|
|
@ -0,0 +1,5 @@
|
|||
module tulpa.dev/cadey/thoth
|
||||
|
||||
go 1.16
|
||||
|
||||
require golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce // indirect
|
|
@ -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=
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
go gopls goimports
|
||||
|
||||
# keep this line if you use bash
|
||||
pkgs.bashInteractive
|
||||
];
|
||||
}
|
|
@ -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.
|
Loading…
Reference in New Issue