From fd8edfb70a60858f2d172c1ac5ac7ca7107a9b28 Mon Sep 17 00:00:00 2001 From: shchva Date: Fri, 10 Jan 2025 18:35:11 +0300 Subject: [PATCH] create: pkg algo/rs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pkg algo/rs подписывает данные с помощью ключа RSA --- algo/rs/option.go | 102 ++++++++++++++++++++++++++++++++++++++ algo/rs/rs.go | 119 +++++++++++++++++++++++++++++++++++++++++++++ algo/rs/rs_test.go | 92 +++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 algo/rs/option.go create mode 100644 algo/rs/rs.go create mode 100644 algo/rs/rs_test.go diff --git a/algo/rs/option.go b/algo/rs/option.go new file mode 100644 index 0000000..d83acab --- /dev/null +++ b/algo/rs/option.go @@ -0,0 +1,102 @@ +package rs + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "os" +) + +type Option func(*Algo) error + +// WithGenerateKey генерирует RSA ключ +func WithGenerateKey(bits int) Option { + return func(a *Algo) error { + key, err := rsa.GenerateKey(rand.Reader, bits) + if err != nil { + return err + } + + a.p = key + + return nil + } +} + +// WithKey устанавливает приватный ключ +func WithKey(key *rsa.PrivateKey) Option { + return func(a *Algo) error { + a.p = key + return nil + } +} + +// WithPub устанавливает публичный ключ +func WithPub(pub *rsa.PublicKey) Option { + return func(a *Algo) error { + if a.p == nil { + a.p = &rsa.PrivateKey{} + } + a.p.PublicKey = *pub + return nil + } +} + +// WithKeyPemFile загружает приватный ключ из pem-файла +func WithKeyPEMFile(file string) Option { + return func(a *Algo) error { + buf, err := os.ReadFile(file) + if err != nil { + return err + } + + block, _ := pem.Decode(buf) + key, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return err + } + + return WithKey(key)(a) + } +} + +func WithPEM(pub, key []byte) Option { + return func(a *Algo) error { + if len(key) > 0 { + block, _ := pem.Decode(key) + val, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return err + } + + return WithKey(val)(a) + } + + block, _ := pem.Decode(pub) + val, err := x509.ParsePKCS1PublicKey(block.Bytes) + if err != nil { + return err + } + + return WithPub(val)(a) + } +} + +// WithPubFile загружает публичный ключ из pem-файла +func WithPubFile(file string) Option { + return func(a *Algo) error { + buf, err := os.ReadFile(file) + if err != nil { + return err + } + + block, _ := pem.Decode(buf) + pub, err := x509.ParsePKCS1PublicKey(block.Bytes) + if err != nil { + return err + } + + return WithPub(pub)(a) + } +} diff --git a/algo/rs/rs.go b/algo/rs/rs.go new file mode 100644 index 0000000..22f69d9 --- /dev/null +++ b/algo/rs/rs.go @@ -0,0 +1,119 @@ +package rs + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + "io" + + "git.daebt.dev/auth/algo" +) + +const ( + KeyRSA algo.KeyType = "RSA" + AlgorithmRS256 algo.AlgorithmType = "RS256" + AlgorithmRS384 algo.AlgorithmType = "RS384" + AlgorithmRS512 algo.AlgorithmType = "RS512" +) + +type Algo struct { + k algo.KeyType + a algo.AlgorithmType + h crypto.Hash + p *rsa.PrivateKey +} + +func (a *Algo) Sign(payload []byte) ([]byte, error) { + val, err := a.hasher(payload) + if err != nil { + return nil, err + } + + return rsa.SignPKCS1v15(rand.Reader, a.p, a.h, val) +} + +func (a *Algo) Verify(payload, signature []byte) error { + val, err := a.hasher(payload) + if err != nil { + return err + } + + if rsa.VerifyPKCS1v15(&a.p.PublicKey, a.h, val, signature) != nil { + return algo.ErrInvalidSignature + } + + return nil +} + +func (a *Algo) Key() algo.KeyType { + return a.k +} + +func (a *Algo) Algo() algo.AlgorithmType { + return a.a +} + +func (a *Algo) hasher(payload []byte) ([]byte, error) { + val := a.h.New() + + if _, err := val.Write(payload); err != nil { + return nil, err + } + + return val.Sum(nil), nil +} + +func (a *Algo) WriteKeyPEM(w io.Writer) error { + return pem.Encode(w, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(a.p), + }) +} + +func (a *Algo) WritePubPEM(w io.Writer) error { + return pem.Encode(w, &pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: x509.MarshalPKCS1PublicKey(&a.p.PublicKey), + }) +} + +func newRS(a *Algo, o ...Option) (*Algo, error) { + for _, f := range o { + if err := f(a); err != nil { + return nil, err + } + } + + if a.p == nil { + return nil, errors.New("key is not initialized") + } + + return a, nil +} + +func NewRS256(o ...Option) (*Algo, error) { + return newRS(&Algo{ + k: KeyRSA, + a: AlgorithmRS256, + h: crypto.SHA256, + }, o...) +} + +func NewRS384(o ...Option) (*Algo, error) { + return newRS(&Algo{ + k: KeyRSA, + a: AlgorithmRS384, + h: crypto.SHA384, + }, o...) +} + +func NewRS512(o ...Option) (*Algo, error) { + return newRS(&Algo{ + k: KeyRSA, + a: AlgorithmRS512, + h: crypto.SHA512, + }, o...) +} diff --git a/algo/rs/rs_test.go b/algo/rs/rs_test.go new file mode 100644 index 0000000..1d15dd8 --- /dev/null +++ b/algo/rs/rs_test.go @@ -0,0 +1,92 @@ +package rs_test + +import ( + "encoding/hex" + "fmt" + "testing" + + "git.daebt.dev/auth/algo/rs" +) + +var pub = `-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAzIWvl1OtwExnQ3HvoYk6bFRIlcCjzdv1yHazJfr6jxk6w+tCsIWE +dtKsAPm3gmmFG+mTuHq+H53sahm6DD9YC5ZQjnvSYkBKv70Zw331/tg9VLbfJc+g +N7kbD3xMQsucYD0973r7l9pEPH4Qw/I+BEKHMxlTmynStgKxnfyO6iPkL5jTzpUX +lD4V9xqUoMY/uX3EpGwbJJKFJuphYX3jzJQ++tovQGGep7RgNeMEoWjyAkJ2yb7t +iSWsw7qk6GO2z6NDmnc1UsdSBZ6Vg7BPUp8EINAdX1wbmB0+QH/vp0huM6lSY6NM +BugwptQUe5UCaly9fN4kb26U0qglEoGB6wIDAQAB +-----END RSA PUBLIC KEY-----` + +var key = `-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAzIWvl1OtwExnQ3HvoYk6bFRIlcCjzdv1yHazJfr6jxk6w+tC +sIWEdtKsAPm3gmmFG+mTuHq+H53sahm6DD9YC5ZQjnvSYkBKv70Zw331/tg9VLbf +Jc+gN7kbD3xMQsucYD0973r7l9pEPH4Qw/I+BEKHMxlTmynStgKxnfyO6iPkL5jT +zpUXlD4V9xqUoMY/uX3EpGwbJJKFJuphYX3jzJQ++tovQGGep7RgNeMEoWjyAkJ2 +yb7tiSWsw7qk6GO2z6NDmnc1UsdSBZ6Vg7BPUp8EINAdX1wbmB0+QH/vp0huM6lS +Y6NMBugwptQUe5UCaly9fN4kb26U0qglEoGB6wIDAQABAoIBADU244UgRKkwN/4Y +ex0ws37UPz6XrQc3IDBUkjBjqSXqjpvDbsq3Mswn7JEkaFcKVZP5pnHtneJkGMtS +flIJeUMqjTNFjGv8Bnb1IOr4rzTr1qlgG5ee+jUFeMECumT2zW1NAfx5p1TPecmz +k3EoanJ5TOxCvro0m5q4ALb2q8jHrtfvtqEBrHepeEp3Lyh7m4ZUib+0yWXs0EPC +HhF9kLpCy+tVXUPDyLCt4cldTUda/3xeswzmxVRrHkt/idsNuTAi7o1Cx/OfZYMI +AzQo8OTh1Bg2DlXKtOX40frIxy3/K77F3ozwV4a3FravUO+wvcQdIyi6KAmNBFS3 +9IVA7IECgYEA4x0TAn7vOvazILmg4Es0/gsWlh7RGmNTqVYtj5TfwOviUGeaculR +BSsyX8pgaROJKjGDcNzSQQEhHfJXrhNXeMJ0zPUIsJCBmih8oaCpAScWpV+qpdky +1Eb2akEg7XbpqBJJ1jnoEvIhd4feCAN8Gv8vcmdER7HaGdyef4XxlFsCgYEA5okG +tbyTtD2cfmYjYsoGqEfGH0Pe9vxc+MBthiPg0f2lpg+YuSPx92ZuJciLNyNWo9qf +NFnzbSEFxzomK/Bgq9ujGnbPyLOCadIADM4/njEEPe+IsagDxBgTrCEUJ56W9MLj +N+b4d/gnBkK4roDW8gjy7x4MbePByoDfaWtU/bECgYEApn8RCZpe7V4gMdSEIQph +fgBI/aL37p10nsbDvegJJRiIoCNjsexj7iMd2eW2SjH9M4Z68smgBfG7AoZASyh4 +ztnX4M2eIjq+GHKn86GhZGvwiSoaI12YitC/I2Q9rHipkQJfSQLIpOMHL+bWGg/b +8rqzYO5duyWiW6VGOPzL/tMCgYB3JVSZcrfnzHvn+8PIF9+u80FbAUnn3m/yhAlW +7Y4RGYWWOLNW5FP26DJ/RpFk0tfBYYksllywBwQkflIiHV7pE1/NmqAy+0uog0dR +VvscN/sYQ4cjQlGH9GWebY4sF9Ou9lZWmwHJhzAsFSm7zozIlIVxvdbwqGiMz2Qn +6LgJUQKBgQC9H2JGm54wg0YPuDig5LjymUxYJrEiJT0IXz4vy+UEMxw+1EmeD5sm +kSqHkwNDp7D+3nik5HzoFVifJAvqFWU73fpvqQlvZSNfVrtq8UvJBIuH7eHkrJrC +L8dEn16HWjLX50GlT+9eYyHWtYI4sMdnzz1/JS6PwQRxKlFQN9HJYg== +-----END RSA PRIVATE KEY-----` + +func TestMain(t *testing.T) { + data := []*struct { + a func(o ...rs.Option) (*rs.Algo, error) + o []rs.Option + f func(a *rs.Algo) error + }{ + { + a: rs.NewRS256, + o: []rs.Option{ + rs.WithPEM(nil, []byte(key)), + }, + f: func(a *rs.Algo) error { + _, err := a.Sign([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9}) + return err + }, + }, + { + a: rs.NewRS256, + o: []rs.Option{ + rs.WithPEM([]byte(pub), nil), + }, + f: func(a *rs.Algo) error { + buf, err := hex.DecodeString("80e9b1c2a38cdb7c383a146597699e76a4b0c606e70eaec59998ff08168f2d51f487c625e30917fcbed75ff40ab1ee723599120fbbc9255d0ea442e0c9c8d76abcae7c5e8c6f56b9b07c30bf217538ce9ea073c4c2e1ef3a24b322a328175293e9b57c0c8d8e034d5e5ecdb059368114861730243af296aedf942d2a9f38d26599a5450845224907fa1599b792b81356bd53985401aaa0a3ad5f02e77d28008f181d3e3aafce4d40ebb770e70c8837c60e3eb307064bc49a2b35e3b73a155fbc490723ec7919aa9740f8c787b7586de1deed8480030bea44dc7ac4882a90a89596b78f58a41fd3e5e0269e6b75490e8e6e63717f0f67f54ba34593cc5520b93f") + if err != nil { + return err + } + p := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9} + + return a.Verify(p, buf) + }, + }, + } + + for i, v := range data { + t.Run(fmt.Sprint(i), func(t *testing.T) { + val, err := v.a(v.o...) + if err != nil { + t.Fatal(err.Error()) + } + if err := v.f(val); err != nil { + t.Fatal(err.Error()) + } + }) + } +}