summaryrefslogtreecommitdiffstats
path: root/internal/cryptokit/ed25519.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/cryptokit/ed25519.go')
-rw-r--r--internal/cryptokit/ed25519.go102
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
+}