mcrypto: move SignVerifier code into Secret, to account for encrypting later on

This commit is contained in:
Brian Picciano 2018-03-26 10:53:49 +00:00
parent d4c665c4d5
commit b30ccf4db8
5 changed files with 148 additions and 112 deletions

View File

@ -7,12 +7,6 @@ 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

84
mcrypto/secret.go Normal file
View File

@ -0,0 +1,84 @@
package mcrypto
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"io"
"time"
"github.com/mediocregopher/mediocre-go-lib/mlog"
)
// Secret contains a set of bytes which are inteded to remain secret within some
// context (e.g. a backend application keeping a secret from the frontend).
//
// Secret inherently implements the Signer and Verifier interfaces.
//
// Secret can be initialized with NewSecret or NewWeakSecret. The Signatures
// produced by these will be of differing lengths, but either can Verify a
// Signature made by the other as long as the secret bytes they are initialized
// with are the same.
type Secret struct {
sigSize uint8 // in bytes, shouldn't be more than 32, cause sha256
secret []byte
// only used during tests
testNow time.Time
}
// NewSecret initializes and returns an instance of Secret which uses the given
// bytes as the underlying secret.
func NewSecret(secret []byte) Secret {
return Secret{sigSize: 20, secret: secret}
}
// NewWeakSecret is like NewSecret but the Signatures it produces will be
// shorter and weaker (though still secure enough for most applications).
// Signatures produced by either normal or weak Secrets can be Verified by the
// other.
func NewWeakSecret(secret []byte) Secret {
return Secret{sigSize: 8, secret: secret}
}
func (s Secret) now() time.Time {
if !s.testNow.IsZero() {
return s.testNow
}
return time.Now()
}
func (s Secret) signRaw(
r io.Reader,
sigLen uint8, salt []byte, t time.Time,
) (
[]byte, error,
) {
h := hmac.New(sha256.New, s.secret)
r = sigPrefixReader(r, sigLen, salt, t)
if _, err := io.Copy(h, r); err != nil {
return nil, err
}
return h.Sum(nil)[:sigLen], nil
}
func (s Secret) sign(r io.Reader) (Signature, error) {
salt := make([]byte, 8)
if _, err := rand.Read(salt); err != nil {
panic(err)
}
t := s.now()
sig, err := s.signRaw(r, s.sigSize, salt, t)
return Signature{sig: sig, salt: salt, t: t}, err
}
func (s Secret) verify(sig Signature, r io.Reader) error {
sigB, err := s.signRaw(r, uint8(len(sig.sig)), sig.salt, sig.t)
if err != nil {
return mlog.ErrWithKV(err, sig)
} else if !hmac.Equal(sigB, sig.sig) {
return mlog.ErrWithKV(ErrInvalidSig, sig)
}
return nil
}

54
mcrypto/secret_test.go Normal file
View File

@ -0,0 +1,54 @@
package mcrypto
import (
. "testing"
"time"
"github.com/ansel1/merry"
"github.com/mediocregopher/mediocre-go-lib/mtest"
"github.com/stretchr/testify/assert"
)
func TestSecretSignVerify(t *T) {
secretRaw := mtest.RandBytes(16)
secret := NewSecret(secretRaw)
weakSecret := NewWeakSecret(secretRaw)
var prevStr string
var prevSig, prevWeakSig Signature
for i := 0; i < 10000; i++ {
now := time.Now().Round(0)
secret.testNow = now
weakSecret.testNow = now
thisStr := mtest.RandHex(512)
thisSig := SignString(secret, thisStr)
thisWeakSig := SignString(weakSecret, thisStr)
thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String()
// sanity checks
assert.Equal(t, now, thisSig.Time())
assert.Equal(t, now, thisWeakSig.Time())
assert.NotEmpty(t, thisSigStr)
assert.NotEmpty(t, thisWeakSigStr)
assert.NotEqual(t, thisSigStr, thisWeakSigStr)
assert.True(t, len(thisSigStr) > len(thisWeakSigStr))
// Either secret should be able to verify either signature
assert.NoError(t, VerifyString(secret, thisSig, thisStr))
assert.NoError(t, VerifyString(weakSecret, thisWeakSig, thisStr))
assert.NoError(t, VerifyString(secret, thisWeakSig, thisStr))
assert.NoError(t, VerifyString(weakSecret, thisSig, thisStr))
if prevStr != "" {
assert.NotEqual(t, prevSig.String(), thisSigStr)
assert.NotEqual(t, prevWeakSig.String(), thisWeakSigStr)
err := VerifyString(secret, prevSig, thisStr)
assert.True(t, merry.Is(err, ErrInvalidSig))
err = VerifyString(secret, prevWeakSig, thisStr)
assert.True(t, merry.Is(err, ErrInvalidSig))
}
prevStr = thisStr
prevSig = thisSig
prevWeakSig = thisWeakSig
}
}

View File

