Skip to content

Commit

Permalink
feat: verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
brokeyourbike committed Aug 28, 2023
1 parent c18d47f commit d89189a
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 0 deletions.
1 change: 1 addition & 0 deletions signature/local/testdata/invalid.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
I am not a valid key
27 changes: 27 additions & 0 deletions signature/local/testdata/private-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEAvWUuT/6MB/5F9eO5cSfTtXNwVYBDOT4q+Vf3SxHDaz4YotSz
x/pbLsU7sQrTFp7Dds9GXMmnioQ7JIPOl+rUDFmwN0vL3UqJ6hHfCOBWMRGhSar/
osE7yA4o7gpBgzDnFhxS+jawvN5hODJYYUyMnawfBjqW1avDAmVJ2EIcTDavUAQP
VAqtfxTZLBC17OowsHcL4QGNV4LTFFKJGh60Xz5pKhYhAJRc9PpzX1qhkQWCW7SD
aWcga8t2FFm4vakQDS4XFF6o1BWr0xOFk368NTPRm6S4+PceSgEs6I/cVJhD567T
+cmDJjLEr58EaC0d57d9MJ1ljzJpfUNYfsCHkQIDAQABAoIBAQCuDv0fG2sKNY+K
Sb0110pkKjGFApnFU/MesM9euHaGas0taJnJIbcHJtCTBsW0tjghSqD5yYImdRv9
lP0a9P/oUcz3JLu5JjXVXpNBjxrngbJfB75kj34nf4vyKI7IyPq3gVRpDDYlfaKP
iQVcdoOMrJDeop3YxUA1RW02SJvFFa+z/rj5UIfpKaeUA6qqoiW0EV9lKXStgM1G
fMYrtgOcD39MqlQ82xUb+o2av5SScd4+e2M/Bw3WNNOgrRH+LHlfLqgAVhJVLEL2
n8aGrtXuMb+AS5jVo1f7o4iF0JGUTrLrKPBaJi4kpRRrnwxJQ7op4Fmt0SCagB5J
2a5ypCs5AoGBAOW9k1sprdgF5/Tl1xym78ltMigHol/4TBNVS0SIxG3ua2qJqNjh
b3Z3CvztBMhsjZaKtTZ5SHVaqQwJSWM5vIb+GFcWXNU6hFF6sPb6Z8hesVLUXXl/
Ouixwx6XCAhi4w2tdm/osyr+oz0fDHcnOp0fEPf4C1ZV0iZ4fpqmEXTDAoGBANML
EGUKVXvpKFUHlhEkH+yaxW6fHNAFagfcbqjdtJ+9odrC5iQ5+bMgO6x0cEEhyfEi
6XrzZCzXrNDLOo3t86kzo1eFtnbvupS5gL0Aa0B1k7xs+F+LKTUhqLbQhvAaeewi
sNk6WP9aKz3glXFCaMsbhaZfiNfGfEBbp6ARIn0bAoGAd7L4bc0feKhLHLwMAi+w
S8CLG3Qu09XovUH5f3HK9oP8j3u975I1owGPRgywK0Tz2HXYyNMUP6DG5zCHfh18
7tDBGc7a+3YLrvA/986r+CfuN/vR4BW8ObU0tmF2m/f52QyB4vjZMROPNa9RrVtR
HMAs9SXVslvwrH6naCgSfEUCgYEAn/YA5TTh+luNUHXBjxh8ghAFmYVMh3ezGGZC
EgiJgZJwfanAWrD+zICf+mT/OL7tm7JEmgalZT/MIy4HIdC8IQGcyxf0Z03pdBTu
uVL5+A+mfOlzkKegAj+S644+2PqYcx0QQnZxQosLv4jcfM5DM7+Wm7ZJuJMfVoCi
GzchlwUCgYEA5NKlcadCkuiUtxvNG8oYdqmMPos5z8TZAqUIZgg1VNe/P1pO1c3J
pH1c7SNqcKt5G1ZS+YkWBaT1r62B9akQMBeDF0EuYzB5WzbciSSq2Dz0+sB8wc/M
lAgR4TSWlwPx9nQO7GV271PloC6NdE3bEaHLVE/Ms6FImQt6yUEYEE0=
-----END RSA PRIVATE KEY-----
3 changes: 3 additions & 0 deletions signature/local/testdata/public-key-ed25519.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-----BEGIN PUBLIC KEY-----
MCowBQYDK2VwAyEAQRI20SbVzNLOKBqEJUnPLyIo3zcxbgOvJHj3JDyjsEI=
-----END PUBLIC KEY-----
9 changes: 9 additions & 0 deletions signature/local/testdata/public-key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvWUuT/6MB/5F9eO5cSfT
tXNwVYBDOT4q+Vf3SxHDaz4YotSzx/pbLsU7sQrTFp7Dds9GXMmnioQ7JIPOl+rU
DFmwN0vL3UqJ6hHfCOBWMRGhSar/osE7yA4o7gpBgzDnFhxS+jawvN5hODJYYUyM
nawfBjqW1avDAmVJ2EIcTDavUAQPVAqtfxTZLBC17OowsHcL4QGNV4LTFFKJGh60
Xz5pKhYhAJRc9PpzX1qhkQWCW7SDaWcga8t2FFm4vakQDS4XFF6o1BWr0xOFk368
NTPRm6S4+PceSgEs6I/cVJhD567T+cmDJjLEr58EaC0d57d9MJ1ljzJpfUNYfsCH
kQIDAQAB
-----END PUBLIC KEY-----
70 changes: 70 additions & 0 deletions signature/local/verifier.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package local

