From 707a0c701fcc485234b271339ca6d14c511ecd98 Mon Sep 17 00:00:00 2001 From: Oskar Sharipov Date: Fri, 22 Jul 2022 01:51:44 +0300 Subject: [PATCH] Init project --- .gitignore | 1 + cmd/mstrusted/mstrusted.go | 133 +++++++++++++++++++++++++++++ go.mod | 10 +++ go.sum | 12 +++ internal/testdata/key.priv | 2 + internal/testdata/key.pub | 2 + internal/testdata/test.txt | 1 + internal/testdata/test.txt.minisig | 4 + mstrusted.go | 66 ++++++++++++++ 9 files changed, 231 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/mstrusted/mstrusted.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/testdata/key.priv create mode 100644 internal/testdata/key.pub create mode 100644 internal/testdata/test.txt create mode 100644 internal/testdata/test.txt.minisig create mode 100644 mstrusted.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..95fa68d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/mstrusted diff --git a/cmd/mstrusted/mstrusted.go b/cmd/mstrusted/mstrusted.go new file mode 100644 index 0000000..c5f2562 --- /dev/null +++ b/cmd/mstrusted/mstrusted.go @@ -0,0 +1,133 @@ +package main + +import ( + "bufio" + "flag" + "fmt" + "io" + "log" + "os" + + "aead.dev/minisign" + "oskarsh.ru/mstrusted" +) + +func main() { + var ( + hashFlag bool + sigFile string + outputFlag bool + quietFlag bool + prettyQuietFlag bool + file string + ) + flag.BoolVar(&hashFlag, "H", false, "require input to be prehashed.") + flag.StringVar(&sigFile, "x", "", "signature file (default: .minisig)") + flag.BoolVar(&outputFlag, "o", false, "output the file content after verification") + flag.BoolVar(&quietFlag, "q", false, "quiet mode, suppress output") + flag.BoolVar(&prettyQuietFlag, "Q", false, "pretty quiet mode, only print the trusted comment") + flag.StringVar(&file, "m", "", "file to verify.") + flag.Parse() + + pubKey, err := mstrusted.SearchTrustedPubKey(sigFile) + if err != nil { + log.Fatalf("Error: %v", err) + } + log.Println(pubKey) + verifyFile(sigFile, "", pubKey, outputFlag, quietFlag, prettyQuietFlag, hashFlag, file) +} + +// Code below was taken from https://github.com/aead/minisign/blob/0d530c6fc203bf1fee619d51809807cc3ed68e7d/cmd/minisign/minisign.go. +// Copyright (c) 2021 Andreas Auernhammer. All rights reserved. +// Use of this source code is governed by a license that can be +// found in the https://github.com/aead/minisign/blob/0d530c6fc203bf1fee619d51809807cc3ed68e7d/LICENSE file. + +func verifyFile(sigFile, pubFile, pubKeyString string, printOutput, quiet, prettyQuiet, requireHash bool, files ...string) { + if len(files) == 0 { + log.Fatalf("Error: no files to verify. Use -m to specify a file path") + } + if len(files) > 1 { + log.Fatalf("Error: too many files to verify. Only one file can be specified") + } + if sigFile == "" { + sigFile = files[0] + ".minisig" + } + + var ( + publicKey minisign.PublicKey + err error + ) + if pubKeyString != "" { + if err = publicKey.UnmarshalText([]byte(pubKeyString)); err != nil { + log.Fatalf("Error: invalid public key: %v", err) + } + } else { + publicKey, err = minisign.PublicKeyFromFile(pubFile) + if err != nil { + log.Fatalf("Error: %v", err) + } + } + + signature, err := minisign.SignatureFromFile(sigFile) + if err != nil { + log.Fatalf("Error: %v", err) + } + if signature.KeyID != publicKey.ID() { + log.Fatalf("Error: key IDs do not match. Try a different public key.\nID (public key): %X\nID (signature) : %X", publicKey.ID(), signature.KeyID) + } + + rawSignature, _ := signature.MarshalText() + if requireHash && signature.Algorithm != minisign.HashEdDSA { + log.Fatal("Legacy (non-prehashed) signature found") + } + if signature.Algorithm == minisign.HashEdDSA || requireHash { + file, err := os.Open(files[0]) + if err != nil { + log.Fatalf("Error: %v", err) + } + reader := minisign.NewReader(file) + if _, err = io.Copy(io.Discard, reader); err != nil { + file.Close() + log.Fatalf("Error: %v", err) + } + + if !reader.Verify(publicKey, rawSignature) { + file.Close() + log.Fatal("Error: signature verification failed") + } + if !quiet { + if !prettyQuiet { + fmt.Println("Signature and comment signature verified") + } + fmt.Println("Trusted comment:", signature.TrustedComment) + } + if printOutput { + if _, err = file.Seek(0, io.SeekStart); err != nil { + file.Close() + log.Fatalf("Error: %v", err) + } + if _, err = io.Copy(os.Stdout, bufio.NewReader(file)); err != nil { + file.Close() + log.Fatalf("Error: %v", err) + } + } + file.Close() + } else { + message, err := os.ReadFile(files[0]) + if err != nil { + log.Fatalf("Error: %v", err) + } + if !minisign.Verify(publicKey, message, rawSignature) { + log.Fatal("Error: signature verification failed") + } + if !quiet { + if !prettyQuiet { + fmt.Println("Signature and comment signature verified") + } + fmt.Println("Trusted comment:", signature.TrustedComment) + } + if printOutput { + os.Stdout.Write(message) + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4b06a98 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module oskarsh.ru/mstrusted + +go 1.18 + +require aead.dev/minisign v0.2.0 + +require ( + golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 // indirect + golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..33a0d9b --- /dev/null +++ b/go.sum @@ -0,0 +1,12 @@ +aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= +aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46 h1:V066+OYJ66oTjnhm4Yrn7SXIwSCiDQJxpBxmvqb1N1c= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/testdata/key.priv b/internal/testdata/key.priv new file mode 100644 index 0000000..87975d2 --- /dev/null +++ b/internal/testdata/key.priv @@ -0,0 +1,2 @@ +untrusted comment: minisign encrypted secret key +RWRTY0IycmRrG6A41efui3sFVTW/Ft4mlFQfRi2HmK29IWCDllcAAAACAAAAAAAAAEAAAAAAVjdsSvEDVXb/RHvRtitxpZ6FQpJzUJTlZp0y32zwwaEbaUYIBrwJ4Y70XXvqq/If3R69RMlTGVe/umar/kD7ijVmHrapkwqkKU9jJ0vBoravN/weFEJLpPh4R8mKzT+ssPMZLPN7JR4= diff --git a/internal/testdata/key.pub b/internal/testdata/key.pub new file mode 100644 index 0000000..9c868f5 --- /dev/null +++ b/internal/testdata/key.pub @@ -0,0 +1,2 @@ +untrusted comment: minisign public key 27E633F4A0EA7E56 +RWRWfuqg9DPmJzteqVmj5xSm7z1V0ZTNA66UGpF+5vdkUe8llEMWkC6n diff --git a/internal/testdata/test.txt b/internal/testdata/test.txt new file mode 100644 index 0000000..980a0d5 --- /dev/null +++ b/internal/testdata/test.txt @@ -0,0 +1 @@ +Hello World! diff --git a/internal/testdata/test.txt.minisig b/internal/testdata/test.txt.minisig new file mode 100644 index 0000000..ffb5f88 --- /dev/null +++ b/internal/testdata/test.txt.minisig @@ -0,0 +1,4 @@ +untrusted comment: signature from minisign secret key +RWRWfuqg9DPmJ53RyYcQ5hxFSl2VYIcGLj51J2+49WJ9aro2ONvCVs0ruJmpFtQrLWGNYvMXnyLfi4/fqzqI4/EUmyvh0Xucxg4= +trusted comment: timestamp:1658442208 file:test.txt +Q+xNdjGSPRCHer/1q0jZv2I0JvUypYA2plaervO/D4S9RKNB2WcCSocZgxVc/Pav61b0brRMvfOuEZidp/g1AQ== diff --git a/mstrusted.go b/mstrusted.go new file mode 100644 index 0000000..f267ac9 --- /dev/null +++ b/mstrusted.go @@ -0,0 +1,66 @@ +package mstrusted + +import ( + "errors" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" + "strings" + + "aead.dev/minisign" +) + +func getTrustedPath() string { + dirname, err := os.UserHomeDir() + if err != nil { + log.Fatal(err) + } + return filepath.Join(dirname, ".minisign/trusted") +} + +func ensureTrustedDir() error { + err := os.MkdirAll(getTrustedPath(), 0700) + if !os.IsExist(err) { + return err + } + return nil +} + +func extractID(s minisign.Signature) string { + return strings.ToUpper(strconv.FormatUint(s.KeyID, 16)) +} + +func readPubKey(keyPath string) (minisign.PublicKey, error) { + bytes, err := ioutil.ReadFile(keyPath) + if os.IsNotExist(err) { + return minisign.PublicKey{}, errors.New("mstrusted: public key doesn't exist.") + } else if err != nil { + return minisign.PublicKey{}, errors.New("mstrusted: public key is unreadable.") + } + var key minisign.PublicKey + if err = key.UnmarshalText(bytes); err != nil { + return minisign.PublicKey{}, err + } + return key, nil +} + +func SearchTrustedPubKey(sigFile string) (string, error) { + if err := ensureTrustedDir(); err != nil { + return "", errors.New("mstrusted: can't create trusted directory.") + } + + signature, err := minisign.SignatureFromFile(sigFile) + if err != nil { + return "", err + } + + keyPath := filepath.Join(getTrustedPath(), extractID(signature)+".pub") + key, err := readPubKey(keyPath) + if err != nil { + return "", err + } + + return key.String(), nil +}