Skip to content

Commit

Permalink
Updating blindrsa to be compliant with RFC9474.
Browse files Browse the repository at this point in the history
  • Loading branch information
armfazh committed Oct 27, 2023
1 parent 44133f7 commit ceb2d90
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 80 deletions.
51 changes: 35 additions & 16 deletions blindsign/blindrsa/brsa.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
package blindrsa

// This package implements the blind RSA protocol based on the CFRG specification:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures
// Package blindrsa implements the RSA Blind Signature Protocol.
//
// The RSA Blind Signature protocol, also called RSABSSA
// (RSA Blind Signature with Appendix) is a two-party protocol
// between a Client and Server where they interact to compute
//
// sig = Sign(sk, input_msg),
//
// where `input_msg = Prepare(msg)` is a prepared version of a private
// message `msg` provided by the Client, and `sk` is the private signing
// key provided by the server.
//
// # Supported Variants
//
// Blind RSA is an example of a blind signature protocol is a two-party protocol
// for computing a digital signature. One party (the server) holds the signing
// key, and the other (the client) holds the message input. Blindness
// ensures that the server does not learn anything about the client's
// input during the BlindSign step.
// This package is compliant with the [RFC-9474] document
// and supports the following variants:
// - [NewVerifier] implements RSABSSA-SHA384-PSS-Deterministic
// - [NewDeterministicVerifier] implements RSABSSA-SHA384-PSSZERO-Deterministic
//
// while these variants are not supported yet:
// - RSABSSA-SHA384-PSS-Randomized
// - RSABSSA-SHA384-PSSZERO-Randomized
//
// [RFC-9474]: https://www.rfc-editor.org/info/rfc9474
package blindrsa

