mcrypto: move SignVerifier code into Secret, to account for encrypting later on
This commit is contained in:
parent
d4c665c4d5
commit
b30ccf4db8
@ -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
84
mcrypto/secret.go
Normal 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
54
mcrypto/secret_test.go
Normal 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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user