implement mcrypto.Public/PrivateKey types and have them implement Signer/Verifier

This commit is contained in:
Brian Picciano 2018-03-23 15:46:14 +00:00
parent 9b6c42572c
commit 02a4cf7c30
3 changed files with 266 additions and 0 deletions

View File

@ -7,6 +7,12 @@ import (
"strings" "strings"
) )
// TODO rather than have the NewSignerVerifier methods, it might be better to
// have a Secret type, which implements Signer/Verifier. That way when there's
// Encrypter/Decrypter interfaces then Secret can implement those too, and
// PublicKey/PrivateKey can implement their respective ones. There'll be a nice
// symmetry there, rather than having NewEncrypterDecrypter functions.
// Instead of outputing opaque hex garbage, this package opts to add a prefix to // Instead of outputing opaque hex garbage, this package opts to add a prefix to
// the garbage. Each "type" of string returned has its own character which is // the garbage. Each "type" of string returned has its own character which is
// not found in the hex range (0-9, a-f), and in addition each also has a // not found in the hex range (0-9, a-f), and in addition each also has a
@ -19,6 +25,8 @@ const (
uuidV0 = "0u" // u for uuid uuidV0 = "0u" // u for uuid
sigV0 = "0s" // s for signature sigV0 = "0s" // s for signature
encryptedV0 = "0n" // n for "n"-crypted, harharhar encryptedV0 = "0n" // n for "n"-crypted, harharhar
pubKeyV0 = "0l" // b for pub"l"ic key
privKeyV0 = "0v" // v for pri"v"ate key
) )
func stripPrefix(s, prefix string) (string, bool) { func stripPrefix(s, prefix string) (string, bool) {

241
mcrypto/pair.go Normal file
View File

@ -0,0 +1,241 @@
package mcrypto
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"io"
"math/big"
"time"
"github.com/mediocregopher/mediocre-go-lib/mlog"
)
var (
errMalformedPublicKey = errors.New("malformed public key")
errMalformedPrivateKey = errors.New("malformed private key")
)
// NewKeyPair generates and returns a complementary public/private key pair
func NewKeyPair() (PublicKey, PrivateKey) {
return newKeyPair(2048)
}
// NewWeakKeyPair is like NewKeyPair but the returned pair uses fewer bits
// (though still a reasonably secure amount for data that doesn't need security
// guarantees into the year 3000 whatever).
func NewWeakKeyPair() (PublicKey, PrivateKey) {
return newKeyPair(1024)
}
func newKeyPair(bits int) (PublicKey, PrivateKey) {
priv, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
panic(err)
}
return PublicKey{priv.PublicKey}, PrivateKey{priv}
}
////////////////////////////////////////////////////////////////////////////////
// PublicKey is a wrapper around an rsa.PublicKey which simplifies using it and
// adds marshaling/unmarshaling methods.
//
// A PublicKey automatically implements the Verifier interface.
type PublicKey struct {
rsa.PublicKey
}
func (pk PublicKey) verify(s Signature, r io.Reader) error {
h := sha256.New()
r = sigPrefixReader(r, 32, s.salt, s.t)
if _, err := io.Copy(h, r); err != nil {
return err
}
if err := rsa.VerifyPSS(&pk.PublicKey, crypto.SHA256, h.Sum(nil), s.sig, nil); err != nil {
return mlog.ErrWithKV(ErrInvalidSig, s)
}
return nil
}
func (pk PublicKey) String() string {
nB := pk.N.Bytes()
b := make([]byte, 8+len(nB))
// the exponent is never negative so this is fine
binary.BigEndian.PutUint64(b, uint64(pk.E))
copy(b[8:], nB)
return pubKeyV0 + hex.EncodeToString(b)
}
// KV implements the method for the mlog.KVer interface
func (pk PublicKey) KV() mlog.KV {
return mlog.KV{"publicKey": pk.String()}
}
// MarshalText implements the method for the encoding.TextMarshaler interface
func (pk PublicKey) MarshalText() ([]byte, error) {
return []byte(pk.String()), nil
}
// UnmarshalText implements the method for the encoding.TextUnmarshaler
// interface
func (pk *PublicKey) UnmarshalText(b []byte) error {
str := string(b)
strEnc, ok := stripPrefix(str, pubKeyV0)
if !ok || len(strEnc) <= hex.EncodedLen(8) {
return mlog.ErrWithKV(errMalformedPublicKey, mlog.KV{"pubKeyStr": str})
}
b, err := hex.DecodeString(strEnc)
if err != nil {
return mlog.ErrWithKV(err, mlog.KV{"pubKeyStr": str})
}
pk.E = int(binary.BigEndian.Uint64(b))
pk.N = new(big.Int)
pk.N.SetBytes(b[8:])
return nil
}
// MarshalJSON implements the method for the json.Marshaler interface
func (pk PublicKey) MarshalJSON() ([]byte, error) {
return json.Marshal(pk.String())
}
// UnmarshalJSON implements the method for the json.Unmarshaler interface
func (pk *PublicKey) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
return pk.UnmarshalText([]byte(s))
}
////////////////////////////////////////////////////////////////////////////////
// PrivateKey is a wrapper around an rsa.PrivateKey which simplifies using it
// and adds marshaling/unmarshaling methods.
//
// A PrivateKey automatically implements the Signer interface.
type PrivateKey struct {
*rsa.PrivateKey
}
func (pk PrivateKey) sign(r io.Reader) (Signature, error) {
salt := make([]byte, 8)
if _, err := rand.Read(salt); err != nil {
panic(err)
}
t := time.Now()
h := sha256.New()
// sigLen has to be 32 here (bytes returned by sha256) cause of the way the
// VerifyPSS function is
if _, err := io.Copy(h, sigPrefixReader(r, 32, salt, t)); err != nil {
return Signature{}, err
}
sig, err := rsa.SignPSS(rand.Reader, pk.PrivateKey, crypto.SHA256, h.Sum(nil), nil)
return Signature{sig: sig, salt: salt, t: t}, err
}
func (pk PrivateKey) String() string {
numBytes := binary.MaxVarintLen64 * 3 // public exponent, N, and D
nB, dB := pk.PublicKey.N.Bytes(), pk.D.Bytes()
numBytes += len(nB) + len(dB)
primes := make([][]byte, len(pk.Primes))
for i, prime := range pk.Primes {
primes[i] = prime.Bytes()
numBytes += binary.MaxVarintLen64 + len(primes[i])
}
b, ptr := make([]byte, numBytes), 0
ptr += binary.PutUvarint(b[ptr:], uint64(pk.E))
ptr += binary.PutUvarint(b[ptr:], uint64(len(nB)))
ptr += copy(b[ptr:], nB)
ptr += binary.PutUvarint(b[ptr:], uint64(len(dB)))
ptr += copy(b[ptr:], dB)
for _, prime := range primes {
ptr += binary.PutUvarint(b[ptr:], uint64(len(prime)))
ptr += copy(b[ptr:], prime)
}
return privKeyV0 + hex.EncodeToString(b[:ptr])
}
// KV implements the method for the mlog.KVer interface
func (pk PrivateKey) KV() mlog.KV {
return mlog.KV{"privateKey": pk.String()}
}
// MarshalText implements the method for the encoding.TextMarshaler interface
func (pk PrivateKey) MarshalText() ([]byte, error) {
return []byte(pk.String()), nil
}
// UnmarshalText implements the method for the encoding.TextUnmarshaler
// interface
func (pk *PrivateKey) UnmarshalText(b []byte) error {
str := string(b)
strEnc, ok := stripPrefix(str, privKeyV0)
if !ok {
return mlog.ErrWithKV(errMalformedPrivateKey, mlog.KV{"privKeyStr": str})
}
b, err := hex.DecodeString(strEnc)
if err != nil {
return mlog.ErrWithKV(err, mlog.KV{"privKeyStr": str})
}
e, n := binary.Uvarint(b)
if n <= 0 {
return mlog.ErrWithKV(errMalformedPrivateKey, mlog.KV{"privKeyStr": str})
}
pk.PublicKey.E = int(e)
b = b[n:]
bigInt := func() *big.Int {
if err != nil {
return nil
}
l, n := binary.Uvarint(b)
if n <= 0 {
err = errMalformedPrivateKey
}
b = b[n:]
i := new(big.Int)
i.SetBytes(b[:l])
b = b[l:]
return i
}
pk.PublicKey.N = bigInt()
pk.D = bigInt()
for len(b) > 0 && err == nil {
pk.Primes = append(pk.Primes, bigInt())
}
if err != nil {
return mlog.ErrWithKV(err, mlog.KV{"privKeyStr": str})
}
return nil
}
// MarshalJSON implements the method for the json.Marshaler interface
func (pk PrivateKey) MarshalJSON() ([]byte, error) {
return json.Marshal(pk.String())
}
// UnmarshalJSON implements the method for the json.Unmarshaler interface
func (pk *PrivateKey) UnmarshalJSON(b []byte) error {
var s string
if err := json.Unmarshal(b, &s); err != nil {
return err
}
return pk.UnmarshalText([]byte(s))
}

17
mcrypto/pair_test.go Normal file
View File

@ -0,0 +1,17 @@
package mcrypto
import (
. "testing"
"github.com/mediocregopher/mediocre-go-lib/mtest"
"github.com/stretchr/testify/assert"
)
func TestKeyPair(t *T) {
pub, priv := NewWeakKeyPair()
// test signing/verifying
str := mtest.RandHex(512)
sig := SignString(priv, str)
assert.NoError(t, VerifyString(pub, sig, str))
}