implement mcrypto.Public/PrivateKey types and have them implement Signer/Verifier
This commit is contained in:
parent
9b6c42572c
commit
02a4cf7c30
@ -7,6 +7,12 @@ import (
|
||||
"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
|
||||
// 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
|
||||
@ -19,6 +25,8 @@ const (
|
||||
uuidV0 = "0u" // u for uuid
|
||||
sigV0 = "0s" // s for signature
|
||||
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) {
|
||||
|
241
mcrypto/pair.go
Normal file
241
mcrypto/pair.go
Normal 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
17
mcrypto/pair_test.go
Normal 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))
|
||||
}
|
Loading…
Reference in New Issue
Block a user