mirror of
https://github.com/octoleo/syncthing.git
synced 2024-11-08 22:31:04 +00:00
Add a signature package and stsigtool CLI utility
This commit is contained in:
parent
7d444021bb
commit
d3eb674b30
102
cmd/stsigtool/main.go
Normal file
102
cmd/stsigtool/main.go
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/signature"
|
||||
)
|
||||
|
||||
func main() {
|
||||
log.SetFlags(0)
|
||||
log.SetOutput(os.Stdout)
|
||||
|
||||
flag.Parse()
|
||||
|
||||
if flag.NArg() < 1 {
|
||||
log.Println(`Usage:
|
||||
stsigtool <command>
|
||||
|
||||
Where command is one of:
|
||||
|
||||
gen
|
||||
- generate a new key pair
|
||||
|
||||
sign <privkeyfile> <datafile>
|
||||
- sign a file
|
||||
|
||||
verify <pubkeyfile> <signaturefile> <datafile>
|
||||
- verify a signature
|
||||
`)
|
||||
}
|
||||
|
||||
switch flag.Arg(0) {
|
||||
case "gen":
|
||||
gen()
|
||||
case "sign":
|
||||
sign(flag.Arg(1), flag.Arg(2))
|
||||
case "verify":
|
||||
verify(flag.Arg(1), flag.Arg(2), flag.Arg(3))
|
||||
}
|
||||
}
|
||||
|
||||
func gen() {
|
||||
priv, pub, err := signature.GenerateKeys()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
os.Stdout.Write(priv)
|
||||
os.Stdout.Write(pub)
|
||||
}
|
||||
|
||||
func sign(keyname, dataname string) {
|
||||
privkey, err := ioutil.ReadFile(keyname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fd, err := os.Open(dataname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
sig, err := signature.Sign(privkey, fd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
os.Stdout.Write(sig)
|
||||
}
|
||||
|
||||
func verify(keyname, signame, dataname string) {
|
||||
pubkey, err := ioutil.ReadFile(keyname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
sig, err := ioutil.ReadFile(signame)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
fd, err := os.Open(dataname)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
|
||||
err = signature.Verify(pubkey, sig, fd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
195
lib/signature/signature.go
Normal file
195
lib/signature/signature.go
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// Package signature provides simple methods to create and verify signatures
|
||||
// in PEM format.
|
||||
package signature
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// GenerateKeys returns a new key pair, with the private and public key
|
||||
// encoded in PEM format.
|
||||
func GenerateKeys() (privKey []byte, pubKey []byte, err error) {
|
||||
// Generate a new key pair
|
||||
key, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Marshal the private key
|
||||
bs, err := x509.MarshalECPrivateKey(key)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Encode it in PEM format
|
||||
privKey = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: bs,
|
||||
})
|
||||
|
||||
// Marshal the public key
|
||||
bs, err = x509.MarshalPKIXPublicKey(key.Public())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Encode it in PEM format
|
||||
pubKey = pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PUBLIC KEY",
|
||||
Bytes: bs,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sign computes the hash of data and signs it with the private key, returning
|
||||
// a signature in PEM format.
|
||||
func Sign(privKeyPEM []byte, data io.Reader) ([]byte, error) {
|
||||
// Parse the private key
|
||||
key, err := loadPrivateKey(privKeyPEM)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Hash the reader data
|
||||
hash, err := hashReader(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sign the hash
|
||||
r, s, err := ecdsa.Sign(rand.Reader, key, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Marshal the signature using ASN.1
|
||||
sig, err := marshalSignature(r, s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode it in a PEM block
|
||||
bs := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "SIGNATURE",
|
||||
Bytes: sig,
|
||||
})
|
||||
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
// Verify computes the hash of data and compares it to the signature using the
|
||||
// given public key. Returns nil if the signature is correct.
|
||||
func Verify(pubKeyPEM []byte, signature []byte, data io.Reader) error {
|
||||
// Parse the public key
|
||||
key, err := loadPublicKey(pubKeyPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the signature
|
||||
block, _ := pem.Decode(signature)
|
||||
r, s, err := unmarshalSignature(block.Bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compute the hash of the data
|
||||
hash, err := hashReader(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify the signature
|
||||
if !ecdsa.Verify(key, hash, r, s) {
|
||||
return errors.New("incorrect signature")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// hashReader returns the SHA256 hash of the reader
|
||||
func hashReader(r io.Reader) ([]byte, error) {
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash := []byte(fmt.Sprintf("%x", h.Sum(nil)))
|
||||
return hash, nil
|
||||
}
|
||||
|
||||
// loadPrivateKey returns the ECDSA private key structure for the given PEM
|
||||
// data.
|
||||
func loadPrivateKey(bs []byte) (*ecdsa.PrivateKey, error) {
|
||||
block, _ := pem.Decode(bs)
|
||||
return x509.ParseECPrivateKey(block.Bytes)
|
||||
}
|
||||
|
||||
// loadPublicKey returns the ECDSA public key structure for the given PEM
|
||||
// data.
|
||||
func loadPublicKey(bs []byte) (*ecdsa.PublicKey, error) {
|
||||
// Decode and parse the public key PEM block
|
||||
block, _ := pem.Decode(bs)
|
||||
intf, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// It should be an ECDSA public key
|
||||
pk, ok := intf.(*ecdsa.PublicKey)
|
||||
if !ok {
|
||||
return nil, errors.New("unsupported public key format")
|
||||
}
|
||||
|
||||
return pk, nil
|
||||
}
|
||||
|
||||
// A wrapper around the signature integers so that we can marshal and
|
||||
// unmarshal them.
|
||||
type signature struct {
|
||||
R, S *big.Int
|
||||
}
|
||||
|
||||
// marhalSignature returns ASN.1 encoded bytes for the given integers,
|
||||
// suitable for PEM encoding.
|
||||
func marshalSignature(r, s *big.Int) ([]byte, error) {
|
||||
sig := signature{
|
||||
R: r,
|
||||
S: s,
|
||||
}
|
||||
|
||||
bs, err := asn1.Marshal(sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return bs, nil
|
||||
}
|
||||
|
||||
// unmarshalSignature returns the R and S integers from the given ASN.1
|
||||
// encoded signature.
|
||||
func unmarshalSignature(sig []byte) (r *big.Int, s *big.Int, err error) {
|
||||
var ts signature
|
||||
_, err = asn1.Unmarshal(sig, &ts)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ts.R, ts.S, nil
|
||||
}
|
81
lib/signature/signature_test.go
Normal file
81
lib/signature/signature_test.go
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (C) 2015 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
package signature_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/signature"
|
||||
)
|
||||
|
||||
var (
|
||||
// A private key for signing
|
||||
privKey = []byte(`-----BEGIN EC PRIVATE KEY-----
|
||||
MIHbAgEBBEFGXB1IgefFF6kSyE17xIAU7fDIn07sPnGf1kLOCVrEZyUbnAmNFk8u
|
||||
lUt/knnvo+Gw1i9ucFjmtYtzDevrhSlG5aAHBgUrgQQAI6GBiQOBhgAEASlcbcgJ
|
||||
4PN+TSnAYiMlA0I/PRtFrDCgrt27K7hR+U7Afjc4KqW+QYwoRLvxueNh7gUK+zc0
|
||||
Aqrk3z+O1epiQTq8ACikHUXsx/bSzEFlPdMygUAAj3hChlgCL6/vOocuRUbtAqc6
|
||||
Zr0L9px+J4L0K+uqhyhKya7y6QLJrYPovFq3A7AK
|
||||
-----END EC PRIVATE KEY-----`)
|
||||
|
||||
// The matching public key
|
||||
pubKey = []byte(`-----BEGIN EC PUBLIC KEY-----
|
||||
MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBKVxtyAng835NKcBiIyUDQj89G0Ws
|
||||
MKCu3bsruFH5TsB+Nzgqpb5BjChEu/G542HuBQr7NzQCquTfP47V6mJBOrwAKKQd
|
||||
RezH9tLMQWU90zKBQACPeEKGWAIvr+86hy5FRu0CpzpmvQv2nH4ngvQr66qHKErJ
|
||||
rvLpAsmtg+i8WrcDsAo=
|
||||
-----END EC PUBLIC KEY-----`)
|
||||
|
||||
// A signature of "this is a string to sign" created with the private key
|
||||
// above
|
||||
exampleSig = []byte(`-----BEGIN SIGNATURE-----
|
||||
MIGGAkFdHjdarlFOrtcnCqcb0BX7Mjjq/Sbgp4mopCxBwXmfamtCeRGhZJ5MikyD
|
||||
VXScaJ2Dq2Ov7L4/gTcYj9fZwcrWgQJBc7+tcw5fpO0/y8DNq0t3g9bqt2MkmoNm
|
||||
eSAM8Fze4usVXHEi+QeMuYM2IKeVPyAR3iyl5gflVul9NRXS3OPAH3A=
|
||||
-----END SIGNATURE-----`)
|
||||
)
|
||||
|
||||
func TestGenerateKeys(t *testing.T) {
|
||||
priv, pub, err := signature.GenerateKeys()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Contains(priv, []byte("PRIVATE KEY")) {
|
||||
t.Fatal("should be a private key")
|
||||
}
|
||||
if !bytes.Contains(pub, []byte("PUBLIC KEY")) {
|
||||
t.Fatal("should be a private key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSign(t *testing.T) {
|
||||
data := bytes.NewReader([]byte("this is a string to sign"))
|
||||
|
||||
s, err := signature.Sign(privKey, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !bytes.Contains(s, []byte("SIGNATURE")) {
|
||||
t.Error("should be a signature")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerify(t *testing.T) {
|
||||
data := bytes.NewReader([]byte("this is a string to sign"))
|
||||
err := signature.Verify(pubKey, exampleSig, data)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
data = bytes.NewReader([]byte("thus is a string to sign"))
|
||||
err = signature.Verify(pubKey, exampleSig, data)
|
||||
if err == nil {
|
||||
t.Fatal("signature should not match")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user