@ -2,9 +2,6 @@ package mcrypto
import ( import (
"bytes" "bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@ -187,73 +184,3 @@ func VerifyBytes(v Verifier, s Signature, b []byte) error {
func VerifyString(v Verifier, s Signature, in string) error { func VerifyString(v Verifier, s Signature, in string) error {
return VerifyBytes(v, s, []byte(in)) return VerifyBytes(v, s, []byte(in))
} }
////////////////////////////////////////////////////////////////////////////////
type signVerifier struct {
outSize uint8 // in bytes, shouldn't be more than 32, cause sha256
secret []byte
// only used during tests
testNow time.Time
}
// NewSignerVerifier returns Signer and Verifier instances which will use the
// given secret to sign and verify all Signatures
func NewSignerVerifier(secret []byte) (Signer, Verifier) {
sv := signVerifier{outSize: 20, secret: secret}
return sv, sv
}
// NewWeakSignerVerifier returns Signer and Verifier instances, similar to how
// NewSignVerifier does. The Signatures generated by this Signer will be smaller
// in text size, and therefore weaker, but are still fine for most applications.
//
// The Verifiers returned by both NewSignVerifier and NewWeakSignVerifier can
// verify each-other's signatures, as long as the secret is the same.
func NewWeakSignerVerifier(secret []byte) (Signer, Verifier) {
sv := signVerifier{outSize: 8, secret: secret}
return sv, sv
}
func (sv signVerifier) now() time.Time {
if !sv.testNow.IsZero() {
return sv.testNow
}
return time.Now()
}
func (sv signVerifier) signRaw(
r io.Reader,
sigLen uint8, salt []byte, t time.Time,
) (
[]byte, error,
) {
h := hmac.New(sha256.New, sv.secret)
r = sigPrefixReader(r, sigLen, salt, t)
if _, err := io.Copy(h, r); err != nil {
return nil, err
}
return h.Sum(nil)[:sigLen], nil
}
func (sv signVerifier) sign(r io.Reader) (Signature, error) {
salt := make([]byte, 8)
if _, err := rand.Read(salt); err != nil {
panic(err)
}
t := sv.now()
sig, err := sv.signRaw(r, sv.outSize, salt, t)
return Signature{sig: sig, salt: salt, t: t}, err
}
func (sv signVerifier) verify(s Signature, r io.Reader) error {
sig, err := sv.signRaw(r, uint8(len(s.sig)), s.salt, s.t)
if err != nil {
return mlog.ErrWithKV(err, s)
} else if !hmac.Equal(sig, s.sig) {
return mlog.ErrWithKV(ErrInvalidSig, s)
}
return nil
}

View File

@ -10,58 +10,35 @@ import (
) )
func TestSignerVerifier(t *T) { func TestSignerVerifier(t *T) {
secret := mtest.RandBytes(16) secret := NewSecret(mtest.RandBytes(16))
sigI, ver := NewSignerVerifier(secret)
sig := sigI.(signVerifier)
weakSigI, weakVer := NewWeakSignerVerifier(secret)
weakSig := weakSigI.(signVerifier)
var prevStr string var prevStr string
var prevSig, prevWeakSig Signature var prevSig Signature
for i := 0; i < 10000; i++ { for i := 0; i < 10000; i++ {
now := time.Now().Round(0) now := time.Now().Round(0)
sig.testNow = now secret.testNow = now
weakSig.testNow = now
thisStr := mtest.RandHex(512) thisStr := mtest.RandHex(512)
thisSig := SignString(sig, thisStr) thisSig := SignString(secret, thisStr)
thisWeakSig := SignString(weakSig, thisStr) thisSigStr := thisSig.String()
thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String()
// checking the times made it
assert.Equal(t, now, thisSig.Time())
assert.Equal(t, now, thisWeakSig.Time())
// sanity checks // sanity checks
assert.NotEmpty(t, thisSigStr) assert.NotEmpty(t, thisSigStr)
assert.NotEmpty(t, thisWeakSigStr) assert.Equal(t, now, thisSig.Time())
assert.NotEqual(t, thisSigStr, thisWeakSigStr) assert.NoError(t, VerifyString(secret, thisSig, thisStr))
assert.True(t, len(thisSigStr) > len(thisWeakSigStr))
// marshaling/unmarshaling // marshaling/unmarshaling
var thisSig2, thisWeakSig2 Signature var thisSig2 Signature
assert.NoError(t, thisSig2.UnmarshalText([]byte(thisSigStr))) assert.NoError(t, thisSig2.UnmarshalText([]byte(thisSigStr)))
assert.Equal(t, thisSigStr, thisSig2.String()) assert.Equal(t, thisSigStr, thisSig2.String())
assert.NoError(t, thisWeakSig2.UnmarshalText([]byte(thisWeakSigStr)))
assert.Equal(t, thisWeakSigStr, thisWeakSig2.String())
assert.Equal(t, now, thisSig2.Time()) assert.Equal(t, now, thisSig2.Time())
assert.Equal(t, now, thisWeakSig2.Time()) assert.NoError(t, VerifyString(secret, thisSig2, thisStr))
// Either sigVer should be able to verify either signature
assert.NoError(t, VerifyString(ver, thisSig, thisStr))
assert.NoError(t, VerifyString(weakVer, thisWeakSig, thisStr))
assert.NoError(t, VerifyString(ver, thisWeakSig, thisStr))
assert.NoError(t, VerifyString(weakVer, thisSig, thisStr))
if prevStr != "" { if prevStr != "" {
assert.NotEqual(t, prevSig.String(), thisSigStr) assert.NotEqual(t, prevSig.String(), thisSigStr)
assert.NotEqual(t, prevWeakSig.String(), thisWeakSigStr) err := VerifyString(secret, prevSig, thisStr)
err := VerifyString(ver, prevSig, thisStr)
assert.True(t, merry.Is(err, ErrInvalidSig))
err = VerifyString(ver, prevWeakSig, thisStr)
assert.True(t, merry.Is(err, ErrInvalidSig)) assert.True(t, merry.Is(err, ErrInvalidSig))
} }
prevStr = thisStr prevStr = thisStr
prevSig = thisSig prevSig = thisSig
prevWeakSig = thisWeakSig
} }
} }