|
@@ -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
|
|
|
|
+}
|