import (
"crypto"
Expand Down Expand Up @@ -48,7 +63,7 @@ type deterministicBRSAVerifier struct {
}

// Verifier is a type that implements the client side of the blind RSA
// protocol, described in https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures
// protocol, described in https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants
type Verifier interface {
// 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.
Expand All @@ -66,7 +81,7 @@ type Verifier interface {

// NewDeterministicVerifier creates a new DeterministicBRSAVerifier using the corresponding Signer parameters.
// This corresponds to the RSABSSA-SHA384-PSSZERO-Deterministic variant. See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures#name-rsabssa-variants
// https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants
func NewDeterministicVerifier(pk *rsa.PublicKey, hash crypto.Hash) Verifier {
h := common.ConvertHashFunction(hash)
return deterministicBRSAVerifier{
Expand All @@ -83,7 +98,7 @@ func (v deterministicBRSAVerifier) Hash() hash.Hash {

// NewVerifier creates a new BRSAVerifier using the corresponding Signer parameters.
// This corresponds to the RSABSSA-SHA384-PSS-Deterministic variant. See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures#name-rsabssa-variants
// https://www.rfc-editor.org/rfc/rfc9474.html#name-rsabssa-variants
func NewVerifier(pk *rsa.PublicKey, hash crypto.Hash) Verifier {
h := common.ConvertHashFunction(hash)
return randomBRSAVerifier{
Expand All @@ -98,6 +113,10 @@ func (v randomBRSAVerifier) Hash() hash.Hash {
return v.hash
}

func prepareMsg(message, prefix []byte) []byte {
return append(append([]byte{}, prefix...), message...)
}

func fixedBlind(message, salt []byte, r, rInv *big.Int, pk *rsa.PublicKey, hash hash.Hash) ([]byte, VerifierState, error) {
encodedMsg, err := common.EncodeMessageEMSAPSS(message, pk.N, hash, salt)
if err != nil {
Expand Down Expand Up @@ -129,7 +148,7 @@ func fixedBlind(message, salt []byte, r, rInv *big.Int, pk *rsa.PublicKey, hash
// 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
// https://www.rfc-editor.org/rfc/rfc9474.html#name-blind
func (v deterministicBRSAVerifier) Blind(random io.Reader, message []byte) ([]byte, VerifierState, error) {
if random == nil {
return nil, VerifierState{}, common.ErrInvalidRandomness
Expand Down Expand Up @@ -171,7 +190,7 @@ func (v deterministicBRSAVerifier) Verify(message, signature []byte) error {
// hash function. 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
// https://www.rfc-editor.org/rfc/rfc9474.html#name-blind
func (v randomBRSAVerifier) Blind(random io.Reader, message []byte) ([]byte, VerifierState, error) {
if random == nil {
return nil, VerifierState{}, common.ErrInvalidRandomness
Expand Down Expand Up @@ -237,7 +256,7 @@ type VerifierState struct {
// Finalize computes and outputs the final signature, if it's valid. Otherwise, it returns an error.
//
// See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02#section-5.1.3
// https://www.rfc-editor.org/rfc/rfc9474.html#name-finalize
func (state VerifierState) Finalize(data []byte) ([]byte, error) {
kLen := (state.pk.N.BitLen() + 7) / 8
if len(data) != kLen {
Expand Down Expand Up @@ -291,7 +310,7 @@ func NewSigner(sk *rsa.PrivateKey) Signer {
// message input, if it's of valid length, and returns an error should the function fail.
//
// See the specification for more details:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-rsa-blind-signatures-02#section-5.1.2
// https://www.rfc-editor.org/rfc/rfc9474.html#name-blindsign
func (signer Signer) BlindSign(data []byte) ([]byte, error) {
kLen := (signer.sk.N.BitLen() + 7) / 8
if len(data) != kLen {
Expand Down
140 changes: 108 additions & 32 deletions blindsign/blindrsa/brsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import (
"io"
"math/big"
"os"
"strings"
"testing"

"github.com/cloudflare/circl/internal/test"
)

// 2048-bit RSA private key
Expand Down Expand Up @@ -254,31 +257,39 @@ func TestFixedRandomSignVerify(t *testing.T) {
}

type rawTestVector struct {
Name string `json:"name"`
P string `json:"p"`
Q string `json:"q"`
N string `json:"n"`
E string `json:"e"`
D string `json:"d"`
Msg string `json:"msg"`
MsgPrefix string `json:"msg_prefix"`
InputMsg string `json:"input_msg"`
Salt string `json:"salt"`
SaltLen string `json:"sLen"`
IsRandomized string `json:"is_randomized"`
Inv string `json:"inv"`
EncodedMsg string `json:"encoded_msg"`
BlindedMessage string `json:"blinded_message"`
BlindedMessage string `json:"blinded_msg"`
BlindSig string `json:"blind_sig"`
Sig string `json:"sig"`
}

type testVector struct {
t *testing.T
name string
p *big.Int
q *big.Int
n *big.Int
e int
d *big.Int
msg []byte
msgPrefix []byte
inputMsg []byte
salt []byte
saltLen int
isRandomized bool
blindInverse *big.Int
encodedMessage []byte
blindedMessage []byte
blindSig []byte
sig []byte
Expand All @@ -290,17 +301,14 @@ type testVectorList struct {
}

func mustUnhexBigInt(number string) *big.Int {
data, err := hex.DecodeString(number)
if err != nil {
panic(err)
}

data := mustUnhex(number)
value := new(big.Int)
value.SetBytes(data)
return value
}

func mustUnhex(value string) []byte {
value = strings.TrimPrefix(value, "0x")
data, err := hex.DecodeString(value)
if err != nil {
panic(err)
Expand All @@ -322,14 +330,18 @@ func (tv *testVector) UnmarshalJSON(data []byte) error {
return err
}

tv.name = raw.Name
tv.p = mustUnhexBigInt(raw.P)
tv.q = mustUnhexBigInt(raw.Q)
tv.n = mustUnhexBigInt(raw.N)
tv.e = mustUnhexInt(raw.E)
tv.d = mustUnhexBigInt(raw.D)
tv.msg = mustUnhex(raw.Msg)
tv.msgPrefix = mustUnhex(raw.MsgPrefix)
tv.inputMsg = mustUnhex(raw.InputMsg)
tv.salt = mustUnhex(raw.Salt)
tv.encodedMessage = mustUnhex(raw.EncodedMsg)
tv.saltLen = mustUnhexInt(raw.SaltLen)
tv.isRandomized = mustUnhexInt(raw.IsRandomized) != 0
tv.blindedMessage = mustUnhex(raw.BlindedMessage)
tv.blindInverse = mustUnhexBigInt(raw.Inv)
tv.blindSig = mustUnhex(raw.BlindSig)
Expand All @@ -356,16 +368,11 @@ func (tvl *testVectorList) UnmarshalJSON(data []byte) error {
}

func verifyTestVector(t *testing.T, vector testVector) {
key, err := loadPrivateKey()
if err != nil {
t.Fatal(err)
}

key := new(rsa.PrivateKey)
key.PublicKey.N = vector.n
key.PublicKey.E = vector.e
key.D = vector.d
key.Primes[0] = vector.p
key.Primes[1] = vector.q
key.Primes = []*big.Int{vector.p, vector.q}
key.Precomputed.Dp = nil // Remove precomputed CRT values

// Recompute the original blind
Expand All @@ -376,34 +383,56 @@ func verifyTestVector(t *testing.T, vector testVector) {
}

signer := NewSigner(key)
verifier := NewVerifier(&key.PublicKey, crypto.SHA384)

blindedMsg, state, err := fixedBlind(vector.msg, vector.salt, r, rInv, &key.PublicKey, verifier.Hash())
if err != nil {
t.Fatal(err)
var verifier Verifier
switch vector.name {
case "RSABSSA-SHA384-PSS-Deterministic":
verifier = NewVerifier(&key.PublicKey, crypto.SHA384)
case "RSABSSA-SHA384-PSSZERO-Deterministic":
verifier = NewDeterministicVerifier(&key.PublicKey, crypto.SHA384)
case "RSABSSA-SHA384-PSS-Randomized", "RSABSSA-SHA384-PSSZERO-Randomized":
t.Skipf("variant %v not supported yet", vector.name)
default:
t.Fatal("variant not supported")
}

blindSig, err := signer.BlindSign(blindedMsg)
if err != nil {
t.Fatal(err)
inputMsg := prepareMsg(vector.msg, vector.msgPrefix)
got := hex.EncodeToString(inputMsg)
want := hex.EncodeToString(vector.inputMsg)
if got != want {
test.ReportError(t, got, want)
}

sig, err := state.Finalize(blindSig)
if err != nil {
t.Fatal(err)
blindedMsg, state, err := fixedBlind(inputMsg, vector.salt, r, rInv, &key.PublicKey, verifier.Hash())
test.CheckNoErr(t, err, "fixedBlind failed")
got = hex.EncodeToString(blindedMsg)
want = hex.EncodeToString(vector.blindedMessage)
if got != want {
test.ReportError(t, got, want)
}

if !bytes.Equal(state.encodedMsg, vector.encodedMessage) {
t.Errorf("Encoded message mismatch: expected %x, got %x", state.encodedMsg, vector.encodedMessage)
blindSig, err := signer.BlindSign(blindedMsg)
test.CheckNoErr(t, err, "blindSign failed")
got = hex.EncodeToString(blindSig)
want = hex.EncodeToString(vector.blindSig)
if got != want {
test.ReportError(t, got, want)
}

if !bytes.Equal(sig, vector.sig) {
t.Errorf("Signature mismatch: expected %x, got %x", sig, vector.sig)
sig, err := state.Finalize(blindSig)
test.CheckNoErr(t, err, "finalize failed")
got = hex.EncodeToString(sig)
want = hex.EncodeToString(vector.sig)
if got != want {
test.ReportError(t, got, want)
}

err = verifier.Verify(inputMsg, sig)
test.CheckNoErr(t, err, "verification failed")
}

func TestVectors(t *testing.T) {
data, err := os.ReadFile("testdata/test_vectors.json")
data, err := os.ReadFile("testdata/test_vectors_rfc9474.json")
if err != nil {
t.Fatal("Failed reading test vectors:", err)
}
Expand All @@ -415,7 +444,9 @@ func TestVectors(t *testing.T) {
}

for _, vector := range tvl.vectors {
verifyTestVector(t, vector)
t.Run(vector.name, func(tt *testing.T) {
verifyTestVector(tt, vector)
})
}
}

Expand Down Expand Up @@ -463,3 +494,48 @@ func BenchmarkBRSA(b *testing.B) {
b.Fatal(err)
}
}

func Example_blindrsa() {
// Setup (offline)

// Server: generate an RSA keypair.
sk, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to generate RSA key: %v", err)
return
}
pk := &sk.PublicKey
server := NewSigner(sk)

// Client: stores Server's public key.
verifier := NewVerifier(pk, crypto.SHA384)

// Protocol (online)

// Client blinds a message.
msg := []byte("alice and bob")
blindedMsg, state, err := verifier.Blind(rand.Reader, msg)
if err != nil {
fmt.Fprintf(os.Stderr, "client failed to generate blinded message: %v", err)
return
}

// Server signs a blinded message, and produces a blinded signature.
blindedSignature, err := server.BlindSign(blindedMsg)
if err != nil {
fmt.Fprintf(os.Stderr, "server failed to sign: %v", err)
return
}

// Client builds a signature from the previous state and the blinded signature.
signature, err := state.Finalize(blindedSignature)
if err != nil {
fmt.Fprintf(os.Stderr, "client failed to obtain signature: %v", err)
return
}

// Client verifies the signature is valid.
ok := verifier.Verify(msg, signature)
fmt.Printf("Valid signature: %v", ok == nil)
// Output: Valid signature: true
}
Loading

0 comments on commit ceb2d90

Please sign in to comment.