import (
"context"
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
)

var (
ErrFailedToDecodeKey = errors.New("failed to decode public key")
ErrKeyIsNotPublic = errors.New("key is not a public key")
ErrKeyIsNotPublicRSA = errors.New("key is not an RSA public key")
)

type verifier struct {
key *rsa.PublicKey
}

// NewVerifier creates a new verifier with local public key.
func NewVerifier(key *rsa.PublicKey) verifier {
return verifier{key: key}
}

// Verify verifies the given message against signature using the local public key.
// Signature must be a base64 encoded string. It returns an error if the verification fails.
func (l verifier) Verify(ctx context.Context, message, signature []byte) error {
decoded, err := base64.StdEncoding.DecodeString(string(signature))
if err != nil {
return fmt.Errorf("failed to decode signature: %w", err)
}

// calculate the digest of the message
digest := sha256.Sum256(message)

if err := rsa.VerifyPKCS1v15(l.key, crypto.SHA256, digest[:], decoded); err != nil {
return fmt.Errorf("failed to verify signature: %w", err)
}

return nil
}

// ParsePublicKey parses a public RSA key from a PEM encoded bytes.
func ParsePublicKey(pub []byte) (*rsa.PublicKey, error) {
pubPem, _ := pem.Decode(pub)
if pubPem == nil {
return nil, ErrFailedToDecodeKey
}

if pubPem.Type != "PUBLIC KEY" {
return nil, ErrKeyIsNotPublic
}

parsed, err := x509.ParsePKIXPublicKey(pubPem.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse public key: %w", err)
}

key, ok := parsed.(*rsa.PublicKey)
if !ok {
return nil, ErrKeyIsNotPublicRSA
}

return key, nil
}
114 changes: 114 additions & 0 deletions signature/local/verifier_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package local_test

import (
"context"
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
_ "embed"
"encoding/base64"
"sync"
"testing"

"github.com/brokeyourbike/clearbank-api-client-go/signature/local"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

//go:embed testdata/invalid.pem
var invalidKeyPem []byte

//go:embed testdata/public-key.pem
var publicKeyPem []byte

//go:embed testdata/public-key-ed25519.pem
var publicKeyEd25519Pem []byte

//go:embed testdata/private-key.pem
var privateKeyPem []byte

func Test_LocalVerifier_Verify(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)

tests := []struct {
name string
message []byte
signature []byte
wantErr bool
}{
{"valid signature", []byte("a"), signPKCS1v15(t, privateKey, []byte("a")), false},
{"invalid signature", []byte("a"), signPKCS1v15(t, privateKey, []byte("b")), true},
{"signature is not base64", []byte("a"), []byte("..."), true},
}

for i := range tests {
test := tests[i]
t.Run(test.name, func(t *testing.T) {
verifier := local.NewVerifier(&privateKey.PublicKey)
err = verifier.Verify(context.TODO(), test.message, test.signature)

if test.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}

func Test_LocalVerifier_Verify_ConcurrenUsage(t *testing.T) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
require.NoError(t, err)

verifier := local.NewVerifier(&privateKey.PublicKey)

wg := sync.WaitGroup{}

for i := 0; i < 10; i++ {
wg.Add(1)

go func() {
defer wg.Done()

message := []byte("a")
sig := signPKCS1v15(t, privateKey, message)

err := verifier.Verify(context.TODO(), message, sig)
assert.NoError(t, err)
}()
}

wg.Wait()
}

func signPKCS1v15(t *testing.T, key *rsa.PrivateKey, message []byte) []byte {
t.Helper()

hashed := sha256.Sum256(message)
signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hashed[:])
require.NoError(t, err)

encoded := base64.StdEncoding.EncodeToString(signature)

return []byte(encoded)
}

func TestParsePublicKey(t *testing.T) {
key, err := local.ParsePublicKey(publicKeyPem)
assert.NoError(t, err)
assert.NotNil(t, key)

_, err = local.ParsePublicKey(invalidKeyPem)
assert.Error(t, err)
assert.ErrorIs(t, err, local.ErrFailedToDecodeKey)

_, err = local.ParsePublicKey(publicKeyEd25519Pem)
assert.Error(t, err)
assert.ErrorIs(t, err, local.ErrKeyIsNotPublicRSA)

_, err = local.ParsePublicKey(privateKeyPem)
assert.Error(t, err)
assert.ErrorIs(t, err, local.ErrKeyIsNotPublic)
}

0 comments on commit d89189a

Please sign in to comment.