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 }