github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/tools/ssss/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/hex" 6 "fmt" 7 "io" 8 "os" 9 10 "github.com/keybase/client/go/libkb" 11 triplesec "github.com/keybase/go-triplesec" 12 saltpack "github.com/keybase/saltpack" 13 basic "github.com/keybase/saltpack/basic" 14 ) 15 16 // ssss = "simple standalone saltpack signer" 17 // 18 // Provide these environment variables: 19 // 20 // PASSPHRASE: a saltpack passphrase 21 // SECRET_KEY: a hex-encoded triplesec'ed encryption of the secret signing key, using the passphrase above 22 // PUBLIC_KEY: the corresponding EdDSA public signing key 23 // 24 // Provide a file to sign via the first argument. It will output to stdout the signature. 25 func main() { 26 err := mainInner() 27 if err != nil { 28 fmt.Fprintf(os.Stderr, "error: %s\n", err.Error()) 29 os.Exit(2) 30 } 31 } 32 33 func getEnv(k string) (val string, err error) { 34 val = os.Getenv(k) 35 if len(val) == 0 { 36 return "", fmt.Errorf("needed environment variable not set: %s", k) 37 } 38 return val, nil 39 } 40 41 func unTriplesec(key []byte, ciphertext []byte) (ret []byte, err error) { 42 43 if len(ciphertext) < 28 { 44 return nil, fmt.Errorf("encrypted data must be at least 28 bytes long") 45 } 46 header := ciphertext[0:8] 47 if !bytes.Equal(header, []byte{0x1c, 0x94, 0xd7, 0xde, 0x00, 0x00, 0x00, 0x03}) { 48 return nil, fmt.Errorf("Got back triplsec header: %x", header) 49 } 50 51 salt := ciphertext[8:24] 52 53 tsec, err := triplesec.NewCipher(key, salt, libkb.ClientTriplesecVersion) 54 if err != nil { 55 return nil, fmt.Errorf("could not make a triplesec decoder: %s", err.Error()) 56 } 57 58 ret, err = tsec.Decrypt(ciphertext) 59 if err != nil { 60 return nil, fmt.Errorf("could not decrypt secret key: %s", err.Error()) 61 } 62 return ret, nil 63 } 64 65 func loadKey() (key saltpack.SigningSecretKey, err error) { 66 pp, err := getEnv("PASSPHRASE") 67 if err != nil { 68 return nil, err 69 } 70 encryptedKeyHex, err := getEnv("SECRET_KEY") 71 if err != nil { 72 return nil, err 73 } 74 encryptedKey, err := hex.DecodeString(encryptedKeyHex) 75 if err != nil { 76 return nil, fmt.Errorf("Could not hex-decode encrypted secret key: %s", err.Error()) 77 } 78 publicKeyHex, err := getEnv("PUBLIC_KEY") 79 if err != nil { 80 return nil, err 81 } 82 publicKeyBytes, err := hex.DecodeString(publicKeyHex) 83 if err != nil { 84 return nil, fmt.Errorf("could not hex-decode public key: %s", err.Error()) 85 } 86 if len(publicKeyBytes) != 32 { 87 return nil, fmt.Errorf("wrong number of bytes for public key; wanted 32 but got %d", len(publicKeyBytes)) 88 } 89 var pub [32]byte 90 copy(pub[:], publicKeyBytes) 91 92 secretKey, err := unTriplesec([]byte(pp), encryptedKey) 93 if err != nil { 94 return nil, err 95 } 96 97 if len(secretKey) != 64 { 98 return nil, fmt.Errorf("expected a secret key of 64 bytes; got one %d long", len(secretKey)) 99 } 100 var sec [64]byte 101 copy(sec[:], secretKey) 102 103 key = basic.NewSigningSecretKey(&pub, &sec) 104 105 return key, nil 106 } 107 108 func openFile() (file io.ReadCloser, err error) { 109 if len(os.Args) != 2 { 110 return nil, fmt.Errorf("Usage: %s <file-to-sign>", os.Args[0]) 111 } 112 return os.Open(os.Args[1]) 113 } 114 115 func sign(key saltpack.SigningSecretKey, file io.ReadCloser) error { 116 iow, err := saltpack.NewSignDetachedArmor62Stream(saltpack.Version1(), os.Stdout, key, "KEYBASE") 117 if err != nil { 118 return err 119 } 120 _, err = io.Copy(iow, file) 121 if err != nil { 122 return err 123 } 124 iow.Close() 125 file.Close() 126 return nil 127 } 128 129 func mainInner() error { 130 key, err := loadKey() 131 if err != nil { 132 return err 133 } 134 file, err := openFile() 135 if err != nil { 136 return err 137 } 138 return sign(key, file) 139 }