aboutsummaryrefslogtreecommitdiff
path: root/pkgs/yokey/crypto.go
diff options
context:
space:
mode:
authorCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-03-31 15:46:54 -0500
committerCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-03-31 15:46:54 -0500
commitc759c4772f0ca40a379cd19e63d3343b1e23714a (patch)
tree53cb7ba9ac3452d32909a64f1795ebdd6a840c20 /pkgs/yokey/crypto.go
parent6154fca38879afcb57f16adad002df083876116d (diff)
yokey: derived cryptography keys tool
Diffstat (limited to 'pkgs/yokey/crypto.go')
-rw-r--r--pkgs/yokey/crypto.go150
1 files changed, 150 insertions, 0 deletions
diff --git a/pkgs/yokey/crypto.go b/pkgs/yokey/crypto.go
new file mode 100644
index 0000000..df6e4ec
--- /dev/null
+++ b/pkgs/yokey/crypto.go
@@ -0,0 +1,150 @@
+package main
+
+import (
+ "crypto"
+ "crypto/ed25519"
+ "crypto/rand"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/pem"
+ "io"
+ "strings"
+
+ "golang.org/x/crypto/curve25519"
+ "golang.org/x/crypto/hkdf"
+ "golang.org/x/crypto/ssh"
+ "pault.ag/go/sshsig"
+)
+
+const sshsigContext = "sshsig-key-v1"
+
+func deriveKey(master []byte, info string, length int) []byte {
+ h := hkdf.New(sha256.New, master, []byte("fixed-salt"), []byte(info))
+ out := make([]byte, length)
+ if _, err := io.ReadFull(h, out); err != nil {
+ panic(err)
+ }
+ return out
+}
+
+func buildInfo(baseInfo, context string) string {
+ trimmed := strings.TrimSpace(context)
+ if trimmed == "" {
+ return baseInfo
+ }
+
+ return baseInfo + ":" + trimmed
+}
+
+func deriveEd25519(master []byte, context string) (string, []byte) {
+ seed := deriveKey(master, buildInfo("ed25519-key-v1", context), 32)
+ priv := ed25519.NewKeyFromSeed(seed)
+ pub := priv.Public().(ed25519.PublicKey)
+
+ sshPub, err := ssh.NewPublicKey(pub)
+ if err != nil {
+ panic(err)
+ }
+
+ privBlock, err := ssh.MarshalPrivateKey(priv, "")
+ if err != nil {
+ panic(err)
+ }
+
+ return string(ssh.MarshalAuthorizedKey(sshPub)), pem.EncodeToMemory(privBlock)
+}
+
+func deriveWireGuard(master []byte, context string) (string, string) {
+ priv := deriveKey(master, buildInfo("wireguard-key-v1", context), 32)
+ pub, _ := curve25519.X25519(priv, curve25519.Basepoint)
+
+ return base64.StdEncoding.EncodeToString(pub), base64.StdEncoding.EncodeToString(priv)
+}
+
+const passwordCharset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@$%^&*"
+const passwordLength = 14
+
+func derivePassword(master []byte, seed string) string {
+ return derivePasswordWithContext(master, seed, "")
+}
+
+func derivePasswordWithContext(master []byte, seed, context string) string {
+ info := buildInfo("password-v1:"+seed, context)
+ stream := hkdf.New(sha256.New, master, []byte("my-fixed-salt"), []byte(info))
+ out := make([]byte, 0, passwordLength)
+ buf := make([]byte, 64)
+ maxMultiple := byte(256 / len(passwordCharset) * len(passwordCharset))
+
+ for len(out) < passwordLength {
+ if _, err := io.ReadFull(stream, buf); err != nil {
+ panic(err)
+ }
+
+ for _, b := range buf {
+ if b >= maxMultiple {
+ continue
+ }
+
+ out = append(out, passwordCharset[int(b)%len(passwordCharset)])
+ if len(out) == passwordLength {
+ break
+ }
+ }
+ }
+
+ return string(out)
+}
+
+func deriveSSHSigSigner(master []byte) ssh.Signer {
+ _, privPem := deriveEd25519(master, sshsigContext)
+ rawPriv, err := ssh.ParseRawPrivateKey(privPem)
+ if err != nil {
+ panic(err)
+ }
+
+ signer, err := ssh.NewSignerFromKey(rawPriv)
+ if err != nil {
+ panic(err)
+ }
+
+ return signer
+}
+
+func signSSHSig(master []byte, namespace string, message []byte) []byte {
+ signer := deriveSSHSigSigner(master)
+ h := crypto.SHA512.New()
+ if _, err := h.Write(message); err != nil {
+ panic(err)
+ }
+
+ sig, err := sshsig.Sign(rand.Reader, signer, []byte(namespace), sshsig.HashAlgoSHA512, h.Sum(nil))
+ if err != nil {
+ panic(err)
+ }
+
+ return sig
+}
+
+func verifySSHSig(master []byte, namespace string, message, signature []byte) error {
+ signer := deriveSSHSigSigner(master)
+ pub := signer.PublicKey()
+
+ parsedSig, err := sshsig.ParseSignature(signature)
+ if err != nil {
+ return err
+ }
+
+ h := crypto.SHA512.New()
+ if _, err := h.Write(message); err != nil {
+ return err
+ }
+
+ return sshsig.Verify(pub, []byte(namespace), sshsig.HashAlgoSHA512, h.Sum(nil), parsedSig)
+}
+
+func generateMasterKeyBytes() []byte {
+ key := make([]byte, 32)
+ rand.Read(key)
+
+ return key
+}