Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blindrsa: updating blindrsa to be compliant with RFC9474 #464

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
armfazh marked this conversation as resolved.
Show resolved Hide resolved
//
// The RSA Blind Signature protocol, also called RSABSSA
armfazh marked this conversation as resolved.
Show resolved Hide resolved
// (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...)
armfazh marked this conversation as resolved.
Show resolved Hide resolved
}

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interestingly, having simpler test vectors like this in the spec could be beneficial. It's not going to be for this protocol as it's already RFC, but I sense it would be quite useful to have a test vector that fits in a simple example.

Copy link
Contributor Author

@armfazh armfazh Oct 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a good suggestion for those specs that haven't been published (frozen) yet.

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