mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-05-05 20:40:37 +00:00
119 lines
3.0 KiB
Go
119 lines
3.0 KiB
Go
package cryptoutil
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
|
|
v1 "github.com/garethgeorge/backrest/gen/go/v1"
|
|
"google.golang.org/protobuf/proto"
|
|
)
|
|
|
|
const keyIDPrefix = "ed25519."
|
|
|
|
type PublicKey struct {
|
|
proto *v1.PublicKey
|
|
publicCryptoKey ed25519.PublicKey
|
|
}
|
|
|
|
func NewPublicKey(pubkey *v1.PublicKey) (*PublicKey, error) {
|
|
pubBytes, err := base64.RawStdEncoding.DecodeString(pubkey.Ed25519Pub)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode public key: %w", err)
|
|
}
|
|
if len(pubBytes) != ed25519.PublicKeySize {
|
|
return nil, fmt.Errorf("invalid ed25519 public key size: got %d, want %d", len(pubBytes), ed25519.PublicKeySize)
|
|
}
|
|
|
|
edPubKey := ed25519.PublicKey(pubBytes)
|
|
if derived := deriveKeyId(edPubKey); derived != pubkey.Keyid {
|
|
return nil, fmt.Errorf("public key_id provided does not match the derived key: %s != %s", derived, pubkey.Keyid)
|
|
}
|
|
|
|
return &PublicKey{
|
|
proto: pubkey,
|
|
publicCryptoKey: edPubKey,
|
|
}, nil
|
|
}
|
|
|
|
func (pk *PublicKey) KeyID() string {
|
|
return pk.proto.Keyid
|
|
}
|
|
|
|
func (pk *PublicKey) PublicKeyProto() *v1.PublicKey {
|
|
return proto.Clone(pk.proto).(*v1.PublicKey)
|
|
}
|
|
|
|
func (pk *PublicKey) Verify(message, sig []byte) error {
|
|
if !ed25519.Verify(pk.publicCryptoKey, message, sig) {
|
|
return errors.New("signature verification failed")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type PrivateKey struct {
|
|
*PublicKey
|
|
proto *v1.PrivateKey
|
|
privateCryptoKey ed25519.PrivateKey
|
|
}
|
|
|
|
func NewPrivateKey(privkey *v1.PrivateKey) (*PrivateKey, error) {
|
|
seed, err := base64.RawStdEncoding.DecodeString(privkey.Ed25519Priv)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("decode private key: %w", err)
|
|
}
|
|
if len(seed) != ed25519.SeedSize {
|
|
return nil, fmt.Errorf("invalid ed25519 private key seed size: got %d, want %d", len(seed), ed25519.SeedSize)
|
|
}
|
|
edPrivKey := ed25519.NewKeyFromSeed(seed)
|
|
|
|
pubKey, err := NewPublicKey(&v1.PublicKey{
|
|
Keyid: privkey.Keyid,
|
|
Ed25519Pub: privkey.Ed25519Pub,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
derivedPub := edPrivKey.Public().(ed25519.PublicKey)
|
|
if !bytes.Equal(derivedPub, pubKey.publicCryptoKey) {
|
|
return nil, errors.New("private key does not match public key")
|
|
}
|
|
|
|
return &PrivateKey{
|
|
PublicKey: pubKey,
|
|
proto: privkey,
|
|
privateCryptoKey: edPrivKey,
|
|
}, nil
|
|
}
|
|
|
|
func GeneratePrivateKey() (*v1.PrivateKey, error) {
|
|
pub, priv, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("generate ed25519 key: %w", err)
|
|
}
|
|
|
|
return &v1.PrivateKey{
|
|
Keyid: deriveKeyId(pub),
|
|
Ed25519Priv: base64.RawStdEncoding.EncodeToString(priv.Seed()),
|
|
Ed25519Pub: base64.RawStdEncoding.EncodeToString(pub),
|
|
}, nil
|
|
}
|
|
|
|
func (pk *PrivateKey) PrivateKeyProto() *v1.PrivateKey {
|
|
return proto.Clone(pk.proto).(*v1.PrivateKey)
|
|
}
|
|
|
|
func (pk *PrivateKey) Sign(message []byte) ([]byte, error) {
|
|
return ed25519.Sign(pk.privateCryptoKey, message), nil
|
|
}
|
|
|
|
func deriveKeyId(key ed25519.PublicKey) string {
|
|
shasum := sha256.Sum256(key)
|
|
return keyIDPrefix + base64.RawURLEncoding.EncodeToString(shasum[:])
|
|
}
|