From 36e536a4f0cb8da887b02ed809589a21aaf3671e Mon Sep 17 00:00:00 2001 From: Micah Hausler Date: Sun, 29 Sep 2024 11:35:05 -0500 Subject: [PATCH] Added rsa algos --- alg_rsa/doc.go | 4 ++ alg_rsa/rsa_pkcs_256.go | 60 +++++++++++++++++++++++++++++ alg_rsa/rsa_pkcs_256_test.go | 75 ++++++++++++++++++++++++++++++++++++ alg_rsa/rsa_pss_512.go | 65 +++++++++++++++++++++++++++++++ alg_rsa/rsa_pss_512_test.go | 75 ++++++++++++++++++++++++++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 alg_rsa/doc.go create mode 100644 alg_rsa/rsa_pkcs_256.go create mode 100644 alg_rsa/rsa_pkcs_256_test.go create mode 100644 alg_rsa/rsa_pss_512.go create mode 100644 alg_rsa/rsa_pss_512_test.go diff --git a/alg_rsa/doc.go b/alg_rsa/doc.go new file mode 100644 index 0000000..47f3a9b --- /dev/null +++ b/alg_rsa/doc.go @@ -0,0 +1,4 @@ +/* +Package alg_rsa provides signers and verifiers rsa-pss-sha512 and rsa-v1_5-sha256. +*/ +package alg_rsa diff --git a/alg_rsa/rsa_pkcs_256.go b/alg_rsa/rsa_pkcs_256.go new file mode 100644 index 0000000..707c4d0 --- /dev/null +++ b/alg_rsa/rsa_pkcs_256.go @@ -0,0 +1,60 @@ +package alg_rsa + +import ( + "context" + "crypto" + "crypto/rsa" + "crypto/sha256" + "errors" + + "github.com/common-fate/httpsig/contentdigest" +) + +const RSASSA_PKCS1_1_5_SHA256 = `rsa-v1_5-sha256` + +// NewRSAPKCS256Signer returns a signing algorithm based on +// the provided rsa private key. +func NewRSAPKCS256Signer(key *rsa.PrivateKey) *RSAPKCS256 { + return &RSAPKCS256{PrivateKey: key} +} + +// NewRSAPKCS256Verifier returns a verification algorithm based on +// the provided rsa public key. +func NewRSAPKCS256Verifier(key *rsa.PublicKey) *RSAPKCS256 { + return &RSAPKCS256{PublicKey: key} +} + +type RSAPKCS256 struct { + PrivateKey *rsa.PrivateKey + PublicKey *rsa.PublicKey + Attrs any +} + +// Attributes returns server-side attributes associated with the key. +func (a RSAPKCS256) Attributes() any { + return a.Attrs +} + +func (a RSAPKCS256) Type() string { + return RSASSA_PKCS1_1_5_SHA256 +} + +func (a RSAPKCS256) ContentDigest() contentdigest.Digester { + return contentdigest.SHA256 +} + +func (a RSAPKCS256) Sign(ctx context.Context, base string) ([]byte, error) { + if a.PrivateKey == nil { + return nil, errors.New("private key was nil") + } + digest := sha256.Sum256([]byte(base)) + return rsa.SignPKCS1v15(nil, a.PrivateKey, crypto.SHA256, digest[:]) +} + +func (a RSAPKCS256) Verify(ctx context.Context, base string, signature []byte) error { + if a.PublicKey == nil { + return errors.New("public key was nil") + } + digest := sha256.Sum256([]byte(base)) + return rsa.VerifyPKCS1v15(a.PublicKey, crypto.SHA256, digest[:], signature) +} diff --git a/alg_rsa/rsa_pkcs_256_test.go b/alg_rsa/rsa_pkcs_256_test.go new file mode 100644 index 0000000..3f3b543 --- /dev/null +++ b/alg_rsa/rsa_pkcs_256_test.go @@ -0,0 +1,75 @@ +package alg_rsa + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "testing" +) + +func TestRSAPKCS256SignVerify(t *testing.T) { + kp, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + + testCases := []struct { + name string + base string + privateKey *rsa.PrivateKey + publicKey *rsa.PublicKey + wantSignErr bool + wantVerifyErr bool + }{ + { + name: "Valid key", + base: "signed base", + privateKey: kp, + publicKey: &kp.PublicKey, + wantSignErr: false, + wantVerifyErr: false, + }, + { + name: "nil private key", + base: "signed base", + privateKey: nil, + wantSignErr: true, + }, + { + name: "nil public key", + base: "signed base", + privateKey: kp, + publicKey: nil, + wantSignErr: false, + wantVerifyErr: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rsaSigner := RSAPKCS256{PrivateKey: tc.privateKey, PublicKey: tc.publicKey} + got, err := rsaSigner.Sign(context.Background(), tc.base) + if err != nil { + if !tc.wantSignErr { + t.Error(err) + } + return + } + if tc.wantSignErr { + t.Errorf("wanted error, got none") + return + } + + err = rsaSigner.Verify(context.Background(), tc.base, got) + if err != nil { + if !tc.wantVerifyErr { + t.Error(err) + } + return + } + if tc.wantVerifyErr { + t.Errorf("wanted error, got none") + return + } + }) + } +} diff --git a/alg_rsa/rsa_pss_512.go b/alg_rsa/rsa_pss_512.go new file mode 100644 index 0000000..841846d --- /dev/null +++ b/alg_rsa/rsa_pss_512.go @@ -0,0 +1,65 @@ +package alg_rsa + +import ( + "context" + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha512" + "errors" + + "github.com/common-fate/httpsig/contentdigest" +) + +const RSASSA_PSS_SHA512 = `rsa-pss-sha512` + +// NewRSAPSS512Signer returns a signing algorithm based on +// the provided rsa private key. +func NewRSAPSS512Signer(key *rsa.PrivateKey) *RSAPSS512 { + return &RSAPSS512{PrivateKey: key, PublicKey: &key.PublicKey} +} + +// NewRSAPSS512Verifier returns a verification algorithm based on +// the provided rsa public key. +func NewRSAPSS512Verifier(key *rsa.PublicKey) *RSAPSS512 { + return &RSAPSS512{PublicKey: key} +} + +type RSAPSS512 struct { + PrivateKey *rsa.PrivateKey + PublicKey *rsa.PublicKey + Attrs any +} + +// Attributes returns server-side attributes associated with the key. +func (a RSAPSS512) Attributes() any { + return a.Attrs +} + +func (a RSAPSS512) Type() string { + return RSASSA_PSS_SHA512 +} + +func (a RSAPSS512) ContentDigest() contentdigest.Digester { + return contentdigest.SHA512 +} + +func (a RSAPSS512) Sign(ctx context.Context, base string) ([]byte, error) { + if a.PrivateKey == nil { + return nil, errors.New("private key was nil") + } + digest := sha512.Sum512([]byte(base)) + return rsa.SignPSS(rand.Reader, a.PrivateKey, crypto.SHA512, digest[:], &rsa.PSSOptions{ + SaltLength: 64, + }) +} + +func (a RSAPSS512) Verify(ctx context.Context, base string, signature []byte) error { + if a.PublicKey == nil { + return errors.New("public key was nil") + } + digest := sha512.Sum512([]byte(base)) + return rsa.VerifyPSS(a.PublicKey, crypto.SHA512, digest[:], signature, &rsa.PSSOptions{ + SaltLength: 64, + }) +} diff --git a/alg_rsa/rsa_pss_512_test.go b/alg_rsa/rsa_pss_512_test.go new file mode 100644 index 0000000..8f49123 --- /dev/null +++ b/alg_rsa/rsa_pss_512_test.go @@ -0,0 +1,75 @@ +package alg_rsa + +import ( + "context" + "crypto/rand" + "crypto/rsa" + "testing" +) + +func TestRSAPSS512SignVerify(t *testing.T) { + kp, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatal(err) + } + + testCases := []struct { + name string + base string + privateKey *rsa.PrivateKey + publicKey *rsa.PublicKey + wantSignErr bool + wantVerifyErr bool + }{ + { + name: "Valid key", + base: "signed base", + privateKey: kp, + publicKey: &kp.PublicKey, + wantSignErr: false, + wantVerifyErr: false, + }, + { + name: "nil private key", + base: "signed base", + privateKey: nil, + wantSignErr: true, + }, + { + name: "nil public key", + base: "signed base", + privateKey: kp, + publicKey: nil, + wantSignErr: false, + wantVerifyErr: true, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rsaSigner := RSAPSS512{PrivateKey: tc.privateKey, PublicKey: tc.publicKey} + got, err := rsaSigner.Sign(context.Background(), tc.base) + if err != nil { + if !tc.wantSignErr { + t.Error(err) + } + return + } + if tc.wantSignErr { + t.Errorf("wanted error, got none") + return + } + + err = rsaSigner.Verify(context.Background(), tc.base, got) + if err != nil { + if !tc.wantVerifyErr { + t.Error(err) + } + return + } + if tc.wantVerifyErr { + t.Errorf("wanted error, got none") + return + } + }) + } +}