diff options
Diffstat (limited to 'internal/cryptokit/ed25519.go')
| -rw-r--r-- | internal/cryptokit/ed25519.go | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/internal/cryptokit/ed25519.go b/internal/cryptokit/ed25519.go new file mode 100644 index 0000000..0e08503 --- /dev/null +++ b/internal/cryptokit/ed25519.go @@ -0,0 +1,102 @@ +package cryptokit + +import ( + "crypto/ed25519" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "errors" + "fmt" + "strings" +) + +func decodeKeyText(value string, expected ...int) ([]byte, error) { + value = strings.TrimSpace(value) + if value == "" { + return nil, errors.New("key material is required") + } + decoded, base64Err := base64.StdEncoding.DecodeString(value) + if base64Err != nil { + decoded, base64Err = base64.RawStdEncoding.DecodeString(value) + } + if base64Err != nil { + var hexErr error + decoded, hexErr = hex.DecodeString(value) + if hexErr != nil { + return nil, errors.New("key material is not valid base64 or hexadecimal") + } + } + for _, size := range expected { + if len(decoded) == size { + return decoded, nil + } + } + return nil, fmt.Errorf("unexpected key length %d bytes", len(decoded)) +} + +// SignEd25519 signs with a raw Ed25519 private key supplied as base64 or hex. +// Both the 32-byte seed and 64-byte private-key encodings are accepted. +func SignEd25519(message []byte, privateKey string) (string, error) { + key, err := decodeKeyText(privateKey, ed25519.SeedSize, ed25519.PrivateKeySize) + if err != nil { + return "", err + } + if len(key) == ed25519.SeedSize { + key = ed25519.NewKeyFromSeed(key) + } + signature := ed25519.Sign(ed25519.PrivateKey(key), message) + return base64.StdEncoding.EncodeToString(signature), nil +} + +// Ed25519PublicKey derives the public key from a raw Ed25519 private key and +// returns it as standard base64. Both the 32-byte seed and 64-byte private-key +// encodings are accepted. +func Ed25519PublicKey(privateKey string) (string, error) { + key, err := decodeKeyText(privateKey, ed25519.SeedSize, ed25519.PrivateKeySize) + if err != nil { + return "", err + } + if len(key) == ed25519.SeedSize { + key = ed25519.NewKeyFromSeed(key) + } + publicKey := ed25519.PrivateKey(key).Public().(ed25519.PublicKey) + return base64.StdEncoding.EncodeToString(publicKey), nil +} + +// Ed25519PublicKeyFingerprint returns a stable SHA-256 fingerprint for a +// base64 or hexadecimal Ed25519 public key. +func Ed25519PublicKeyFingerprint(publicKey string) (string, error) { + key, err := decodeKeyText(publicKey, ed25519.PublicKeySize) + if err != nil { + return "", err + } + digest := sha256.Sum256(key) + return "sha256:" + hex.EncodeToString(digest[:]), nil +} + +// CanonicalEd25519PublicKey accepts a raw Ed25519 public key as base64 or +// hexadecimal and returns the protocol representation used by VFace. +func CanonicalEd25519PublicKey(publicKey string) (string, error) { + key, err := decodeKeyText(publicKey, ed25519.PublicKeySize) + if err != nil { + return "", fmt.Errorf("decode Ed25519 public key: %w", err) + } + return base64.StdEncoding.EncodeToString(key), nil +} + +// VerifyEd25519 verifies a base64 or hexadecimal Ed25519 signature against a +// raw public key supplied as base64 or hex. +func VerifyEd25519(message []byte, signature, publicKey string) error { + sig, err := decodeKeyText(signature, ed25519.SignatureSize) + if err != nil { + return fmt.Errorf("decode Ed25519 signature: %w", err) + } + key, err := decodeKeyText(publicKey, ed25519.PublicKeySize) + if err != nil { + return fmt.Errorf("decode Ed25519 public key: %w", err) + } + if !ed25519.Verify(ed25519.PublicKey(key), message, sig) { + return errors.New("Ed25519 signature verification failed") + } + return nil +} |
