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  }