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"
)
// 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

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 (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/binary"
"encoding/hex"
"encoding/json"
@ -187,73 +184,3 @@ func VerifyBytes(v Verifier, s Signature, b []byte) error {
func VerifyString(v Verifier, s Signature, in string) error {
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) {
secret := mtest.RandBytes(16)
sigI, ver := NewSignerVerifier(secret)
sig := sigI.(signVerifier)
weakSigI, weakVer := NewWeakSignerVerifier(secret)
weakSig := weakSigI.(signVerifier)
secret := NewSecret(mtest.RandBytes(16))
var prevStr string
var prevSig, prevWeakSig Signature
var prevSig Signature
for i := 0; i < 10000; i++ {
now := time.Now().Round(0)
sig.testNow = now
weakSig.testNow = now
secret.testNow = now
thisStr := mtest.RandHex(512)
thisSig := SignString(sig, thisStr)
thisWeakSig := SignString(weakSig, thisStr)
thisSigStr, thisWeakSigStr := thisSig.String(), thisWeakSig.String()
// checking the times made it
assert.Equal(t, now, thisSig.Time())
assert.Equal(t, now, thisWeakSig.Time())
thisSig := SignString(secret, thisStr)
thisSigStr := thisSig.String()
// sanity checks
assert.NotEmpty(t, thisSigStr)
assert.NotEmpty(t, thisWeakSigStr)
assert.NotEqual(t, thisSigStr, thisWeakSigStr)
assert.True(t, len(thisSigStr) > len(thisWeakSigStr))
assert.Equal(t, now, thisSig.Time())
assert.NoError(t, VerifyString(secret, thisSig, thisStr))
// marshaling/unmarshaling
var thisSig2, thisWeakSig2 Signature
var thisSig2 Signature
assert.NoError(t, thisSig2.UnmarshalText([]byte(thisSigStr)))
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, thisWeakSig2.Time())
// 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))
assert.NoError(t, VerifyString(secret, thisSig2, thisStr))
if prevStr != "" {
assert.NotEqual(t, prevSig.String(), thisSigStr)
assert.NotEqual(t, prevWeakSig.String(), thisWeakSigStr)
err := VerifyString(ver, prevSig, thisStr)
assert.True(t, merry.Is(err, ErrInvalidSig))
err = VerifyString(ver, prevWeakSig, thisStr)
err := VerifyString(secret, prevSig, thisStr)
assert.True(t, merry.Is(err, ErrInvalidSig))
}
prevStr = thisStr
prevSig = thisSig
prevWeakSig = thisWeakSig
}
}