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