Просмотр исходного кода

release/dist/cli: add sign-key and verify-key-signature commands (#9041)

Now we have all the commands to generate the key hierarchy and verify
that signing keys were signed correctly:
```
$ ./tool/go run ./cmd/dist gen-key --priv-path root-priv.pem --pub-path root-pub.pem --root
wrote private key to root-priv.pem
wrote public key to root-pub.pem

$ ./tool/go run ./cmd/dist gen-key --priv-path signing-priv.pem --pub-path signing-pub.pem --signing
wrote private key to signing-priv.pem
wrote public key to signing-pub.pem

$ ./tool/go run ./cmd/dist sign-key --root-priv-path root-priv.pem --sign-pub-path signing-pub.pem
wrote signature to signature.bin

$ ./tool/go run ./cmd/dist verify-key-signature --root-pub-path root-pub.pem --sign-pub-path signing-pub.pem --sig-path signature.bin
signature ok
```

Updates #8760

Signed-off-by: Andrew Lytvynov <[email protected]>
Andrew Lytvynov 2 лет назад
Родитель
Сommit
d45af7c66f
2 измененных файлов с 111 добавлено и 9 удалено
  1. 19 9
      clientupdate/distsign/distsign.go
  2. 92 0
      release/dist/cli/cli.go

+ 19 - 9
clientupdate/distsign/distsign.go

@@ -98,7 +98,7 @@ func ParseRootKey(privKey []byte) (*RootKey, error) {
 // SignSigningKeys signs the bundle of public signing keys. The bundle must be
 // a sequence of PEM blocks joined with newlines.
 func (r *RootKey) SignSigningKeys(pubBundle []byte) ([]byte, error) {
-	if _, err := parseSigningKeyBundle(pubBundle); err != nil {
+	if _, err := ParseSigningKeyBundle(pubBundle); err != nil {
 		return nil, err
 	}
 	return ed25519.Sign(r.k, pubBundle), nil
@@ -221,7 +221,7 @@ func (c *Client) Download(srcPath, dstPath string) error {
 		return err
 	}
 	msg := binary.LittleEndian.AppendUint64(hash, uint64(len))
-	if !verifyAny(sigPub, msg, sig) {
+	if !VerifyAny(sigPub, msg, sig) {
 		// Best-effort clean up of downloaded package.
 		os.Remove(dstPathUnverified)
 		return fmt.Errorf("signature %q for key %q does not validate with the current release signing key; either you are under attack, or attempting to download an old version of Tailscale which was signed with an older signing key", sigURL, srcURL)
@@ -248,11 +248,11 @@ func (c *Client) signingKeys() ([]ed25519.PublicKey, error) {
 	if err != nil {
 		return nil, err
 	}
-	if !verifyAny(c.roots, raw, sig) {
+	if !VerifyAny(c.roots, raw, sig) {
 		return nil, fmt.Errorf("signature %q for key %q does not validate with any known root key; either you are under attack, or running a very old version of Tailscale with outdated root keys", sigURL, keyURL)
 	}
 
-	keys, err := parseSigningKeyBundle(raw)
+	keys, err := ParseSigningKeyBundle(raw)
 	if err != nil {
 		return nil, fmt.Errorf("cannot parse signing key bundle from %q: %w", keyURL, err)
 	}
@@ -315,10 +315,20 @@ func parsePrivateKey(data []byte, typeTag string) (ed25519.PrivateKey, error) {
 	return ed25519.PrivateKey(b.Bytes), nil
 }
 
-func parseSigningKeyBundle(bundle []byte) ([]ed25519.PublicKey, error) {
+// ParseSigningKeyBundle parses the bundle of PEM-encoded public signing keys.
+func ParseSigningKeyBundle(bundle []byte) ([]ed25519.PublicKey, error) {
+	return parsePublicKeyBundle(bundle, pemTypeSigningPublic)
+}
+
+// ParseRootKeyBundle parses the bundle of PEM-encoded public root keys.
+func ParseRootKeyBundle(bundle []byte) ([]ed25519.PublicKey, error) {
+	return parsePublicKeyBundle(bundle, pemTypeRootPublic)
+}
+
+func parsePublicKeyBundle(bundle []byte, typeTag string) ([]ed25519.PublicKey, error) {
 	var keys []ed25519.PublicKey
 	for len(bundle) > 0 {
-		pub, rest, err := parsePublicKey(bundle, pemTypeSigningPublic)
+		pub, rest, err := parsePublicKey(bundle, typeTag)
 		if err != nil {
 			return nil, err
 		}
@@ -356,9 +366,9 @@ func parsePublicKey(data []byte, typeTag string) (pub ed25519.PublicKey, rest []
 	return ed25519.PublicKey(b.Bytes), rest, nil
 }
 
-// verifyAny verifies whether sig is valid for msg using any of the keys.
-// verifyAny will panic of any of the keys have the wrong size for Ed25519.
-func verifyAny(keys []ed25519.PublicKey, msg, sig []byte) bool {
+// VerifyAny verifies whether sig is valid for msg using any of the keys.
+// VerifyAny will panic if any of the keys have the wrong size for Ed25519.
+func VerifyAny(keys []ed25519.PublicKey, msg, sig []byte) bool {
 	for _, k := range keys {
 		if ed25519consensus.Verify(k, msg, sig) {
 			return true

+ 92 - 0
release/dist/cli/cli.go

@@ -95,6 +95,36 @@ func CLI(getTargets func(unixpkgs.Signers) ([]dist.Target, error)) *ffcli.Comman
 					return fs
 				})(),
 			},
+			{
+				Name: "sign-key",
+				Exec: func(ctx context.Context, args []string) error {
+					return runSignKey(ctx)
+				},
+				ShortUsage: "dist sign-key",
+				ShortHelp:  "Sign signing keys with a root key",
+				FlagSet: (func() *flag.FlagSet {
+					fs := flag.NewFlagSet("sign-key", flag.ExitOnError)
+					fs.StringVar(&signKeyArgs.rootPrivPath, "root-priv-path", "root-private-key.pem", "path to the root private key to sign with")
+					fs.StringVar(&signKeyArgs.signPubPath, "sign-pub-path", "signing-public-keys.pem", "path to the signing public key bundle to sign; the bundle should include all active signing keys")
+					fs.StringVar(&signKeyArgs.sigPath, "sig-path", "signature.bin", "oputput path for the signature")
+					return fs
+				})(),
+			},
+			{
+				Name: "verify-key-signature",
+				Exec: func(ctx context.Context, args []string) error {
+					return runVerifyKeySignature(ctx)
+				},
+				ShortUsage: "dist verify-key-signature",
+				ShortHelp:  "Verify a root signture of the signing keys' bundle",
+				FlagSet: (func() *flag.FlagSet {
+					fs := flag.NewFlagSet("verify-key-signature", flag.ExitOnError)
+					fs.StringVar(&verifyKeySignatureArgs.rootPubPath, "root-pub-path", "root-public-key.pem", "path to the root public key; this can be a bundle of multiple keys")
+					fs.StringVar(&verifyKeySignatureArgs.signPubPath, "sign-pub-path", "", "path to the signing public key bundle that was signed")
+					fs.StringVar(&verifyKeySignatureArgs.sigPath, "sig-path", "signature.bin", "path to the signature file")
+					return fs
+				})(),
+			},
 		},
 		Exec: func(context.Context, []string) error { return flag.ErrHelp },
 	}
@@ -224,3 +254,65 @@ func runGenKey(ctx context.Context) error {
 	fmt.Println("wrote public key to", genKeyArgs.pubPath)
 	return nil
 }
+
+var signKeyArgs struct {
+	rootPrivPath string
+	signPubPath  string
+	sigPath      string
+}
+
+func runSignKey(ctx context.Context) error {
+	rkRaw, err := os.ReadFile(signKeyArgs.rootPrivPath)
+	if err != nil {
+		return err
+	}
+	rk, err := distsign.ParseRootKey(rkRaw)
+	if err != nil {
+		return err
+	}
+
+	bundle, err := os.ReadFile(signKeyArgs.signPubPath)
+	if err != nil {
+		return err
+	}
+	sig, err := rk.SignSigningKeys(bundle)
+	if err != nil {
+		return err
+	}
+
+	if err := os.WriteFile(signKeyArgs.sigPath, sig, 0400); err != nil {
+		return fmt.Errorf("failed writing signature file: %w", err)
+	}
+	fmt.Println("wrote signature to", signKeyArgs.sigPath)
+	return nil
+}
+
+var verifyKeySignatureArgs struct {
+	rootPubPath string
+	signPubPath string
+	sigPath     string
+}
+
+func runVerifyKeySignature(ctx context.Context) error {
+	rootPubBundle, err := os.ReadFile(verifyKeySignatureArgs.rootPubPath)
+	if err != nil {
+		return err
+	}
+	rootPubs, err := distsign.ParseRootKeyBundle(rootPubBundle)
+	if err != nil {
+		return fmt.Errorf("parsing %q: %w", verifyKeySignatureArgs.rootPubPath, err)
+	}
+	signPubBundle, err := os.ReadFile(verifyKeySignatureArgs.signPubPath)
+	if err != nil {
+		return err
+	}
+	sig, err := os.ReadFile(verifyKeySignatureArgs.sigPath)
+	if err != nil {
+		return err
+	}
+	if !distsign.VerifyAny(rootPubs, signPubBundle, sig) {
+		return errors.New("signature not valid")
+	}
+	fmt.Println("signature ok")
+	return nil
+}