Skip to content

Commit

Permalink
Add deterministic blind RSA verifier (#379)
Browse files Browse the repository at this point in the history
* Add deterministic blind RSA verifier
  • Loading branch information
chris-wood authored Oct 17, 2022
1 parent fa1d557 commit 2536df9
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 35 deletions.
125 changes: 107 additions & 18 deletions blindsign/blindrsa/blindrsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ package blindrsa
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"errors"
"hash"
Expand All @@ -15,21 +18,63 @@ import (
"github.com/cloudflare/circl/blindsign"
)

var errUnsupportedHashFunction = errors.New("unsupported hash function")

// An RSAVerifier represents a Verifier in the RSA blind signature protocol.
// It carries state needed to produce and validate an RSA blind signature.
type RSAVerifier struct {
// Public key of the Signer
pk *rsa.PublicKey

// Identifier of the cryptographic hash function used in producing the message signature
cryptoHash crypto.Hash

// Hash function used in producing the message signature
hash hash.Hash
}

// A DeterminsiticRSAVerifier is an RSAVerifier that supports deterministic signatures.
type DeterminsiticRSAVerifier struct {
// Public key of the Signer
pk *rsa.PublicKey

// Identifier of the cryptographic hash function used in producing the message signature
cryptoHash crypto.Hash

// Hash function used in producing the message signature
hash hash.Hash
}

func convertHashFunction(hash crypto.Hash) hash.Hash {
switch hash {
case crypto.SHA256:
return sha256.New()
case crypto.SHA384:
return sha512.New384()
case crypto.SHA512:
return sha512.New()
default:
panic(errUnsupportedHashFunction)
}
}

// NewDeterministicRSAVerifier creates a new RSAVerifier using the corresponding Signer parameters.
func NewDeterministicRSAVerifier(pk *rsa.PublicKey, hash crypto.Hash) DeterminsiticRSAVerifier {
h := convertHashFunction(hash)
return DeterminsiticRSAVerifier{
pk: pk,
cryptoHash: hash,
hash: h,
}
}

// NewRSAVerifier creates a new RSAVerifier using the corresponding Signer parameters.
func NewRSAVerifier(pk *rsa.PublicKey, hash hash.Hash) RSAVerifier {
func NewRSAVerifier(pk *rsa.PublicKey, hash crypto.Hash) RSAVerifier {
h := convertHashFunction(hash)
return RSAVerifier{
pk: pk,
hash: hash,
pk: pk,
cryptoHash: hash,
hash: h,
}
}

Expand Down Expand Up @@ -64,32 +109,68 @@ func generateBlindingFactor(random io.Reader, key *rsa.PublicKey) (*big.Int, *bi
return r, rInv, nil
}

func (v RSAVerifier) fixedBlind(message, salt []byte, r, rInv *big.Int) ([]byte, blindsign.VerifierState, error) {
encodedMsg, err := encodeMessageEMSAPSS(message, v.pk, v.hash, salt)
func fixedBlind(message, salt []byte, r, rInv *big.Int, pk *rsa.PublicKey, hash hash.Hash) ([]byte, blindsign.VerifierState, error) {
encodedMsg, err := encodeMessageEMSAPSS(message, pk, hash, salt)
if err != nil {
return nil, nil, err
}

m := new(big.Int).SetBytes(encodedMsg)

bigE := big.NewInt(int64(v.pk.E))
x := new(big.Int).Exp(r, bigE, v.pk.N)
bigE := big.NewInt(int64(pk.E))
x := new(big.Int).Exp(r, bigE, pk.N)
z := new(big.Int).Set(m)
z.Mul(z, x)
z.Mod(z, v.pk.N)
z.Mod(z, pk.N)

kLen := (v.pk.N.BitLen() + 7) / 8
kLen := (pk.N.BitLen() + 7) / 8
blindedMsg := make([]byte, kLen)
z.FillBytes(blindedMsg)

return blindedMsg, RSAVerifierState{
encodedMsg: encodedMsg,
verifier: v,
pk: pk,
hash: hash,
salt: salt,
rInv: rInv,
}, nil
}

// Blind initializes the blind RSA protocol using an input message and source of randomness. The
// signature is deterministic. This function fails if randomness was not provided.
//
// See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02#section-5.1.1
func (v DeterminsiticRSAVerifier) Blind(random io.Reader, message []byte) ([]byte, blindsign.VerifierState, error) {
if random == nil {
return nil, nil, ErrInvalidRandomness
}

r, rInv, err := generateBlindingFactor(random, v.pk)
if err != nil {
return nil, nil, err
}

return fixedBlind(message, nil, r, rInv, v.pk, v.hash)
}

func verifyMessageSignature(message, signature []byte, saltLength int, pk *rsa.PublicKey, hash crypto.Hash) error {
h := convertHashFunction(hash)
h.Write(message)
digest := h.Sum(nil)

err := rsa.VerifyPSS(pk, hash, digest, signature, &rsa.PSSOptions{
Hash: hash,
SaltLength: saltLength,
})
return err
}

// Verify verifies the input (message, signature) pair and produces an error upon failure.
func (v DeterminsiticRSAVerifier) Verify(message, signature []byte) error {
return verifyMessageSignature(message, signature, 0, v.pk, v.cryptoHash)
}

// Blind initializes the blind RSA protocol using an input message and source of randomness. The
// signature includes a randomly generated PSS salt whose length equals the size of the underlying
// hash function. This function fails if randomness was not provided.
Expand All @@ -112,7 +193,7 @@ func (v RSAVerifier) Blind(random io.Reader, message []byte) ([]byte, blindsign.
return nil, nil, err
}

return v.fixedBlind(message, salt, r, rInv)
return fixedBlind(message, salt, r, rInv, v.pk, v.hash)
}

// FixedBlind runs the Blind function with fixed blind and salt inputs.
Expand All @@ -127,14 +208,22 @@ func (v RSAVerifier) FixedBlind(message, blind, salt []byte) ([]byte, blindsign.
return nil, nil, ErrInvalidBlind
}

return v.fixedBlind(message, salt, r, rInv)
return fixedBlind(message, salt, r, rInv, v.pk, v.hash)
}

// Verify verifies the input (message, signature) pair and produces an error upon failure.
func (v RSAVerifier) Verify(message, signature []byte) error {
return verifyMessageSignature(message, signature, v.hash.Size(), v.pk, v.cryptoHash)
}

// An RSAVerifierState carries state needed to complete the blind signature protocol
// as a verifier.
type RSAVerifierState struct {
// An RSA verifier carrying Signer verification state
verifier RSAVerifier
// Public key of the Signer
pk *rsa.PublicKey

// Hash function used in producing the message signature
hash hash.Hash

// The hashed and encoded message being signed
encodedMsg []byte
Expand Down Expand Up @@ -163,20 +252,20 @@ func verifyBlindSignature(pub *rsa.PublicKey, hashed, sig []byte) error {
// See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02#section-5.1.3
func (state RSAVerifierState) Finalize(data []byte) ([]byte, error) {
kLen := (state.verifier.pk.N.BitLen() + 7) / 8
kLen := (state.pk.N.BitLen() + 7) / 8
if len(data) != kLen {
return nil, ErrUnexpectedSize
}

z := new(big.Int).SetBytes(data)
s := new(big.Int).Set(state.rInv)
s.Mul(s, z)
s.Mod(s, state.verifier.pk.N)
s.Mod(s, state.pk.N)

sig := make([]byte, kLen)
s.FillBytes(sig)

err := verifyBlindSignature(state.verifier.pk, state.encodedMsg, sig)
err := verifyBlindSignature(state.pk, state.encodedMsg, sig)
if err != nil {
return nil, err
}
Expand All @@ -186,7 +275,7 @@ func (state RSAVerifierState) Finalize(data []byte) ([]byte, error) {

// CopyBlind returns an encoding of the blind value used in the protocol.
func (state RSAVerifierState) CopyBlind() []byte {
r := new(big.Int).ModInverse(state.rInv, state.verifier.pk.N)
r := new(big.Int).ModInverse(state.rInv, state.pk.N)
return r.Bytes()
}

Expand Down
43 changes: 27 additions & 16 deletions blindsign/blindrsa/blindrsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"encoding/hex"
"encoding/json"
Expand All @@ -15,6 +14,8 @@ import (
"math/big"
"os"
"testing"

"github.com/cloudflare/circl/blindsign"
)

// 4096-bit RSA private key
Expand Down Expand Up @@ -85,7 +86,7 @@ func loadPrivateKey(t *testing.T) *rsa.PrivateKey {
return privateKey
}

func runSignatureProtocol(signer RSASigner, verifier RSAVerifier, message []byte, random io.Reader) ([]byte, error) {
func runSignatureProtocol(signer RSASigner, verifier blindsign.Verifier, message []byte, random io.Reader) ([]byte, error) {
blindedMsg, state, err := verifier.Blind(random, message)
if err != nil {
return nil, err
Expand All @@ -110,13 +111,7 @@ func runSignatureProtocol(signer RSASigner, verifier RSAVerifier, message []byte
return nil, err
}

hash := sha512.New()
hash.Write(message)
digest := hash.Sum(nil)
err = rsa.VerifyPSS(verifier.pk, crypto.SHA512, digest, sig, &rsa.PSSOptions{
Hash: crypto.SHA512,
SaltLength: crypto.SHA512.Size(),
})
err = verifier.Verify(message, sig)
if err != nil {
return nil, err
}
Expand All @@ -128,7 +123,23 @@ func TestRoundTrip(t *testing.T) {
message := []byte("hello world")
key := loadPrivateKey(t)

verifier := NewRSAVerifier(&key.PublicKey, sha512.New())
verifier := NewRSAVerifier(&key.PublicKey, crypto.SHA512)
signer := NewRSASigner(key)

sig, err := runSignatureProtocol(signer, verifier, message, rand.Reader)
if err != nil {
t.Fatal(err)
}
if sig == nil {
t.Fatal("nil signature output")
}
}

func TestDeterministicRoundTrip(t *testing.T) {
message := []byte("hello world")
key := loadPrivateKey(t)

verifier := NewDeterministicRSAVerifier(&key.PublicKey, crypto.SHA512)
signer := NewRSASigner(key)

sig, err := runSignatureProtocol(signer, verifier, message, rand.Reader)
Expand All @@ -140,11 +151,11 @@ func TestRoundTrip(t *testing.T) {
}
}

func TestDeterministicSignFail(t *testing.T) {
func TestDeterministicBlindFailure(t *testing.T) {
message := []byte("hello world")
key := loadPrivateKey(t)

verifier := NewRSAVerifier(&key.PublicKey, sha512.New())
verifier := NewDeterministicRSAVerifier(&key.PublicKey, crypto.SHA512)
signer := NewRSASigner(key)

_, err := runSignatureProtocol(signer, verifier, message, nil)
Expand All @@ -157,7 +168,7 @@ func TestRandomSignVerify(t *testing.T) {
message := []byte("hello world")
key := loadPrivateKey(t)

verifier := NewRSAVerifier(&key.PublicKey, sha512.New())
verifier := NewRSAVerifier(&key.PublicKey, crypto.SHA512)
signer := NewRSASigner(key)

sig1, err := runSignatureProtocol(signer, verifier, message, rand.Reader)
Expand Down Expand Up @@ -193,7 +204,7 @@ func TestFixedRandomSignVerify(t *testing.T) {
message := []byte("hello world")
key := loadPrivateKey(t)

verifier := NewRSAVerifier(&key.PublicKey, sha512.New())
verifier := NewRSAVerifier(&key.PublicKey, crypto.SHA512)
signer := NewRSASigner(key)

mockRand := &mockRandom{0}
Expand Down Expand Up @@ -334,9 +345,9 @@ func verifyTestVector(t *testing.T, vector testVector) {
}

signer := NewRSASigner(key)
verifier := NewRSAVerifier(&key.PublicKey, sha512.New384())
verifier := NewRSAVerifier(&key.PublicKey, crypto.SHA384)

blindedMsg, state, err := verifier.fixedBlind(vector.msg, vector.salt, r, rInv)
blindedMsg, state, err := fixedBlind(vector.msg, vector.salt, r, rInv, verifier.pk, verifier.hash)
if err != nil {
t.Fatal(err)
}
Expand Down
8 changes: 7 additions & 1 deletion blindsign/blindsign.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@
// input during the BlindSign step.
package blindsign

import "io"

// A Verifier represents a specific instance of a blind signature verifier.
type Verifier interface {
// Blind produces an encoded protocol message and VerifierState based on
// the input message and Signer's public key.
Blind(message []byte) ([]byte, VerifierState, error)
Blind(random io.Reader, message []byte) ([]byte, VerifierState, error)

// Verify verifies a (message, signature) pair over and produces an error
// if the signature is invalid.
Verify(message, signature []byte) error
}

// A VerifierState represents the protocol state used to run and complete a
Expand Down

0 comments on commit 2536df9

Please sign in to comment.